【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
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
62 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
15天前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
1月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
43 3
|
1月前
|
算法 Java
JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
本文详细介绍了JVM中的GC算法,包括年轻代的复制算法和老年代的标记-整理算法。复制算法适用于年轻代,因其高效且能避免内存碎片;标记-整理算法则用于老年代,虽然效率较低,但能有效解决内存碎片问题。文章还解释了这两种算法的具体过程及其优缺点,并简要提及了其他GC算法。
 JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
6天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
4天前
|
Java Linux Windows
JVM内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制。
8 1
|
1月前
|
存储 缓存 算法
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
|
23天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
46 10