欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

JVM垃圾回收,

来源: javaer 分享于  点击 37762 次 点评:274

JVM垃圾回收,


垃圾回收与内存分配策略

  • 垃圾回收与内存分配策略
    • “垃圾”的定义
      • 对象是否为“垃圾”
      • 何为“引用”--四种引用类型
      • 最后的挣扎--finalize()方法
      • 回收方法区
    • 垃圾回收算法
      • 回收的前置--分代理论
      • 标记-清除算法(Mark Sweep)
      • 标记-复制算法
      • 标记-整理算法(Mark Compact)
      • 标记?清除:整理
    • 经典垃圾回收器
      • Serial收集器
      • ParNew收集器
      • Parallel Scavenge搜集器
        • 参数说明
      • Serial Old 收集器
      • Parallel Old 收集器
      • CMS收集器
      • Garbage First 收集器
    • 低延迟垃圾收集器
      • Shenandoah 收集器
      • ZGC 收集器

“垃圾”的定义

对象是否为“垃圾”

判断对象是否已成为“垃圾”的两种方法:引用计数法可达性分析算法

  • 引用计数法

如果一个对象被引用一次,则加1,如果没人引用则被回收;存在问题:如果两个对象循环引用,但是没有任何外部对象引用他们俩,则那两个对象无法被回收。

  • 可达性分析算法(主流JVM采用)

没有被根对象(GC ROOT)直接或简介引用的对象则会被回收
根对象--肯定不能对回收的对象
GC ROOT对象:system class、同步锁、线程类、本地方法类

何为“引用”--四种引用类型

JDK1.2以后将引用分为:强引用、软引用、弱引用和虚引用4种,强度依次减弱。

  • 强引用
    被GC ROOT直接引用(等号赋值
  • 软引用
    被GC ROOT间接引用;当内存不足时被回收,内存充足时不会被回收
  • 弱引用
    没有GC ROOT直接引用,当发生垃圾回收时,不管内存是否充足都会被回收
  • 虚引用
    没有GC ROOT直接引用,虚引用使用时必须配合引用队列进行管理。

比如创建一个ByteBuffer实现类对象时,会创建个一个Cleaner对象,当ByteBuffer实现类对象没有再被引用时,ByteBuffer实现类对象会被回收,Cleaner对象则会进入引用队列,这时候一个referencehandles线程会查找引用队列中是否存在cleaner对象,如果有则调用Cleaner.clean方法,clean方法则根据记录的直接内存的地址,调用unsafe.freememory方法释放直接内存

  • 补充:引用队列

软引用、弱引用本身也要占用一定内存,当软引用、弱引用的引用对象都被回收时,则进入引用队列,会对引用队列进行后续管理;虚引用引用的对象被释放后,虚引用会进入引用队列

最后的挣扎--finalize()方法

即使可达性分析后,对象被判定为“垃圾”,也并非非死不可。一个对象的死亡至少需要两次标记:

没有与GC Root的引用链,标记一次
对象没有重写finalize()方法,或finalize()重写但已被调用过一次,标记第二次

如果重写了finalize()方法,且还没有被调用,那么对象会被放置在F-Queue的队列中,会有一条虚拟机自建的、优先度较低的线程Finalizer线程去执行对象的finalize()方法,但为了防止finalize()方法出现死循环等异常,并不会保证等待finalize()方法执行结束。在此期间,若对象建立了引用链,则对象可以存活一次,否则就“死定了”。

不建议使用该finalize()方法

回收方法区

方法区的垃圾回收主要包含两部分:废弃的常量、不再使用的类型

常量的回收类似与Java堆中的对象,当没有引用时,则允许回收
类型的回收相对比较苛刻,需要同时满足以下条件,才允许被回收

  • 该类所有实例都已被回收
  • 该类的类加载器已被回收
  • 该类对应的java.lang.Class对象没有被引用,且在任何地方都不可以通过反射访问该类方法

垃圾回收算法

从判定垃圾消亡的角度出发,垃圾回收算法可以划分为“引用计数式垃圾收集”、“追踪式垃圾收集”两类。在Java虚拟机中的讨论都在追踪式垃圾收集的范畴中。

回收的前置--分代理论

分代设计的理论建立在两个分代假说之上:

标记-清除算法(Mark Sweep)

先标记需要回收的对象,再统一清除

效率不稳定,随着对象数量增多,标记、清除两个过程的执行效率降低
内存碎片化,导致存入大对象时无法获得足够的连续内存空间,触发另一次垃圾收集动作

标记-复制算法

将可用内存划分为两个完全相等空间,每次只使用其中的一块。如果其中的一块内存用完,则将存活的对象完全复制到另一块,再对原来的空间进行统一清除回收。

  • 缺点
    内存空间的浪费
    若空间内大量对象都是存活的,复制的开销增大
  • 优点
    简单高效
    不用考虑内存空间碎片化
    PS.
    现商用Java虚拟机多在新生代中采用该方法
  • Appel式回收
    HotSpot虚拟机中的Serial、ParNew等新生代收集器均采取该策略。具体如下:
    把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配只使用Eden和一块Survivor,发生垃圾回收时,将存活的对象一次性复制给另一块Survivor空间内,然后清理已用的空间。

    HotSpot虚拟机给Eden和Survivor默认大小比例为8:1,也就是说会有10%的空间会被浪费。当预留的10%的内存空间存不下存活的对象时,,就需要依赖其它内存空间(大多为老年代)进行内存分配。

标记-整理算法(Mark Compact)

区别与标记--清除算法,标记--整理算法,在标记后将存活的对象移向一端,然后将另一端的空间整体回收,是一种移动式的算法。

  • 优点
    不存在碎片化内存,则无需依赖复杂的内存分配器
  • 缺点
    对象的移动操作需要触发“Stop The World”耗时较久

标记?清除:整理

标记-清除是一种非移动式算法、标记-整理是一种移动式算法,两者比较说明:

  • 吞吐量比较
    吞吐量定义:赋值器和收集器效率之和
    不移动会使得收集器效率增大,但是内存分配和访问会比垃圾回收频率高得多,所以整体吞吐量还是降低的。

  • 举例说明
    HotSpot虚拟机中关注吞吐量的Parallel Scavenger收集器基于标记-整理算法;关注低延迟的CMS收集器基于标记-清除算法

  • 混合方案
    使虚拟机多数时间采用标记-清除算法,暂时容忍碎片的存在,等到碎片化程度开始影响对象的内存分配时,在采用标记-整理算法收集一次(CMS就采取该方式)

经典垃圾回收器

所谓“经典”垃圾回收器是指区别于实验室阶段的、已通过应用实践的垃圾回收器。

HotSpot垃圾回收器

Serial收集器

Serial:新生代:标记-复制算法
Serial Old:老年代:标记-整理算法
HotSpot虚拟机运行在客户端模式下的默认新生代收集器
简单高效、内存消耗最小

ParNew收集器

ParNew:新生代:标记-复制算法
Serial Old:老年代:标记-整理算法
激活CMS后,默认的新生代收集器
Serial的多线程版本,默认开启的线程数与CPU核心数相同

Parallel Scavenge搜集器

标记-复制算法,与ParNew相似
关注点在于达成可控制的吞吐量(吞吐量=用户代码运行时间/总时间;总时间=用户代码运行时间+垃圾回收时间)

参数说明

  • -XXMaxGCPauseMillis更关注停顿时间
    一个大于0的毫秒数,尽量使回收时间不超过这个值
    实现原理:牺牲吞吐量和新生代空间获取,小内存新生代空间的回收速度一定由于高内存速度,但是回收频率也会增加

  • -XXGCTimeRatio更关注吞吐量
    0到100之间的整数,代表垃圾回收时间占总时间的比率,相当于吞吐量的倒数

  • -UserAdaptiveSizePolicy
    开关函数,激活后虚拟机会根据当前运行情况自动调整Eden与Survivor的内存比例、老年代内存大小等参数,已提供合适的停顿时间和最大吞吐量

Serial Old 收集器

serial 收集器的老年版本,标记-整理算法
在CMS收集器并发失败时的预备方案

Parallel Old 收集器

Parallel Scavenge 收集器的老年版本,标记-整理算法
在注重吞吐量或处理器资源稀缺时使用

CMS收集器

获取最短停顿时间的为目标,采用并发-清除算法

  • 工作步骤

整个过程中,并发标记和并发清除耗时最久

  • 关键问题

Garbage First 收集器

建立可预测的停顿时间模型,开创了面向局部收集的内存设计思路,基于Region的内存布局形式。默认停顿时间为200毫秒

  • 基于Region的内存布局
    把连续的Java堆内存划分为多个大小相等的独立空间,每个空间都可以扮演Eden、Survivor空间或者老年代空间,其中Humongous区域转为收集大对象(大小超过了一个Region的对象,Region的大小可通过参数调整),G1大多会把Humongous当做老年代看待。收集器可以根据不同的角色采取不同的收集策略。

  • 局部收集思想
    Region作为每次回收的最小内存单位,每次收集到的空间都是Region的整倍数,G1会跟踪Region堆积的“价值”大小(回收所获空间/回收所需时间的经验值),再后台维护一个优先级列表,优先回收价值大的Region

  • 工作步骤
  • 关键问题
  • G1与CMS
    • 优点:
      可以指定最大停顿时间、分Region的内存布局、按收益动态回收、不会产生内存碎片、回收完成后可提供规整的可用内存
    • 缺点:
      内存占用、程序执行的额外负载都较高
      G1的卡表更为复杂;运行负载方面,CMS使用写后屏障来更细维护卡表,而G1为了实现原始搜索(SATB)快照算法,还需要写前屏障来跟踪并发时的指针变化情况,G1能减少并发标记和重新标记的消耗,避免像CMS那样在最终标记阶段停顿时间过长。CMS直接同步处理,而G1异步处理
  • 总结
    小内存上使用CMS有优势,而大内存状态下使用G1有更多优势,而Java堆内存容量平衡点大约在6-8GB之间(经验数据)

低延迟垃圾收集器

Shenandoah 收集器

ZGC 收集器

相关文章

    暂无相关文章
相关栏目:

用户点评