今天在家本来是闲暇的一天,很舒适,结果这个时候,妈妈敲门进来我房间了,咨询我有没有时间帮忙打扫一下父母的房间;(没有时间

当然我不能这么说了,我是个炒鸡孝顺的好孩子,当然了,妈妈,当然有时间了啊,now go,我的乖乖,这么乱的屋子,不对啊,平时都是很干净的啊(内心想逃,后悔,想拒绝

不对啊,妈,为什么房间这么乱啊,这有的东西我也不知道要不要扔掉啊,瞬间难到我了,你们生活中有没有遇到过类似的烦恼?

或者有没有遇到纠结一个东西要不要扔掉的时候,那时候你是如何做的呢?

我们知道在JVM内存中,实例对象基本都是存在于堆中的,那总不能无期限的往里面放吧,一些用不着的对象就需要随时回收掉,这样才能保证这个内存的均衡性,才能保证JVM的正常运行

那么问题来了,JVM如何知道哪些对象该回收、哪些不该回收,就像刚才大鱼不知道爸妈房间哪些东西该收拾、哪些不该收拾一个道理的,其实在JVM中是有两种解决办法的,分别是引用计数法和可达性分析法两种方法,来确定这些对象之中哪些是存活着的、哪些是已经死去的(不可能再被任何途径使用的对象)

问题明白了,下面就是来解决这个问题了,冲吧,干饭人

引用计数算法

这个其实很简单了,重点就是计数;给对象添加一个引用计数器,每引用一次,计数器加一;引用失效的时候,计数器减一;当计数器为0 的时候,则认为不可能被再次使用了;

我觉得不需要大鱼多解释了应该,这个应该及其好理解,但是,这种方法存在一个致命的问题:无法解决对象相互循环引用的问题

解释下这个循环引用问题

一起来看看下面这个例子

public class ReferenceCountingGC {public Object instance = null;private byte[] bigSize = new byte[2 * 1024 * 1024];public static void main(String[] args) {ReferenceCountingGC o1 = new ReferenceCountingGC();ReferenceCountingGC o2 = new ReferenceCountingGC();o1.instance = o2;o2.instance = o1;o1 = null;o2 = null;//假设在这行发生了GC,o1和o2是否被回收System.gc();}

上面例子中o1和o2对象都分别将对方作为自己的属性注入,这也就是形成了所谓的循环引用;最后o1和o2对象都置为null,也就是栈中不再指向堆中的实例对象地址,但是他们还是会互相引用,所以不会被GC回收

再来看个图解版,加深理解

刚new的o1和o2对象是这个样子的:

分别引用了双方之后是这样子的状态:

最后置为null变成这个样子的:

是的,没错,最后就变成了如上图所示的尴尬境地,对象1和对象2在内部互相引用,永远失效不了,导致GC通过引用计数法判断他们的引用计数的时候,永远无法判断为0,也就是无法回收咯,不就造成了内存泄漏了吗

可达性分析法

上面说的引用计数法有缺点,而且这个问题还不小,所以现在使用这种方式来作为判断对象是否存活标准的比较少,多数使用的是另一种,可达性分析法

先来解释下可达性分析法

基本思路就是通过一系列的”GC Roots“的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径就是引用链,当一个对象到GC Roots没有任何的引用链可达的时候,则证明这个对象是不可用的

什么意思呢?来个白话文版本的,就是选择一系列的基准点,这个点能通过引用链连接到的对象就被认为是可用的,只要是无法到达的,都被认为是不可用的,这个不可用并不一定代表对象死亡,只代表对象无法触达,无法再次引用

这就像递归定义的关系一样,如果只定义了递归项而不定义初始项的话,关系也就无从成立,无从开始;如果初始项定义漏掉了内容的话,递推的结果也会随之而漏掉;

什么是GC Roots

垃圾回收时,JVM会首先找到所有的GC Roots,这个过程叫做枚举根节点,这个过程需要暂停用户线程,也就是stop the world;然后再从GC Roots这些根节点向下搜索,可达的对象保留,不可达的便会回收掉

那么,到底什么是GC Roots呢?

GC Roots就是对象,就是JVM确定当前绝对不能回收的对象,只有找到这种对象,后面的搜索才会有意义,不能被回收的对象所依赖的对象也就必然不能回收

GC Roots是一种特殊的对象,是Java程序在运行过程中所必须的对象,而且必须是根对象

哪些对象可以作为GC Roots

基本可以作为GC Roots的对象基本分为两大类:全局对象和执行上下文

全局对象

  • 方法区静态属性引用的对象:全局对象的一种,Class对象本身很难被回收,回收的条件也是很苛刻,只要Class不被回收,静态成员不会被回收

  • 方法区常量池引用的对象:全局对象,比如字符串常量池,常量初始化之后不会再次改变

执行上下文对象

  • 方法栈的栈帧本地变量表引用的对象:线程方法执行的时候,会将方法打包成一个栈帧入栈执行,方法里得到的局部变量会存放到本地变量表中,只要方法未执行完,还没出栈,即本地变量表还会被访问,GC不应该回收

  • JNI本地方法栈引用的对象:和上面同样的道理

  • 被同步锁持有的对象:被synchronized锁住的对象不可回收,否则锁就失效了,那锁就没意义了

不可达的对象一定会回收吗?(缓刑阶段)

其实被判定为 不可达的对象,也不一定是”非死不可“的,还有一次复活机会,这时是处于缓刑阶段,要真正宣告一个对象死亡,至少要经历再次标记过程(其实就是finalize方法在搞怪)

我们在电视中也是经常见到类似的场景,一个人被判定死刑了,午时已到,立即执行,一般这个时候就会出来一个飞刀,刀下留人,皇上有旨;也有可能是一个飞刀,直接二话不说,噼里啪啦一顿操作,把人救走,是不是很熟悉

没错,这个过程就是finalize的内部过程,让被判定死刑的犯人”重获新生“

标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链

第一次标记

筛选的条件是这个对象是否有必要执行finalize()方法;若对象未重写这个方法或者已被虚拟机调用过,虚拟机则认为没有必要执行,对象被回收

第二次标记

若这个对象有必要执行finalize方法,则这个对象会被放到一个F-Queue队列中,并在稍后由虚拟机自动创建的一个低优先级的finalizer线程去执行;

这里的执行指的是虚拟机会触发这个方法,但是不保证运行完成,这样做的原因是这个方法执行缓慢,也可能出现死循环,严重可能会导致回收系统崩溃

finalize是对象逃脱死亡命运的最后一次机会,稍后GC会对F-Queue中的对象进行二次标记,如果在这里面重新和GC Roots挂上引用关系,则可以逃脱被回收的命运;否则,就肯定GG

方法区的回收

很多人认为方法区没有垃圾回收,Java虚拟机规范中也确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区中的垃圾回收的性价比一般比较低,在上面说的堆中进行一次垃圾回收会回收70—95的空间,而永久代中的垃圾回收的效率远低于此

方法区中的垃圾回收主要是两部分:废弃常量和无用的类;废弃常量的回收和Java堆中的对象类似,不多说了

但是判断一个类是否是无用的类,则条件比较苛刻,需要满足三个条件:

  • 该类的所有实例都已经被回收,即Java堆中无该类的任何实例

  • 加载该类的ClassLoader已经被回收

  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问到该类的方法

虚拟机规范中说的是满足上面三个条件,便可以对无用的类进行回收,但是并不是必然回收;是否对类对类进行回收,可以根据虚拟机提供的参数来进行控制

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类的卸载功能,以保证永久代不会溢出

我爱总结

我爱总结之JVM如何判断哪些对象可以回收,总结很重要,整理思路,记得后续的温故而知新,GitHub地址在下面,我会把所有原创技术文章放到上面,持续不断的更新

  • 引用计数法:存在循环引用的致命问题

  • 可达性分析法:以GC Roots作为起点,可以达到的就不可回收,不可达到的暂定认为”死亡“;但是不是非死不可,有通过finalize方法加重新连接引用链的方法,让一个对象重新复活;但是不保证执行完成,这种方法是不靠谱的,也是不建议使用的

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

JVM如何判断哪些对象可以回收?相关推荐

  1. 【JVM进阶之路】垃圾回收机制和GC算法之三色标记(三)

    JVM往期文章 [JVM进阶之路]内存结构(一) [JVM进阶之路]玩转JVM中的对象(二) 上篇文章中讲到JVM中的对象以及判断对象的存活,那么对于"已死"的对象应该如何处理,怎 ...

  2. Java对象垃圾回收调用,JVM垃圾回收之哪些对象可以被回收

    1.背景 Java语言相比于C和C++,一个最大的特点就是不需要程序员自己手动去申请和释放内存,这一切交由JVM来完成.在Java中,运行时的数据区域分为程序计数器.Java虚拟机栈.本地方法栈.方法 ...

  3. [转载] JVM中对象的回收过程

    参考链接: JVM是否创建Main类(具有main()的类)的对象 当我们的程序开启运行之后就,就会在我们的java堆中不断的产生新的对象,而这是需要占用我们的存储空间的,因为创建一个新的对象需要分配 ...

  4. 深入理解JVM(三)——JVM之判断对象是否存活(引用计数算法、可达性分析算法,最终判定),Eclipse设置GC日志输出,引用

    本文转载自https://blog.csdn.net/ochangwen/article/details/51406779 本文是基于周志明的<深入理解Java虚拟机> 堆中几乎存放着Ja ...

  5. 垃圾回收之如何判断对象可以回收、四种引用以及实际案例操作

    垃圾回收 JVM内存结构中的堆存在垃圾回收机制,我们接下来就来详细地学习一下垃圾回收的相关知识. 1. 如何判断对象可以回收 1.1 引用计数法 只要一个对象被其他变量所引用,那就让这个对象的计数+1 ...

  6. jvm内存分配及对象创建和回收过程

    个人博客:https://suveng.github.io/blog/​​​​​​​ Java历史 2004.9 jdk1.5 tiger 自动装箱拆箱,泛型,,注解,枚举,变长参数,增强for循环 ...

  7. jvm学习笔记(3)——java对象的内存分配和对象的回收(GC)

    引言: 之前的文章已经提过,java对象实例是存放在堆上的,至于是在伊甸区.存活区还是老年区,这些都是从对象回收(GC)角度来进行的逻辑划分.所以我们先说对象的回收(GC),然后再依据GC的策略来说明 ...

  8. 一文详解,jvm内存分代与垃圾回收原理

    jvm运行时数据区 Java程序启动后,本质上就是启动一个jvm进程,jvm会将自己管理的内存划分为几个区域,每个区域都有自己的用途.在程序运行时的内存区域主要可以划分为五个,分别是:方法区.堆.虚拟 ...

  9. 七种垃圾收集器和垃圾回收、分代收集、GCROOTS相关概念、GC如何判断一个对象可以被回收

    文章目录 垃圾收集器概述 垃圾回收算法 1)标记-清除算法(Mark-Sweep)(DVM 使用的算法) 2)复制算法(Copying) 3)标记-整理算法(Mark-Compact) 4)分代收集( ...

最新文章

  1. linux下批量修改文件名的方法
  2. gpg加密命令 linux_用 PGP 保护代码完整性(四):将主密钥移到离线存储中 | Linux 中国...
  3. java线程池应用的好处_java高级应用:线程池全面解析
  4. Python基础(二)--数据类型,运算符与流程控制
  5. 高级java面试宝典
  6. java字符串替换一部分_字符串中部分字符替换
  7. Tomb.Finance的每周更新(5.16-5.22)「Harry大财主的每周二更新」
  8. wi7计算机桌面删除,win7系统删除桌面右键多余选项
  9. win10下装win7双系统_win10下怎么装win8系统 win10下装win8系统方法【详细教程】
  10. curl 访问 IPv6 url
  11. Mac上安装MySQL图文教程(解决了临时密码和编码集问题)
  12. 如何用python画爱心?
  13. NYOJ-999-师傅又被妖怪抓走了
  14. win系统C++的udp通信(接收并发送)详细教程、win下inet_pton和inet_ntop无法使用解决方法
  15. java编写md5加密解密算法
  16. C++程序设计_图书管理系统的控制台实现
  17. 静态方法:关于Java8中的日期时间API,你需要掌握这些!!
  18. 为什么这几年电脑病毒不见了?
  19. 利用python 绘制有效边界efficient frontier
  20. 机器学习笔记(十)——这样推导SMO算法才易理解

热门文章

  1. ChatGPT 中文指南
  2. java schtasks 不生效,关于计划的任务:在Windows的schtasks命令中指定“启动”目录...
  3. 中国古代帝王的十大驭人术
  4. ldap 服务器性能调优,监控OpenLDAP服务器性能指南.pdf
  5. python Django框架之URL与视图(3)
  6. Python 右键没有(idle)EDIT WITH IDLE
  7. cmd命令行查看mysql数据库命令
  8. [BZOJ1008]越狱
  9. rabbitmq单机到集群完整搭建
  10. 全国青少年电子信息智能创新大赛(决赛)python·模拟二卷,含答案解析