如何判断对象已死?

引用计数算法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器减1;其中计数器为0的对象是不可能再被使用的已死对象。

引用计数算法的实现很简单,但有个巨大的缺点,当两个对象相互引用时,这两个对象就不会被回收,导致内存泄漏。

可达性分析算法

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所经过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(在图论中称为对象不可达)时,这个对象就是不可用的。

在java语言中,可作为GC Roots的对象包括:

虚拟机栈(栈帧中的本地变量表)中引用的对象

方法区中类静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI引用的对象

关于java中的引用

jdk1.2之前,java中的引用指的就是另一块内存的起始地址。

jdk1.2之后,java对引用的概念进行了扩充,将引用分为:

强引用(Strong Reference):在程序代码中普遍存在,只要强引用还存在,垃圾回收器就不会回收被引用对象 ==> Object obj = new Object();

软引用(Soft Reference):是用来描述一些还有用但非必须的对象,在系统将要发生OutOfMemoryError时,才会对被引用对象进行回收 ==> SoftReference类

弱引用(Weak Reference):跟软引用一样也是来描述非必需对象的,但强度更弱,被引用对象只能存活到下次垃圾收集发生之前。无论当前内存是否充足,都会进行回收。 ==> WeakReference类,WeakHashMap类

虚引用(Phantom Reference):也称为幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,唯一的目的就是能在这个对象被收集器回收时收到一个系统通知 WTF? ==> PhantomReference类

生存还是死亡

在可达性分析算法中不可达的对象也不是“非死不可”,这个时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少需要经历两次标记过程。当一个对象被判定为不可达时,会先进行第一次标记并判断是否需要执行finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已经被调用过,则判定为不需要执行finalize()方法。finalize()方法是对象逃脱死亡命运的最后一次机会,GC会将对象放置在F-Queue队列中,由低优先级的Finalizer线程去执行。虚拟机会触发finalize()方法,但不保证会等待它执行结束,因为finalize()方法可能执行缓慢或发生死循环。稍后GC会将F-Queue中的对象进行第二次标记,如果对象想要成功救赎自己就需要迅速的将自己与引用链上的任何一个对象建立关联,那么在第二次标记时它将会被移出即将回收的集合。

回收方法区

很多人认为方法区(HotSopt中的永久代)是没有垃圾收集的,java虚拟机规范中也没有要求需要对方法区实现垃圾收集。

永久代垃圾收集主要回收两部分:

废弃常量

回收废弃常量与回收java堆中的对象非常相似。例如常量池中的“abc”字符串在当前系统中没有被任何一个String对象引用,也没有在其他地方被引用,而且必要的话,这个“abc”常量就会被系统清理出常量池。

无用的类

类需要满足下面3个条件才能算是“无用类”。

该类的所有实例都已经被回收

加载该类的ClassLoader已经被回收

该类对应的Class对象没有在任何地方被引用,即无法通过反射获取该类的方法

虚拟机可以对无用类进行回收,但不是一定会回收。

垃圾收集算法

标记-清除算法

标记-清除算法是最基础的收集算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收。之所以所它是最基础的收集算法,是因为后续的收集算法都是基于这个思路并对其不足进行改进而得到的。

主要不足有两个:

效率,标记和清除两个过程的效率都不高。

空间,标记清除后会产生大量不连续的内存碎片,导致以后需要分配较大对象时,无法找到足够的连续内存而不得提前触发垃圾收集动作。

“标记-清除”算法示意图

复制算法

为了解决效率问题,一种称为“复制”的收集算法出现了,它将可用内存分为大小相等的两块,每次只使用其中一块。当这块内存用完了,就将还存活的对象复制到另一块上,然后将已使用过的内存空间一次清理掉。这样就不需要考虑内存碎片等复杂情况,简单高效,是典型的空间换时间,内存消耗太大。

复制算法示意图

现在的商业虚拟机都采用这种收集算法来回收新生代,由于新生代中98%的对象是“朝生夕死”的,所以不需要按照1:1的比例来划分空间,而是把内存分为一块较大的Eden区和两块较小的Survivor区,每次使用Eden和其中一块Survivor。HotSpot虚拟机默认Eden:Survivor = 8:1。当然我们无法保证每次回收都只有不多于10%的对象存活,当Survivor空间不足是,需要依赖其他内存(这里是老年代)进行分配担保(Handle Promotion),多余的对象会直接通过分配担保进制进入老年代。

标记-整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会降低,而且还需要额外空间进行分配担保,以应对被使用内存中对象100%存活的极端情况,所以在老年代一般不能直接选择这种算法。标记-整理算法就是根据老年代的特点设计的。该算法第一步与标记-清除算法一样,第二步则是将所有存活对象都向一端移动,然后直接清理掉其他内存。

“标记-整理”算法示意图

分代收集算法

目前商业虚拟机垃圾收集都采用“分代收集”(Generational Collection)算法,其原理就是根据对象存活周期的不同将内存划分为几块,如新生代和老年代,然后根据各个年代的特点采用最适当的收集算法。

GC算法实现基础

枚举根节点

在可达性分析中,GC必须先从GC Roots节点找引用链,然后逐个检查里面的引用,可作为GC Roots的节点主要在全局性引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中。另外,可达性分析必须在一个能确保一致性的快照中进行,这里的一致性是指在整个分析期间整个系统看起来就像被冻结在某个时间点上,不可以出现对象引用关系还在不断变化的情况,该点不满足的话分析结果准确性就无法得到保证。这点是导致GC进行时必须停顿所有java执行线程(Stop The World)的其中一个重要原因,即使在号称几乎不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。

安全点

由于程序在运行时导致引用变化的指令非常多,因此在程序执行时并非在所有地方都能停顿下来开始GC,只有到达特定的位置即安全点(Safepoint)才能暂停。

Safepoint的选定既不能太少以致于让GC等待时间太长,也不能太多以致于过分增大运行时的负荷,还有一个需要考虑的问题就是如何在GC发生时让所有线程都“跑”到最近的安全点上再停顿下来。这里有两种方案可供选择:

抢先式中断(Preemptive Suspension):不需要线程执行代码主动区配合,在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它跑到安全点上。现在几乎没有虚拟机实现采用抢先式中断来暂停线程从而响应GC事件。

主动式中断(Voluntary Suspension):当GC需要中断线程时,不直接对线程进行操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。

安全区域

对于Safepoint来说,有一个最大的缺点就是需要等待线程走到安全的地方去中断挂起,例如Sleep状态或Blocked状态。这个时候线程无法响应JVM的中断请求,显然JVM也不大可能等待线程重新被分配CPU事件。对于这个情况,就需要安全区域(Safe Region)来解决。安全区域是指在一段代码片段中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的。当线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,这样,在这段时间内JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。当线程要离开Safe Region时,要检查系统是否完成了根节点枚举(或者整个GC过程),如果完成则继续执行,否则就必须等待直到可以安全离开Safe Region的信号为止。Safe Region可以看作是Safepoint的扩展。

垃圾收集器

这里以JDK 1.7 Update 14之后的HotSpot虚拟机为例

HotSpot虚拟机的垃圾收集器

图中展示了7中作用与不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所在区域表示它属于新生代收集器还是老年代收集器。

Serial收集器

Serial收集器是最基本、发展历史最悠久的收集器,该收集器是一个单线程的收集器。这个单线程说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程(Stop The World),直到收集结束。

Serial收集器运行示意图

ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本。

ParNew收集器运行示意图

从ParNew收集器开始,后面还会接触到几款并发和并行的收集器。在这之前有必要了解并发和并行:

并行(Parallel):指多条垃圾收集线程并行工作,但此时用户相似仍然处于等待状态

并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续执行,而垃圾收集程序运行在另一个CPU上。

Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,使用复制算法与并行多线程。它的特点在于关注点与其他收集器不同,目的是为了达到一个可控制的吞吐量(Throughput)。所谓的吞吐量 = 运行用户代码时间/(运行用户代码事件+垃圾收集时间),垃圾收集时间越短响应速度越快,吞吐量越大运算速度越快。对于Parallel Scavenge收集器来说还有一个重要参数-XX:+UseAdaptiveSizePolicy,当这个参数打开后,就不需要手动指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会自动根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应调节策略(GC Ergonomics),这是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,使用单线程和标记-整理算法。

Serial Old收集器运行示意图

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。

Parallel Old收集器运行示意图

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,使用多线程和标记-清除算法,对于重视响应速度的应用来说十分适合。整个运作过程分为4步:

初始标记(CMS initial mark):仅仅只标记GC Roots能直接关联到的对象

并发标记(CMS concurrent mark):进行GC追踪(GC Roots Tracing)的过程

重新标记(CMS remark):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段停顿时间一般会比初始标记阶段稍长一点,但远比并发标记的时间短

并发清除(CMS concurrent sweep):对标记对象进行清除

Concurrent Mark Sweep收集器运行示意图

CMS收集器有3个明显的缺点:

对CPU资源非常敏感:在并发阶段,会占用一部分CPU资源,从而导致应用程序变慢,总吞吐量降低

无法处理浮动垃圾(Floating Garbage):浮动垃圾是指在并发清理阶段用核线程还在运行并产生新的垃圾,这部分垃圾出现在标记之后,CMS无法在当此处理掉它们,只好等待下一次GC。这意味着CMS收集器必须预留一部分空间提供并发收集的程序使用,会导致GC频率增加。

基于标记-清除算法

G1收集器

G1收集器的优点

并行与并发

分代收集

空间整合

可预测的停顿

在G1之前的其他收集器进行收集时,都是对整个新生代或者老年代进行回收,而G1收集器不再是这样。G1收集器将java堆分为多个独立区域(Region),每个区域之间相互独立,多个Region共同组成Eden、Survivor和老年代。这样G1收集器在进行垃圾收集时,就可以以Region为单位进行,而不需要对整个java堆进行全区域的垃圾收集。此时还有两个问题:

对象在所在Region死亡时,并不意味着对于java堆中其他的Region也是死亡的。难道还需要扫描这个java堆才能保证准确性?

对于这个问题,虚拟机时采用Remembered Set来避免全堆扫描的。每个Region都有相对应的Remembered Set,用于记录其他Region中的引用信息,这样就可以避免全堆扫描。

对象大小超过Region最大容量

虚拟机中定义了巨无霸区域(Humongous regions)来存储大对象,但是对于大对象的回收貌似没有什么好办法。

ps:在java8中,持久代也移动到了普通的堆内存中,改为元空间。

G1收集器运行示意图

其他的一些策略

对象优先在Eden分配

大对象直接进入老年代

长期存活对象将进入老年代

动态对象年龄判定

空间分配担保

参考文章

java虚拟机手动内存分配_《深入理解java虚拟机》-垃圾收集器与内存分配策略相关推荐

  1. java虚拟机内存监控_深入理解JVM虚拟机9:JVM监控工具与诊断实践

    本文转自: https://juejin.im/post/59e6c1f26fb9a0451c397a8c 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到 ...

  2. java jvm垃圾回收算法_深入理解JVM虚拟机2:JVM垃圾回收基本原理和算法

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 喜欢的话麻烦点下Star哈 文章将同步到我的个人博客: www.how ...

  3. java方法区内存泄露_深入理解java虚拟机-第二章:java内存区域与内存泄露异常...

    2.1概述: java将内存的管理(主要是回收工作),交由jvm管理,确实很省事,但是一点jvm因内存出现问题,排查起来将会很困难,为了能够成为独当一面的大牛呢,自然要了解vm是怎么去使用内存的. 2 ...

  4. 深入java虚拟机 第四版_深入理解Java虚拟机-常用vm参数分析

    Java虚拟机深入理解系列全部文章更新中... https://blog.ouyangsihai.cn/shen-ru-li-jie-java-xu-ni-ji-java-nei-cun-qu-yu- ...

  5. java if在内存中_全面理解Java内存模型

    Java 内存模型的抽象 在 java 中,所有实例域.静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用"共享变量"这个术语代指实例域,静态域和数组元素).局部变量( ...

  6. java垃圾回收策论_深入理解 Java 虚拟机【3】垃圾收集策略与算法

    作者:杨立滨 链接:https://github.com/yanglbme/jvm 程序计数器.虚拟机栈.本地方法栈随线程而生,也随线程而灭:栈帧随着方法的开始而入栈,随着方法的结束而出栈.这几个区域 ...

  7. 全面理解java内存模型_深入理解Java内存模型(八)——总结

    处理器内存模型 顺序一致性内存模型是一个理论参考模型,JVM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JVM和处理器内存模型在设计时会对顺序一致性模型做一些放松,因为如果完全按照顺序 ...

  8. java中对象的生存期_深入理解Java虚拟机-判断对象是否存活算法与对象引用

    我们知道Java中的对象一般存放在堆中,但是总不能让这些对象一直占着内存空间,这些对象最终都会被回收并释放内存,那么我们如何判断对象已经成为垃圾呢?这篇文章会提出两种算法解决这个问题.另外,本文还要谈 ...

  9. java虚拟机的生命周期_深入理解Java虚拟机——JVM的生命周期

    package test; public class JVMTestLife { public static void main(String[] args) { new Thread(new Run ...

  10. 深入jvm虚拟机第4版_深入理解JVM虚拟机

    自动内存管理机制 Java虚拟机原理 所谓虚拟机,就是一台虚拟的机器.他是一款软件,用来执行一系列虚拟计算指令,大体上虚拟机可以分为 系统虚拟机和程序虚拟机, 大名鼎鼎的Visual Box.Vmar ...

最新文章

  1. ospf路由汇总的目的
  2. SQL CROSS JOIN
  3. NIO详解(五):Buffer详解
  4. JavaWeb学习笔记——JSTL核心标签库
  5. python基础学习11----函数
  6. python3.6.8卸载_Mac 卸载 彻底删除 自己下载的 python 3
  7. nx二次开发c语言,NX二次开发-UFUN API函数编程基础
  8. .NET垃圾回收机制 转
  9. TransactionScrope 2
  10. (8)matplotlib 将点连成线
  11. window - 安装 tomcat
  12. 为什么要有红黑树?什么是红黑树?
  13. Nginx的alias/root/try_files实战
  14. 马云的江湖 史玉柱的兵法
  15. 万象优鲜生鲜配送系统源码 团队开发
  16. Excel表格的基本操作,看这里,excel表格数据汇总教程简单易学
  17. 男人至少的道德底线(男女都该看)
  18. Devil May Cry 1 台词及翻译
  19. 微软必应词典客户端 案例分析
  20. 真能处,180公里的纯电续航,这款车居然一点都没亏

热门文章

  1. 行内块元素(HTML、CSS)
  2. HoloLens 2开发:使用Gaze开发,视线小球不停向眼端移动
  3. Angular 启动项目时 port 4200 is already in use 解决方法
  4. Opencv之生成棋盘标定板
  5. edge安装包_Chromium版Edge浏览器将支持多平台,Windows版支持IE模式
  6. arcgis怎么只显示一个图斑_森林监测、图斑核查必备技能
  7. 【汇编语言与计算机系统结构笔记17】MIPS 汇编初步
  8. 【李宏毅2020 ML/DL】P8-9 Optimization for Deep Learnin | 优化器技术总结,SGDM 与 Adam 对比与使用建议
  9. Android-解决ViewFlipper与ScrollView滑动响应事件拦截的问题【转】
  10. easyui tree的简单使用