JVM系列 从一到掌握JVM系列之垃圾回收算法
Posted wx61cd7f82847ee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM系列 从一到掌握JVM系列之垃圾回收算法相关的知识,希望对你有一定的参考价值。
标记阶段:引用计数算法
垃圾标记阶段:对象存活判断
- 在堆里存放着几乎所有的 Java 对象实例,在 GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为己经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程我们可以称为垃圾标记阶段。
- 那么在 JVM 中究竟是如何标记一个死亡对象呢?简单来说,当一个对象已经不再被任何的存活对象继续引用时,就可以宣判为已经死亡。
- 判断对象存活一般有两种方式:引用计数算法和可达性分析算法。
方式一:引用计数算法
引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。
对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。
优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。
缺点:
- 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。 - 引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。
- 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
循环引用
当p的指针断开的时候,内部的引用形成一个循环,这就是循环引用,从而造成内存泄漏。
举例
/**
* -XX:+PrintGCDetails
* 证明:java使用的不是引用计数算法
* @author shkstart
* @create 2020 下午 2:38
*/
public class RefCountGC
//这个成员属性唯一的作用就是占用一点内存
private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB
Object reference = null;
public static void main(String[] args)
RefCountGC obj1 = new RefCountGC();
RefCountGC obj2 = new RefCountGC();
obj1.reference = obj2;
obj2.reference = obj1;
obj1 = null;
obj2 = null;
//显式的执行垃圾回收行为
//这里发生GC,obj1和obj2能否被回收?
System.gc();
try
Thread.sleep(1000000);
catch (InterruptedException e)
e.printStackTrace();
通过 -XX:+PrintGCDetails
输出详细信息
[GC (System.gc()) [PSYoungGen: 15497K->696K(76288K)] 15497K->704K(251392K), 0.0013045 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 696K->0K(76288K)] [ParOldGen: 8K->624K(175104K)] 704K->624K(251392K), [Metaspace: 3274K->3274K(1056768K)], 0.0043467 secs] [Times: user=0.05 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 76288K, used 655K [0x000000076ad80000, 0x0000000770280000, 0x00000007c0000000)
eden space 65536K, 1% used [0x000000076ad80000,0x000000076ae23ee8,0x000000076ed80000)
from space 10752K, 0% used [0x000000076ed80000,0x000000076ed80000,0x000000076f800000)
to space 10752K, 0% used [0x000000076f800000,0x000000076f800000,0x0000000770280000)
ParOldGen total 175104K, used 624K [0x00000006c0800000, 0x00000006cb300000, 0x000000076ad80000)
object space 175104K, 0% used [0x00000006c0800000,0x00000006c089c178,0x00000006cb300000)
Metaspace used 3281K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 359K, capacity 388K, committed 512K, reserved 1048576K
我们能够看到,上述进行了GC收集的行为,将上述的新生代中的两个对象都进行回收了
PSYoungGen: 15490K->808K(76288K)] 15490K->816K(251392K)
如果使用引用计数算法,那么这两个对象将会无法回收。而现在两个对象被回收了,说明Java使用的不是引用计数算法来进行标记的。
引用计数算法,是很多语言的资源回收选择,例如因人工智能而更加火热的 Python,它更是同时支持引用计数和垃圾收集机制。
具体哪种最优是要看场景的,业界有大规模实践中仅保留引用计数机制,以提高吞吐量的尝试。
Java 并没有选择引用计数,是因为其存在一个基本的难题,也就是很难处理循环引用关系。
Python 如何解决循环引用?
手动解除:很好理解,就是在合适的时机,解除引用关系。
使用弱引用 weakref,weakref 是 Python 提供的标准库,旨在解决循环引用。
以上是关于JVM系列 从一到掌握JVM系列之垃圾回收算法的主要内容,如果未能解决你的问题,请参考以下文章
直通BAT必考题系列:深入剖析JVM之G1收集器及回收流程与推荐用例
直通BAT必考题系列:深入剖析JVM之G1收集器及回收流程与推荐用例
直通BAT必考题系列:7种JVM垃圾收集器特点,优劣势及使用场景