一个Java对象的死亡证明

1,166 阅读8分钟

本文首发至java技术博客[码上]:jdkcb.com/

前言:

生的对立面不是死亡,而是遗忘。

生的对立面不是死亡,而是遗忘。

生的对立面不是死亡,而是遗忘。

重要的事情说三遍!大家请一定要好好记住这句话,这句话是我们整篇文章的精髓所在。

我信你个鬼,你个世界上最帅最机智勇敢玉树临风风流倜傥的帅哥坏得很。

不信?走着瞧,扯不上去算我输。

概念准备:

VM 洋文全称为Virtual Machine ,中文一般被译为虚拟机,虚拟机一般用来指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。

而 JVM则是代表java语言的虚拟机,洋文全称:Java Virtual Machine。

世界上第一款商用java虚拟机是Sun公司1996年在jdk1.0中发布的Sun Classic VM,很早之前Sun Classic VM就已经被废弃掉了,由HotSot VM虚拟机来代替。

扯蛋环节:

既然你是要写jvm内存回收系列的学习笔记,那关java对象死亡有什么关系?

诶,这你就不对了,怎么能没有关系呢,jvm回收的是什么?是内存,内存被谁占着呢?java对象(主要),那对象不死怎么回收他呢?

对象不死怎么就不能回收了?

杠是吧,扛是吧,扛是吧,我们把jvm虚拟机在垃圾回收过程中的角色理解为我们神话传说中的阎王爷,按照古老的传说,人只有死了之后灵魂才会被回收继续投胎,人活着就算再废物也不能直接让他投胎了,必须要等他死了才行,而且,在人没死之前直接回收这个人的灵魂可能会导致一些很严重的问题,比如你回收个美国的杀人犯的灵魂,那肯定对这个世界不会产生什么特别大的影响,但是万一你要不小心抽到特朗普了,那可能就会引起大乱子。

jvm也是这么考虑的,万一你这个对象别的地方正用着呢,jvm直接给他回收了,就有可能直接导致整个应用的崩溃。

微剧场:

大家好,我是一个java对象,我叫阿呆。万万没想到的是,我现在竟然要证明自己死了。

事情是这样的,我是一个废材对象,当主人把我创造出来时,我满以为自己会光芒万丈大有作为成为java对象之王,可是这货把我new出来之后他娘的竟然把我给忘了,他和其他的对象嬉戏打闹,我却只能在角落里伤心假笑,我感觉整个运行环境的天空都变成了灰色,我不想活了,我太难了,于是我跑到java虚拟机内存回收局恳请可以将我回收掉,像我这种废材没人在乎的对象真的就是活着浪费内存了。

可是java虚拟机内存回收局残忍地拒绝了我,还让我哪凉快哪呆着去,当然我并不打算为此退缩,在我的威逼利诱(苦苦哀求)下,java虚拟机内存回收局终于告诉了我事情的真相。

很遗憾,阿呆先生,这个世界上还有人仍然记得你,所以我们不能把你回收掉。

还有人记得..记得我?(双眼闪烁着泪花)

听完阿呆屁颠屁颠迈着扭秧歌的魔鬼的步伐回去了。(尼玛,刚才还想自杀呢,节操呢?变这么快)

当然,java虚拟机内存回收局肯定不是随随便便就把阿呆认定死亡的,经历过几十年的发展,该机构研发了一款java对象验死仪,原理主要是基于引用计数可达性分析算法这两个算法,那么下面我们就依次来介绍这两种算法。

引用计数算法:

官方概念:引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。

Hello h = new Hello(); 其中h就称之为Hello对象的引用,即我们可以通过h来访问到我们的Hello对象。

在这里我们可以理解为,假如B对象中包含A对象的引用,就意味着B对象和A对象有所联系,即B对象记得A对象。当B对象因为一些原因比如意外死亡,老年痴呆,韩国偶像剧等,不记得A对象的存在了,则我们称之为B遗忘了A,我说嘛,我肯定可以扯到这里的。

每当有一个新的对象和A对象产生联系时,即引用A对象,那么A对象的引用计数器就+1,如果谁不小心把A对象给忘了, 则意味着引用失效了,那么A对象的引用计数器就-1,当A对象的引用计数器变成0的时候,意味着虚拟机中已经没有任何对象还保留着A对象的引用了,即除了A对象自己,没有对象记得A对象的存在了,这个时候A对象就会被判定为死亡,被内存回收掉。

这个时候可能有善良的小伙伴问了,A对象那么可爱,为什么要回收掉它呢,留着它不好吗。

现在我们回归引用的概念,A对象引用计数器为0意味着再也没有任何一个对象可以访问到A对象了,侧面就说明了A对象再也不能被使用,变成了一个彻彻底底的废对象。

所以:生的对立面不是死亡,而是遗忘。

这么一想,引用计数算法的确还不错,这样死亡的对象都会被正确识别出来,从而回收掉。

年轻人,你还是想的太简单了。你忘了这个世界上有奇葩的存在了吗?且看下面这段对话:

A:我诅咒你今年秃头!

B:诅咒反贪

A:我也反弹

B:我再反弹

此处省略N个字。

没完没了,放在对象也是这样,加入A对象包含了B对象的引用,B对象又包含了A对象的引用,因为相互引用,他们的引用计数器都无法为0,也就不会被回收掉了。

所以引用计数在目前主流的java虚拟机中被废弃掉了。

可达性分析算法:

官方概念:可达性分析算法是通过判断对象的引用链是否可达来决定对象是否可以被回收。

从GC Roots作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。图长这样:

可达性分析算法

这个算法就好玩多了,我们可以把这个算法看作是一个巨大的传销集团,而GC Roots就是这个集团的传销头子 ,那它是怎么判断对象是否死亡的呢?

当需要内存回收的时候,虚拟机会沿着传销头子往下依次搜索它的下线,如果这个对象能被搜索到,那么就意味着这个对象是在传销组织里面的,比如Object2 3 4 都在传销集团里面,而Object 5 6 7 呢。

Object 5 6 7就是那些没有组织的野传销,传销集团怎么会容忍这些野传销存在呢,于是他们都被判断为死亡。

所以:生的对立面不是死亡,而是遗忘。

而 Object 5 6 7 这三个对象都被遗忘掉了,因为他们没有组织,因为失去了和组织的联系,所以组织也没有办法差遣他们,发挥他们的价值,于是它们变成废对象被回收掉了。

也就是说呢,在可达性分析算法中,如果一个对象无法被虚拟机沿着GC Roots依次向下搜索到,那么这个对象就是不可达的,因为没有任何一条路通往这个对象,这个对象就像是海上的孤岛一样,最后只能被java虚拟机回收。

的确优势明显,随便你们反弹,没有组织你们都要完蛋。

而GC Roots也不是谁都能当的,在长期的实践中,发现有四类对象拥有成为传销头子,呸,Gc Roots的潜质,他们分别是:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中JNI(Native方法)的引用对象

正因为可达性分析在实际使用中的优异表现,所以很多现代虚拟机都采用了这一实现。

珍爱生命,远离传销,珍爱生命,远离传销,珍爱生命,远离传销!

下面到技术总结环节:

总结:

本篇文章我们可谓是用尽了废话来帮助刚开始学习java或者jvm的朋友们彻底理解jvm判断java对象死亡的两种算法,再学习jvm的过程中,发现很多jvm书籍,博客都写的比较学术,让新手们望而却步,导致很多人看了一遍以为自己懂了,但是过了几天之后再想却没有办法想起来,根本原因还是在于并没有彻底的理解这些知识,为了帮助一些朋友更好的理解jvm虚拟机,所以才有了jvm学习笔记系列,同样的,本系列笔记会开源至github,并保持更新。

我是韩数,我们下篇文章再见。