大家好,又见面了,我是你们的朋友全栈君。
Java虚拟机(JVM)垃圾回收机制
JVM中的垃圾回收计数是采用的一种自适应的技术(可以通过其工作方式将它“啰嗦地”称为:自适的、分代的、停止-复制、标记-清扫式垃圾回收器)
在讲Java虚拟机的自适应回收机制前,有必要说一下其他比较单纯的垃圾回收机制,然后会比较容易地理解JVM的垃圾回收:
1、引用计数
一种简单但速度很慢的垃圾回收计数。
每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加 1 ;当引用离开作用域或者被置为null时,引用计数减 1 。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为 0 时,就释放其占用的空间。
引用计数常用来说明垃圾收集的工作方式,但似乎从未被应用于任何Java虚拟机的实现中。
缺点:
- 虽然管理引用计数开销不大,但这种开销在整个程序的生命周期中是持续发生的。
- 如果对象之间存在循环引用,可能会出现“对象应该被回收,但是其引用计数却不等于 0 ”的情况,且定位如此交互自引用的对象组对于垃圾回收器来说所需的工作量极大
2、找到所有“活”的对象
对于一些更快(回收性能更优秀)的垃圾回收技术,依据的思想:**对于任何“活着”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。 **
JVM便是在基于此种方式采用的一种自适应的垃圾回收技术。不过对于如何处理找到的存活对象,这取决于不同的Java虚拟机实现。
-
“停止-复制”
先停止程序的运行,然后把所有存活的对象从当前堆复制到另一个堆中,其中没有被复制的对象都是“死”的,便是需要清除、释放的垃圾了。
优点:
- 当对象复制到新堆时,在新堆中保持紧凑排列,可以简单、方便地分配新空间。
缺点:
- “复制-回收”并不属于后台回收模式,该种模式下需要事先停止程序运行。即垃圾回收动作发生时候,程序会被暂停。
- 效率不高,原因如下:
- 需要有两个堆,然后在两个堆之间来回折腾,需要维护的空间比实际的多了一倍。
- 程序进入稳定状态时,可能只会产生少量的垃圾,但此时复制式垃圾回收器还是会把几乎所有内存从一处复制到另一处,非常浪费。
-
“标记-清扫”
从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象;每当找到一个存活对象,便会给对象设一个标记。只有全部标记工作完成时候,才会开始清理动作。
优点:
- 清理过程中,没有标记的对象将被释放,不用进行任何复制动作。
缺点:
- 清理后剩下的堆空间是不连续的,如果希望得到连续空间的话,需要重新整理剩下的对象。
3、JVM中自适应的垃圾回收机制
在这里讨论的Java虚拟机中,内存分配以较大的“块”为单位;当对象比较大时,会占用单独的块。
有了块的概念之后,我们可以对“复制-回收”的回收机制做一定的改进。之前的“复制-回收”要求在释放旧有对象之前,必须将所有存活对象从旧堆复制到新堆中,这会导致大量的内存复制行为;有了块之后,我们可以往废弃或闲置的块中复制对象。
那如何判断某个块是不是闲置的呢?每个块都是用对应的一个代数(generation count)记录该块是否还存活;如果该块在某处被引用,其代数便会增加。
垃圾回收器会定期地进行完整的清理动作——大型对象(占用单独的块)不会被复制,而含有小型对象的那些块便会被复制并整理。
在这过程中,JVM会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低了,就切换到“标记-清扫”方式;而当“标记-清扫”下堆空间出现很多碎片时,便会切换回“停止-复制”方式。(所以知道为什么叫他“自适的、分代的、停止-复制、标记-清扫式垃圾回收器”了吧……)
Java虚拟机的回收机制介绍的差不多了,也只是比较表面的介绍,之后有机会、有能力的话再写写更加详细些的,嘻嘻。
参考文献:
《Java编程思想 Thinking in Java》 Bruce Eckel 著
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/133433.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...