JVM垃圾回收(GC)

1.简介

垃圾回收器的主要工作区域是堆内存,JDK1.8之后JVM的堆区分为年轻代和年老代,这样做可以优化GC性能,因为大多数对象存活时间都比较短,对象新创建时将其放入年轻代,年轻代空间满了之后进行回收,称为Minor GC,回收时扫描年轻代,可以腾出来大量的空间。年老代内存占满之后执行Full GC回收年老代的内存空间。

总内存=JVM内存+系统内存

JVM内存=堆+元数据空间+虚拟机栈+本地方法栈+程序计数器

堆=年轻代+年老代

年轻代=Eden区+Survivor区,Survivor区分为大小相等的两部分,默认Eden区和Survivor区的比例为8:2

2.GC原理

  1. 对象被创建时会放到年轻代的Eden区;
  2. Eden区满之后执行Minor GC,将仍存活的对象放入Survivor区;
  3. 多次Minor GC仍未被清除的对象会移动至年老区;
  4. 年老区空间满了之后会触发Full GC。

Minor GC的执行过程

  • 所有新创建的对象都分配在Eden区,经过一次Minor GC存活下来的对象被移到From区;

  • 之后的Minor GC,Eden区存活下来的对象会被移到To区,From中年龄小于阈值的也会被移到To区,年龄大于阈值的会被移到年老区,然后再将From区和To区互换,保证To区为空;当To区满了之后,就把To区的所有对象都移动到年老区;

  • 在Survivor区中,每经过一次Minor GC年龄都会加1,年龄大于15(我看到的资料是这么说的)会被移动到Eden区。

3.如何判断对象可以回收

计数器算法

为每个对象都设置一个计数器,当其被引用时,计数器加1,如果某个对象的计数器为0,则代表这个对象可以回收。

缺点:无法解决循环引用的问题;计数器的使用会带来额外的开销。

可达性分析算法

以GC Roots对象作为起点,往下搜索,搜索的路径为引用链,没有被引用链连接的对象是不可用的,可以被回收,但是再彻底被回收之前还有一次存活的机会,和finalized函数相关,可以再查其它资料了解。

可以作为GC Roots的对象:

  • 在虚拟机栈中(栈帧中的本地变量表)引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等

  • 在方法区中类静态属性引用的对象,譬如Java类的引用静态变量

  • 在方法区中常量引用的对象,譬如字符串常量池里的引用

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

  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointException,OutOfMemoryError)等,还有系统类加载器

  • 所有被同步锁持有的对象

  • 反应Java虚拟机内部情况的JMXBean。JVMTI中注册的回调、本地代码缓存等

补充:面试的时候有被面试官问到为什么这些对象可以作为GC Roots,当时的回答了它们可以永久存在,后被质疑栈的对象肯定不会永久存在。面试完成之后查了一些资料,自己也认真的思考了一下,认为这些对象之所以可以作为GC Roots是因为可以保证它们当前是一定在使用的,比如只要虚拟机栈中有某个局部变量,那么这个方法就还没有执行结束,因此它所引用的对象一定还不能被回收。如果有其它原因,欢迎沟通探讨。

4.垃圾回收算法

  • 标记-清除算法:标记所有可以回收的空间,清除这些内存空间。效率比较低,回收后会产生大量不连续空间。

  • 标记-复制算法:将内存空间划分为大小相同的两部分,一部分用于存放对象,标记该部分中哪个位置可以被清除,然后将所有存活的对象移动到另一块内存空间中,下次再统计另一块内存可以被清除的对象,并移动剩余的对象,如此往复。实现简单、运行效率高,但是内存利用率不高,在新生代中使用,Eden和Survivor比例是8:2。

  • 标记整理算法:标记-复制算法存在的极端情况是对象都没死,在年老代有一定的几率出现,该算法把存活的对象往内存一端移动,回收另一端的内存。

  • 分代收集

    • 年轻代:标记-复制算法

    • 年老代:标记-整理算法

5.垃圾收集器

针对年轻代和年老代不同的特点,设计不同的垃圾收集器。

5.1年轻代

  • Serial:单线程方式、使用“复制算法”

    • 优点:没有线程交互开销,可以获得最高单线程收集效率

    • 缺点:垃圾回收时需要停止其它所有工作线程

  • ParNew

    • Serial的多线程版本

  • Parallel Scavenge:并发多线程的方式,“复制算法”

    • 特点:以“最优吞吐量”作为目标,可以修改参数使其满足“GC自适应调节策略”,不需要设定Eden和Survivor的比例,新生代大小等参数

    • 优点:适用于注重吞吐量和CPU资源敏感的场合

    • 缺点:不能保证垃圾回收时间太短

5.2年老代

  • Serial Old:Serial的年老代版本,单线程收集,使用“标记-整理算法”

    • 使用:与Parallel Scavenge搭配使用;作为CMS收集器的预备方案适用

    • 优点:简单

    • 缺点:单线程,性能较差

  • Parallel Old:Parallel Scavenge的年老代版本,多线程回收,“标记-整理”算法

    • 使用:与Parallel Scavenge搭配使用,避免Serial Old的性能拖累

    • 优点:适用于注重吞吐量和CPU敏感的场合

    • 缺点:不能保证回收时间最短

  • CMS(ConCurrent Mark Sweep):并发多线程回收、“标记-清除”算法,以最短回收时间为目标,使用最多

    • 过程

      • 初始标记:标记GC Roots直接关联的对象,耗时很短

      • 并发标记:根据 GC Roots Tracing过程

      • 重新标记:修正并发期间因为用户程序继续运作而导致标记产生变动的记录,时间略长于初始标记

      • 并发清除

    • 优点:最短回收时间

    • 缺点:比较占用CPU资源,容易造成内存碎片,无法处理“浮动垃圾”,即并发清除期间产生的垃圾,因此等年老代空间还没有满的时候就应该启动垃圾回收过程,在这个过程中可能导致内存空间不够,因此需要Serial Old进行内存碎片的整理;此外,为了解决内存碎片的问题,提供了相应的参数在适当的时候(马上要进行FULL GC的时候)进行内存整理

5.3 G1收集器

将整个内存区域划分为多个region,分别属于不同的数据区域,包括Eden区、Survivor区、年老代等。

特点:面向服务端、并行与并发、分代收集、空间整合、可预测的停顿、使用“标记-整理”算法。比较完美,但是使用较少。

三种回收机制:

  • Young GC:回收年轻代内存,Eden区满的时候触发,依旧活跃的对象晋升到Survivor区或年老代;
  • Mixed GC:回收整个年轻代和一部分年老代,因为回收的年老代的大小可以指定,因此可以预测停顿时间。当年老代空间占用达到一定阈值之后启动Mixed GC,回收过程与CMS类似,包括初始标记、并发标记、重新标记和并发清除四部分。
  • Full GC:Mixed GC来不及收集时导致年老代被填满时触发。

5.4 ZGC垃圾收集器

之前没有了解过这个垃圾收集器,在面试的时候被面试官问到知不知道,说这是一个比较新的垃圾收集器,效率很好,可以自己看一看。

参考文章

  • JDK11推出的一款低延迟垃圾回收器,标记-复制方法,设计目标包括:

    • 停顿时间不会超过10ms

    • 停顿时间不会随着堆的大小或活跃对象的大小而增加,ZGC几乎所有暂停都只依赖于GC Roots集合大小

    • 支持8MB~4TB级别的堆,仅适用于64位操作系统

  • G1垃圾回收周期,STW阶段包括初始标记、再标记、清理、复制。四个STW过程中,初始标记因为只标记GC Roots,耗时较短。再标记因为对象数少,耗时也较短。清理阶段因为内存分区数量少,耗时也较短。转移阶段要处理所有存活的对象,耗时会较长。因此,G1停顿时间的瓶颈主要是标记-复制中的转移阶段STW。为什么转移阶段不能和标记阶段一样并发执行呢?主要是G1未能解决转移过程中准确定位对象地址的问题。

  • ZGC垃圾回收周期:STW阶段包括ZGC几乎所有暂停都只依赖于GC Roots集合大小,其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。

  • 关键技术:ZGC通过着色指针读屏障技术,解决了转移过程中准确访问对象的问题,实现了并发转移。大致原理描述如下:并发转移中“并发”意味着GC线程在转移对象的过程中,应用线程也在不停地访问对象。假设对象发生转移,但对象地址未及时更新,那么应用线程可能访问到旧地址,从而造成错误。而在ZGC中,应用线程访问对象将触发“读屏障”,如果发现对象被移动了,那么“读屏障”会把读出来的指针更新到对象的新地址上,这样应用线程始终访问的都是对象的新地址。那么,JVM是如何判断对象被移动过呢?就是利用对象引用的地址,即着色指针。

    • 着色指针:一种将信息存储在指针中的技术,ZGC将对象存活信息存储在地址空间的42~45位中,这与传统的垃圾回收并将对象存活信息放在对象头中完全不同。

    • 读屏障技术:读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。需要注意的是,仅“从堆中读取对象引用”才会触发这段代码。

6.触发Full GC的几种情况

  • 调用system.gc()方法,不建议使用;
  • 年老代空间不足,导致年老代空间不足的原因可能有多个,第一个是内存泄漏,第二个是大对象和大数组的创建,也可能是同时加载大量数据等等;
  • 持久代空间不足,JDK1.8已经取消持久代;
  • 使用CMS作为垃圾回收器进行垃圾回收时,浮动垃圾过多导致年老代溢出;
  • 统计得到的Minor GC要移动到年老代的空间的大小大于年老代剩余的空间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值