JVM深入理解(GC部分)
JVM 内存区域
虚拟机栈:描述的是方法执行时的内存模型,是线程私有的,生命周期与线程相同,每个方法被执行的同时会创建栈桢,主要保存执行方法时的局部变量表、操作数栈、动态连接和方法返回地址等信息,方法执行时入栈,方法执行完出栈,出栈就相当于清空了数据,入栈出栈的时机很明确,所以这块区域不需要进行 GC。
本地方法栈:与虚拟机栈功能非常类似,主要区别在于虚拟机栈为虚拟机执行 Java 方法时服务,而本地方法栈为虚拟机执行本地方法时服务的。这块区域也不需要进行 GC。
程序计数器:线程独有的, 可以把它看作是当前线程执行的字节码的行号指示器,比如如下字节码内容,在每个字节码`前面都有一个数字(行号),我们可以认为它就是程序计数器存储的内容。
本地内存:线程共享区域,Java 8 中,本地内存,也是我们通常说的堆外内存,包含元空间和直接内存, Java 8 和 Java 8 之前的 JVM 内存区域的主要区别是永久代的功能由元空间代替,由于永久代有 -XX:MaxPermSize 的上限,所以如果动态生成类(将类信息放入永久代)或大量地执行 String.intern (将字段串放入永久代中的常量区),很容易造成 OOM,所以在 Java 8 中就把方法区的实现移到了本地内存中的元空间中,这样方法区就不受 JVM 的控制了,也就不会进行 GC,也因此提升了性能。
堆:对象实例和数组都是在堆上分配的,GC 也主要对这两类数据进行回收。
识别垃圾算法
public class TestRC { TestRC instance; public TestRC(String name) { } public static void main(String[] args) { // 第一步 A a = new TestRC("a"); B b = new TestRC("b"); // 第二步 a.instance = b; b.instance = a; // 第三步 a = null; b = null; } }
2.可达性算法:现代虚拟机基本都是采用这种算法来判断对象是否存活,可达性算法的原理是以一系列名称为 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点...(这样通过 GC Root 串成的一条线就叫引用链),直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为「垃圾」,会被 GC 回收。
回收机制:当对象不可达(可回收)时,当发生GC时,会先判断对象是否执行了 finalize 方法,如果未执行,则会先执行 finalize 方法,我们可以在此方法里将当前对象与 GC Roots 关联,这样执行 finalize 方法之后,GC 会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收
GC Roots:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
垃圾回收主要算法
1.标记清除算法:
先根据可达性算法标记出相应的可回收对象,然后对可回收的对象进行回收,缺点比较明显,回收的空间可能不连续。
2..复制算法
把堆等分成两块区域, A 和 B,区域 A 负责分配对象,区域 B 不分配, 对区域 A 使用以上所说的标记法把存活的对象标记出来,然后把区域 A 中存活的对象都复制到区域 B(存活对象都依次紧邻排列)最后把 A 区对象全部清理掉释放出空间,这样就解决了内存碎片的问题了。
3.标记整理法
前面两步和标记清除法一样,不同的是它在标记清除法的基础上添加了一个整理的过程 ,即将所有的存活对象都往一端移动,紧邻排列,再清理掉另一端的所有区域,这样的话就解决了内存碎片的问题。
4.分代收集算法
垃圾收集器种类
在新生代工作的垃圾回收器:Serial, ParNew, ParallelScavenge
在老年代工作的垃圾回收器:CMS,Serial Old, Parallel Old
同时在新老生代工作的垃圾回收器:G1