【JVM】垃圾回收机制(GC)之引用计数和可达性分析

简介: 【JVM】垃圾回收机制(GC)之引用计数和可达性分析

1. 引用计数

这种思想方法,并没有在 JVM 中使用,但是广泛应用于其他主流语言的垃圾回收机制中(PythonPHP)。

《深入理解 Java 虚拟机》中谈到了引用计数,就导致有些面试官还是会问

给每个对象安排一个额外的空间,空间里要保存当前这个对象有几个引用

Test a = new Test();
Test b = a;
a = null;
b = null;

  • new 出对象的时候,就在堆上开辟了一块空间,并且在前面额外有一块空间用来存储引用计数
  • 当把对象的地址给到栈上的局部变量的时候,这个引用就指向了这个对象,引用计数就变成了 1
  • 当引用 b 同样指向这个对象的时候,引用计数就变成了 2
  • 当引用 a 的值由对象的地址变为 null 的时候,a 引用就销毁了,引用计数变为 1
  • 当引用 b 的值由对象的地址变为 null 的时候,b 引用也销毁了,引用计数变为 0

此时垃圾回收机制发现对象的引用计数为 0,说明这个对象就可以释放掉了

  • 引用计数为 0,就说明这个对象是垃圾了
  • 有专门的线程,去获取到当前每个对象的引用计数的情况

存在问题

引用计数机制,是一个简单有效的机制,存在两个关键问题

1. 消耗额外的内存空间

要给每个对象都安排一个计数器,就算计数器按照两个字节算,整个程序中对象数目很多,总的消耗空间也会非常多;尤其是如果每个对象体积比较小,假设每个对象四个字节,计数器消耗的空间,就达到了对象空间的一半

类似于花钱买 100 平的房子,实际上你房子的使用面积也就 70 多平(非常难受)

2. “循环引用“问题

引用计数可能会产生“循环引用的问题”。此时,引用计数就无法正确工作了

class Test {
  Test t;
}
Test a = new Test();
Test b = new Test();
a.t = b;
b.t = a;
a = null;
b = null;

  • Test 对象里面有一个成员变量 t,他的类型也是 Test,也就是说它也可以引用一个对象
  • a.t = b的意思是:将a引用对象中的t成员变量的值赋为b的引用
  • 所以此时第二个引用对象就会有两个引用指向,一个是 a,一个是 a.t
  • 所以第二个引用对象的引用计数就会变成 2
  • 同理,b.t=a 的结果就是第一个引用计数也会变成 2

  • ab 都被赋值为 0 之后,两个对象的引用计数都变成了 1,但此时这两个对象都没法使用了(双方的引用指向都在对方那里,类似于“死锁”的情况)。由于引用计数不为 0,也没法被回收

2. 可达性分析(JVM 用的)

本质上是用“时间换空间”,相比于引用计数,需要小号更多的额外的时间。但是总体来说还是可控的,不会产生类似于“循环引用”这样的问题

在写代码的过程中,会定义很多的变量。比如,栈上的局部变量/方法区中的静态类型的变量/常量池引用的对象…

  • 就可以从这些变量作为起点出发,尝去进行“遍历”。
  • 所谓遍历就是会沿着这些变量中持有的引用类型的成员,再进一步的往下进行访问
  • 所有能被访问到的对象,自然就不是垃圾,剩下的遍历一圈也访问不到的对象,自然就是垃圾了

比如有如下代码:

class Node {
  char val;
  Node left;
  Node right;
}
Node buildTree() {
  Node a = new Node();
  Node b = new Node();
  Node c = new Node();
  Node d = new Node();
  Node e = new Node();
  Node f = new Node();
  Node g = new Node();
  
  a.right = b;
  a.left = c;
  b.left = d;
  b.right = e;
  e.left = g;
  c.right = f;
  
  return a;
)
Node root = buildTree();

虽然这个代码中,只有一个 root 这样的引用,但是实际上上述 7 个节点对象都是“可达”的

  • b == root. left;
  • c == root. right;
  • d == root. left. left;
  • 依此类推,上述的对象都能通过 . 的方式访问到
    JVM 中存在扫描线程,会不停地尝试对代码中已有的这些变量进行遍历,尽可能多的访问到对象

上述代码中,如果执行这个代码:root.right.right = null;

  • 就会导致 cf 之间断开了,此时 f 这个对象就被“孤立”了
  • 按照上述从 root 出发进行遍历的操作就也无法访问到 f 了,f 这个节点对象就称为“不可达
  • 如果 ac 之间断开了,此时 c 就不可达了。由于访问 f 必须通过 cc 不可达就导致 f 不可达。所以此时 cf 都是垃圾了
  • 如果 root=null,此时整棵树都是垃圾了

JVM 自身知道一共有哪些对象,通过可达性分析的遍历,把可达的对象都标记出来了,剩下的自然就是不可达的了


相关文章
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
56 0
|
25天前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
2月前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
1月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
2月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
63 1
|
2月前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
49 5
|
2月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
2月前
|
算法 Java
JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。 (2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代 (3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代
27 0
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
501 1
|
25天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型