深入理解JAVA虚拟机读书笔记:垃圾收集与垃圾收集算法

概述

我们需要思考三个问题:
1、哪些内存需要回收?
2、什么时候回收?
3、如何回收?

一、哪些内存需要回收?

在Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,这部分不需要过多考虑回收问题。
垃圾收集器主要关注的是Java堆中内存回收。

二、什么时候回收

判断对象是否存活的算法:

1、引用计数算法

给对象添加一个引用计数器,每一个地方引用它时,计数器就加一;当引用失效时,计数器就减一,当计数器为0时,就表示该对象是不可能再被使用。
存在的问题:对象之间互相引用,A对象引用了B,B对象引用了A。实际上这两个对象已经不可能再次被访问,但是他们的引用计数器不为0,导致垃圾收集器无法回收这部分内存。

2、可达性分析算法

通过一系列的成为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,证明此对象是不可用的。
在这里插入图片描述
如图 object5、object6、object7虽然互相引用,但是它们到GC Roots是不可达的,所以它们将会被判定为可回收的对象。
可作为GC Roots的对象包括下面几种:
1、虚拟机栈中引用的对象。
2、方法区中的静态属性引用的对象。
3、方法区中常量引用的对象。
4、本地方法栈JNI中引用的对象。
可达就一定安全吗?
回答是
强引用:对于Object object = new Object();这类的引用默认为强引用,只要引用存在,就不会被回收。
软引用:软引用当要发生内存溢出异常之前回收。SoftReference来实现。
弱引用:下一次垃圾收集发生之前回收。WeakReference实现。
虚引用:形同虚设,无法通过虚引用来取得一个对象实例。仅仅是在这个对象被收集器收集时回答收到一个通知。PhantomReference实现。
不可达就一定死亡吗?
回答也是
如果对象覆盖了finalize()方法并且没有调用过该方法,那么在可达性分析之后,会将对象放入一个F-Queue队列中,并在稍后由一个虚拟机自动建立的、低优先级Finalizer线程去执行该方法(执行但并不等待该方法结束)。稍后虚拟机会对F-Queue中的对象进行第二次小规模的标记,如果对象重新建立引用,那么在第二次标记时会将它移出“即将回收”的集合,如果没有逃逸,那么该对象是真的被回收了。
如果对象没有覆盖finalize()方法,或者调用过该方法,虚拟机就认为“没有必要执行”,直接回收。
我们可以通过覆盖finalize方法来实现一些对象回收前的收尾工作。该方法和C++的析构函数不同,C++析构函数调用的时机是确定的,但是finalize方法调用的时机完全由虚拟机决定。
过程如下:
在这里插入图片描述

3、回收方法区

永久代垃圾回收主要两部分内容:废弃的常量和无用的类。
判断废弃常量:一般是判断没有该常量的引用。
判断无用的类:(要以下三个条件都满足)
1.该类所有的实例都已经回收,也就是 Java 堆中不存在该类的任何实例
2.加载该类的 ClassLoader 已经被回收
3.该类对应的 java.lang.Class 对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法

三、如何回收

1、垃圾回收算法

1、标记-清理算法
标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
优点:简单
缺点:效率不高,产生大量不连续的内存碎片。
在这里插入图片描述
2、复制算法
将可用内存按容量分成大小相等的两块(现在的虚拟机都是8(eden):1(Survivor):1(Survivor)”),每次只使用其中一块。当这块内存用完,就将还存活的对象复制到另外一块上面。然后再把已使用的过的内存一次清理掉。新生代都采用这种收集算法。
优点:弥补了标记-清理算法产生大量内存碎片的缺点。
缺点:内存利用率变低。当Survivor内存不足时,需要其他内存进行分配担保。在对象存活率较高时进行多次复制操作较多,效率会变低。所以通常使用在对象生命周期短的新生代。
在这里插入图片描述
3、标记-整理算法
标记所有存活对象,让他们向一端移动,然后直接清理掉端边界的内存。
优点:避免了上两种算法的缺点。
缺点:效率较低。
在这里插入图片描述
分代收集算法
因为在Java程序中,大部分对象都是“朝生夕死”,很快会被回收的,所以新生代的minorGC的触发频率要远大于老年代的majorGC,这就需要效率更高的“复制算法”,而老年代由于majorGC触发频率比较低,所以可以选择效率较低的“标记整理法”来节约内存。
此外,值得一提的是新生代的eden内存要大于Survivor内存,这样就会出现一个问题:当eden中有一部分对象生命周期一样并且占用的内存大于Survivors时,执行minorGC仍然幸存,Survivor将无法存放这么多的对象。这时老年代就会起到“担保”作用,那些存活的对象将直接晋升到老年代。同理“复制算法”需要有人来做担保,这也是老年代使用“标记整理法”的原因之一。

猜你喜欢

转载自blog.csdn.net/f191501223/article/details/84401021