如何判断对象是否是垃圾
创始人
2024-03-07 12:02:10
0

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

我们都Java会自动进行内存管理,JVM会进行垃圾回收,哪它是怎么判定哪些是“垃圾”并决定“垃圾”的生死呢?

判断对象是否为“垃圾”

Java有两种算法判断对象是否是垃圾:引用计数算法和可达性分析算法。

引用计数算法

引用计数(Reference Counting)算法就是给对象加一个引用计数器,当对象被引用,计数器加一;当引用失效时,计数器减一;当对象的引用计数器为0,对象就会被视为垃圾。

优点: 简单、判定垃圾效率高。

缺点:

  • 需要额外的空间存储引用计数器
  • 每当一个引用被赋值给另一个引用时,引用计数器就要进行调整,增加了赋值语句时间
  • 会出现循环引用。比如,对象a引用了对象b,同时对象b也引用了对象a,这就导致两个对象之间循环引用。对象a和对象b的引用都不为0,即使这两个对象已经没有其他引用,由于它们的引用计数都大于0,所以它们就没有办法被回收。如果要解决这个问题就要引入额外机制,这样效率又进一步降低了。

循环引用

引用计数算法在当前主流的JVM中已经没有再被使用了。

简单的例子测试一下

public class ReferenceCountingTest {public Object instance = null;// 10M 占用内存,便于分析private byte[] bytes = new byte[10*1024*1024];public static void main(String[] args) {ReferenceCountingTest objectA = new ReferenceCountingTest();ReferenceCountingTest objectB = new ReferenceCountingTest();//互相引用objectA.instance = objectB;objectB.instance = objectA;//切断可达objectA = null;objectB = null;//强制进行垃圾回收System.gc();}
}

ReferenceCountingTest类中有一个10M的byte数组,
objectA.instance = objectBobjectB.instance = objectA导致objectAobjectB互相引用,如果采用引用计数法的话,这两个对象是没法办法进行回收的,并且每个对象占用不少于10M的内存空间。

在VM options 设置参数 -XX:+PrintGC打印GC情况,来看下运行结果是怎样的:

[GC (System.gc())  24381K->1106K(249344K), 0.0009894 secs]
[Full GC (System.gc())  1106K->957K(249344K), 0.0054511 secs]

从结果24381K->1106K可以看到内存从24381K回收到1106K,回收的空间差不多就是objectAobjectB两个对象占用的空间。这也从侧面说明JVM不是采用引用计数算法判定对象是否存活的。

可达性分析算法

可达性分析算法思路是使用一系列根对象(GC Roots)作为起点,从根节点开始向下进行搜索,搜索过的路径称为引用链(Reference Chain),如果某个对象到根节点没有任何引用链相连或者说从根节点到这个对象不可达,则这个对象就被视为“垃圾”。

如上图所示,白色椭圆形Object4、Object5、Object6之间虽然有关联,但是由于没有和GC Roots关联,所以它们被判定为可回收对象。

在Java中有以下7种GC Roots:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。比如:方法入参、局部变量等

  • 方法区中常量引用的对象

  • 方法区中类静态属性引用的对象:Java类的引用类型静态变量

  • 通过JNI调用本地代码(nactive code)产生的JNI引用。包括JNI的局部变量或者全局变量

  • 被系统类加载器加载的类,这些类不会被回收。它们可以以静态字段的方式去持有对象。

  • 所有被同步锁(synchronized 关键)持有的对象

  • 被JVM保留用于特殊目的的对象。哪些对象被保留取决于虚拟机的实现,可能的有:系统类加载器、一些重要的异常类、做为异常类处理的被预分配对象或者一些自定义的类加载器。

以上8种GC Roots中前4个比较重要,在面试中也会经常被问到,后3个了解一下即可。

可达性分析算法是目前在动态语言中使用最广泛的算法,目前JVM判断对象是否是垃圾用的都是这种算法。

垃圾的回收

Finalize方法

对象通过可达性分析算法被判定为可回收对象,也不是说对象一定要被回收,对象可以通过重写finalize()方法获得一次“免死”机会。当发生GC的时候,JVM会判断可回收的对象是否调用过finalize()方法,如果调用过finalize()方法,对象将会被回收;反之,如果没有调用过 finalize()方法,会将要调用finalize()方法的对象 F-Queue的队列之中等待调用,在调用时如果对象重写了finalize()方法,可以在finalize()方法中“托关系想办法”让自己和GC Roots搭上关系进行一次自我拯救,比如把自己(this关键字) 赋值给某个类变量或者对象的成员变量,对象就会从即将回收的列表中移除,这样对象就完成了一次自我拯救。在执行完finalize()方法后,还会再判断一次对象是否可达,如果不可达,自我拯救失败,最后还是要被回收的。

要注意的一点是:对象finalize()方法只会调用一次,如果对象自我拯救成功一次,当第二次再发生GC的时候会忽略调用对象的finalize()方法,最后都要被回收。这就是JVM世界的法则,只给对象一次不成为垃圾的机会,如果再次成为垃圾,不好意思那只能被回收了。所以机会只有一次,要好好抓住。

下面通过例子测试一下对象的自我拯救:

public class FinalizeGC {private static Object instance;public static void main(String[] args) throws InterruptedException {instance = new FinalizeGC();instance = null;//进行第一次垃圾回收System.gc();//休眠1sThread.sleep(1000);if (instance != null) {System.out.println("I'm still alive.");}else {System.out.println("I'm dead.");}instance = null;//进行第二次垃圾回收System.gc();//休眠1sThread.sleep(1000);if (instance != null) {System.out.println("I'm still alive.");}else {System.out.println("I'm dead.");}}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("Override finalize method execute");instance = this;}    
}

运行结果:

Override finalize method execute
I'm still alive.
I'm dead.

从运行结果可以看到对象只被自我拯救一次,第二次自我拯救失败。

让线程休眠Thread.sleep(1000)1s是因为F-Queue的队列中的finalize()方法,会由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们,休眠是为了等待Finalizer线程去执行finalize()方法。

Thread.sleep(1000)注释掉连续执行多次,你可能会看到如下情况:

Override finalize method execute
I'm dead.
I'm dead.

或者

I'm dead.
Override finalize method execute
I'm dead.

出现上面的原因是finalize()方法执行缓慢,对象还没有自我拯救就会回收了。所以finalize()方法最好不要使用,太不可靠了,也不要想着用finalize()方法进行自我拯救,finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及时。

方法区回收

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。回收废弃常量与回收Java堆中的对象非常类似。举个常量池中字面量回收的例子,假如一个字符串“suncodernote”曾经进入常量池中,但是当前系统又没有任何一个字符串对象的值是“suncodernote”,换句话说,已经没有任何字符串对象引用常量池中的“suncodernote”常量,且虚拟机中也没有其他地方引用这个字面量。如果在这时发生内存回收,而且垃圾收集器判断确有必要的话,这个“suncodernote”常量就将会被系统清理出常量池。常量池中其他类(接口)、方法、字段的符号引用也与此类似。

判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了,必须同时满足以下的条件(仅仅是可以,不代表必然,因为还有一些参数可以进行控制):

  1. 该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
  2. 加载该类的 ClassLoader 已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法.
  4. 参数控制:-Xnoclassgc参数可以禁用类的垃圾收集(GC),这可以节省一些GC时间,从而缩短应用程序运行期间的中断

总结

本文主要介绍了JVM是通过可达性分析算法来判断对象是否为可回收对象,同时介绍了哪些对象或者是类可作为可达性分析算法的GC Root,最后介绍了对象在Finalize方法中的回收过程和方法区的回收。想学习关于JVM的文章可关注我,会持续更新。

参考书籍:《深入了解JVM虚拟机》

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...