p#### 一、运行时数据区-帧栈详解
完整的Java虚拟机是由三部分组成的:类装载子系统、运行时数据区(内存模型)、字节码执行引擎。其中运行时数据区包含了堆、栈(线程)、本地方法栈、方法区(元空间)、程序计数器。
堆:堆是Java对象的存储区域,任何用new字段分配的Java对象实例和数组,都被分配在堆上,Java堆可使用-Xms -Xmx进行内存控制,值得一提的是从JDK1.7版本之后,运行时常量池从方法区移到了堆上。
方法区:它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,方法区在JDK1.7版本及以前被称为永久代,从JDK1.8永久代被移除。
虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
本地方法栈:与虚拟机栈发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
程序计数器:指示Java虚拟机下一条需要执行的字节码指令。
栈的主要作用是放各个线程的局部变量。那么栈是如何存放这些局部变量的,这就涉及到另一个词——栈帧。比如一个类有多个方法,当一个线程执行到一个方法的时候,虚拟机会马上给这个方法分配一个独立的一块内存区域(即栈帧),一个方法对应一块栈帧内存区域。
这里的栈也就是我们平时所说的数据结构的栈,符合先进后出的原则。如图,当线程执行main方法的时候,栈会分配一个栈帧存放main方法的局部变量。当main方法调用compute方法时,栈会分配一个栈帧存放compute方法的局部变量并且进栈。当compute方法执行结束后,存放compute方法局部变量的栈帧就会被销毁(出栈);接着mian方法会继续往后执行,当main方法执行结束后,main方法的栈帧才被销毁(出栈)。符合先进后出的规律。
二、堆
1、可达性分析算法
将“GC Roots”对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象。
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等。
2、垃圾回收机制
当对象刚创建的时候,会存放在Eden区;当Eden区内存空间不足的时候,会进行一次垃圾回收,即minor gc;有被引用的对象会通过复制算法移动到此时的From区,并且这些对象上的一个叫“分代年龄”的变量会加1,而不在引用链上的对象就会被回收。当进行第二次minor gc时,Eden区和From区上的有被引用的对象会通过复制算法被移动到To区,并且对象的“分代年龄”变量加1,没有被引用的对象被回收;此时,To区变成From区,From区变成To区;如此往复,当对象的“分代年龄”变量值为15时,该对象会被复制移动到老年代。(默认分代年龄15时会移动到老年代,但是这个值我们是可以修改的)
那是不是老年代的对象就永远存在,不会被回收呢?其实,老年代的内存大小也是有限的,当老年代空间不足的时候,会进行一次full GC,对整个堆区域进行垃圾回收,如果回收过后还是空间不足,那么此时就会出现——内存溢出异常(java.lang.OutOfMemoryError)。
每进行一次full GC的时候,线程都会被停止,出现STW(stop the word)现象(虽然minor gc也会STW,但是时间很短,用户几乎感知不到)。如果经常出现STW,用户体验是非常不好的,所以需要通过虚拟机调优来减少STW的出现。