首页 > 编程语言 > 详细

JAVA垃圾回收机制探究

时间:2020-03-19 23:47:18      阅读:61      评论:0      收藏:0      [点我收藏+]

参考资料

资料名称 来源地址
《深入理解JVM&G1 GC》 图书
《深入理解Java虚拟机》 图书
JAVA 8官方文档 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/

什么是垃圾回收

Java一个特点就是引入了垃圾回收机制,Java垃圾回收是指JVM自动对内存中无用对象所占用空间的释放,使Java程序员在编写程序的时候不再需要考虑内存管理,可以有效的防止内存泄露。

为什么要进行垃圾回收

JVM运行过程中会为对象分配内存空间,当对象不会再被使用后,垃圾收集器需要对其所占用空间进行回收,否则迟早会导致内存占用越来越大直到内存溢出,程序就会无法运行。就比如家里的垃圾一直不扔,迟早会塞满整个家,人也没有空间在家里生活下去。

对什么进行回收

垃圾回收主要针对堆和方法区的对象进行回收。线程私有的程序计数器、虚拟机栈和本地方法栈在线程结束之后就被清除,不参与垃圾回收。

如何判断一个对象是否可以被回收

  • 引用计数法

    为对象添加一个引用计数器,每当有一个地方引用它时,计数器值加一;当引用失效时,计数器减一;任何时刻当计数器值为0时说明该对象不可能再被使用,可以被垃圾回收。

    如下代码,当存在两个对象之间的循环引用时,两者的引用计数永远不为0,导致无法对它们进行回收。

    public class Test {
    
        public Object instance = null;
    
        public static void main(String[] args) {
            Test a = new Test();
            Test b = new Test();
            a.instance = b;
            b.instance = a;
            a = null;
            b = null; 
          dosomething();
        }
    }

    因为循环引用问题难以处理,JAVA虚拟机没有选用引用计数法来管理内存。

  • 可达性分析

    在主流的商用语言中如Java、C#的主流实现中都是通过可达性分析来判断对象是存活。这个算法的基本思路就是通过一系列的GC Roots对象作为起点,从这些节点开始向下搜索引用的对象,走过的路径称为引用链当一个对象到GC Roots没有任何引用链相连时,证明此对象是不可用的,所以会被判定为是可回收的对象
    技术分享图片
    在Java中,GC Roots包含以下几种:

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

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

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

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

      对象的finalize()方法

      在可达性分析中不可达的对象,也不一定会被回收,这里涉及到finalize方法的知识:

      • finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法,如果对象覆盖了该方法并且在该方法中将自身重新被引用链中任意对象引用, 就能完成自救。

      • finalize()方法最多只能被GC执行一次,第二次被标记回收时不会再执行该方法

      • finalize()方法不被推荐使用,它能做的所有工作,try-finally都能做得更好更及时

垃圾回收算法

标记-清除

该算法首先标记出需要回收的对象,标记完成后统一回收掉所有的被标记对象

缺点:

  • 效率问题:标记和清除的效率不高

  • 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能导致以后在程序运行过程中产生大量不连续的内存碎片。空间碎片太多可能会导致以后在程序运行中需要分配大对象时因为无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
    技术分享图片

标记-整理

该算法的标记过程与标记-整理算法一致,但不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

优点:不会产生内存碎片
缺点:需要移动大量对象,处理效率较低
技术分享图片

复制

复制算法将可用内存分为大小相等的两块,每次只使用其中的一块,当这块内存用完了就将还存活的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉。

优点:不存在内存碎片问题,实现简单,运行高效。
缺点:内存缩小为了原来的一半。如果对象存活率高就要进行较多的复制操作,效率会变低。
回收前状态:
技术分享图片
回收后状态:
技术分享图片

分代收集

当前的商业虚拟机垃圾收集都使用分代收集算法。该算法思想是根据对象存活周期的不同将内存划分为几块,一般是分为年轻代和老年代,然后根据不同年代的特点采用最合适的收集算法。

新生代:每次垃圾收集都有大批对象死亡,少量存活,适合选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

老年代:对象存活率高,适合使用标记-清理或标记-整理来进行回收。

内存分配和回收策略

技术分享图片

JAVA堆结构

技术分享图片
在JDK1.8版本之前,JAVA堆被划分为年轻代、老年代和永久代,其中年轻代又被划分为eden、s1、s2。

在 JDK 1.8 中, HotSpot 已经没有 永久代这个区间了,取而代之的是 Metaspace(元空间)。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数设置来指定元空间的大小。

MinorGC 和 Full GC

Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。

Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。

内存分配策略

对象优先在Eden分配

大多数情况下,对象在新生代 Eden 上分配,当 Eden 空间不够时,发起 Minor GC。

大对象直接进入老年代

大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。

经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。

-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间的大量内存复制。

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

为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。

-XX:MaxTenuringThreshold 用来定义年龄的阈值。

动态对象年龄判定

虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于
Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。

空间分配担保

在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。

如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。

#### Full GC的触发条件

对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:

  • 调用System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

  • 老年代空间不足

老年代空间不足的常见场景为大对象直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

  • 空间分配担保失败

使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。

  • JDK 1.7 及以前的永久代空间不足

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。

为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。

  • Concurrent Mode Failure

执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。

垃圾收集器

垃圾收集器是内存回收的具体实现。不同厂商、不同版本的虚拟机所提供的垃圾收集器可能有很大差别,一般来说会提供参数供用户根据自己的应用特点组合出各个年代所使用的收集器。
如下分为JDK1.7Update14之后HotSpot虚拟机的7种作用于不同分代的收集器,两个收集器存在连线则说明它们可以搭配使用:
技术分享图片

Serial收集器

Serial收集器是最基本、发展历史最悠久的收集器。
它是单线程的收集器,只会使用一个线程进行垃圾收集工作。
在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束,这个停顿被称为“Stop The World”。
技术分享图片
它的优点是简单高效,在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
它是 Client 场景下的默认新生代收集器,因为在该场景下内存一般来说不会很大。它收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿时间是可以接受的。
启用配置:-XX:+UseSerialGC

ParNew收集器

ParNew收集器是Serial收集器的多线程版本。
技术分享图片
它是 Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用。CMS收集器的介绍后面会进行说明。

Parallel Scavenge 收集器

与 ParNew 一样是多线程收集器。
CMS等收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,因此它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值。

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。

缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。

可以通过一个开关参数-XX:+UseAdaptiveSizePolicy打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。

Serial Old 收集器

是 Serial 收集器的老年代版本,也是给 Client 场景下的虚拟机使用。如果用在 Server 场景下,它有两大用途:

  • 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
  • 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
    技术分享图片

Parallel Old 收集器

是 Parallel Scavenge 收集器的老年代版本。
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。

CMS收集器

技术分享图片
CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记-清除算法。
分为以下四个流程:

  • 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
  • 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
  • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
  • 并发清除:不需要停顿。

在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
具有以下缺点:

  • 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
  • 无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
  • 标记-清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。

相关配置参数:

  • -XX:CMSInitiatingOccupancyFraction设置老年代占用多少百分比激活CMS收集
  • +UseCMSCompactAtFullCollection开关参数,默认开启:由于CMS顶不住要进行Full GC时开启内存碎片的合并整理过程,这个过程会导致停顿时间变长
  • -XX:CMSFullGCsBeforeCompaction:设置执行多少次不压缩的Full GC后来一次压缩的(默认值为0,代表每次进入Full GC都进行碎片整理)

G1收集器

G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离
技术分享图片
通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。
技术分享图片
如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
  • 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

具备如下特点:

  • 空间整合:整体来看是基于“标记-整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
  • 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。

存疑问题

JVM的-server参数和-client参数

Java5以上版本JVM能够根据硬件配置等数据自动判断使用Server模式还是Client模式。
cilent模式与server模式的区别

1)编译器方面:
当虚拟机运行在client模式时,使用的是一个代号为c1的轻量级编译器,而server模式启动时,虚拟机采用的是相对重量级,代号为c2的编译器;c2编译器比c1编译器编译的相对彻底,服务起来之后,性能更高。

2)gc方面:
cilent模式下的新生代(Serial收集器)和老年代(Serial Old)选择的是串行gc
server模式下的新生代选择并行回收gc,老年代选择并行gc

3)启动方面:
client模式启动快,编译快,内存占用少,针对桌面应用程序设计,优化客户端环境的启动时间
server模式启动慢,编译更完全,编译器是自适应编译器,效率高,针对服务端应用设计,优化服务器环境的最大化程序执行速度

使用命令java –version可以查看当前JVM默认工作在哪个模式
技术分享图片

JAVA垃圾回收机制探究

原文:https://www.cnblogs.com/gongshiyun/p/12528253.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!