JVM学习笔记---垃圾收集

3.1 垃圾收集(Garbage Collection)关注的问题

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

 程序计数器、虚拟机栈、本地方法栈三个区域随线程共存亡,内存的分配和回收具备确定性,所以回收比较方便

 Java堆和方法区则不一样,只有在程序处于运行期间才知道哪些对象会创建,所以这部分内存的创建和回收都是动态的

3.2 对象存活判断算法

3.2.1 引用计数算法

 给对象添加一个引用计数器,每当有地方引用他时,计数器加一,引用失效时。计数器减一,任何时刻计算器为0的对象是不可引用的

 缺点:无法解决对象之间相互循环引用的问题

3.2.2 可达性算法分析(Rechbility Analysis)

 以GC Roots 对象为起点,走过的路径为引用链(Reference Chain) 当一个对象不可达GC Roots 时(即从GC到这个对象不可达时),证明此对象不可用

 可以作为GC Root的对象:

  • 虚拟机栈(栈帧中变量表)中引用的对象 局部变量表的对象
  • 方法区中类静态属性应用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即Native方法)引用的对象

可达性分析算法

3.2.3 再谈引用

 JDK1.2后将引用分成了四种

  1. 强引用(Strong Reference) 类似于 Objcet o=new Objcet()只要强引用还在,GC就不会回收
  2. **软引用(Soft Renference)**有用但非必需的对象,将要发生内存溢出之前,就会回收这些对象,如果这次还没有回收到足够的内存才会报内存溢出异常
  3. **弱引用(Weak Renference)**非必须对象,当GC工作时,无论内存是否足够,都会回收
  4. **虚引用(Phantom Renference)**幽灵引用/幻影引用 ,虚引用对对象及其生存周期无影响,唯一目的是在这个对象被垃圾收集的时候收到一个通知

3.2.4 生存还是死亡

 即使在可达性分析中不可达的对象也不是非死不可的**,要宣告一个对象的死亡,至少需要经历两次标记过程**

 当一个对象被发现没有与GC Roots相连的引用链的时候,将被第一次标记而且进行一次筛选,筛选该对象有没有必要执行finalize()方法

  • 如果该对象没有覆盖finalize()方法或者finalize()方法已被虚拟机调用过,则没有必要执行
  • 如果被判定为有必要执行,那么这个对象会放置在一个F-Queue的队列中,并稍后在一个由虚拟机自动建立的、低优先级的Finalizer线程去执行,因为低优先级,所以不承诺等待他的运行结果,避免finalize()方法执行缓慢发生死循环等状态

 故而,finalize()方法是对象逃脱死亡命运的最后一次机会,只要在finalize()方法中和GC Roots的引用链相连就可以逃脱死亡的命运,因为稍后GC将对F-Queue的队列进行第二次小规模标记,如果被标记两次,那么死亡宣告

finalize()方法只会被执行一次,下次会直接被回收

3.2.5 回收方法区

 HotSpot虚拟机中永久代的垃圾回收主要回收两部分内容

  • 废弃常量

    以字符串"abc"为例,如果没有任何String对象引用常量池中的"abc",也没有其他地方引用这个字面量,如果此时发生内存回收且必要的话,那么这个"abc"就会被清理出常量池

  • 无用的类

    类需要满足3个条件才算无用类

    • 该类的所有实例都已被回收,即堆内存中不存在该类的任何实例
    • 加载该类的ClassLoader已被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

    无用类不是必然被回收,是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class 以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class 和 -XX:+TraceClassLoading可以在Product版本的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持

    在大量使用反射、动态代理、GCLib等ByteCode框架、动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出

3.3 垃圾收集算法

3.3.1 标记-清除算法(Mark-Sweep)

 适用于:老年代

 思想:

  • 标记阶段:首先标记处所有需要回收的对象
  • 清除阶段:在标记完成后统一回收所有被标记的对象

 缺点:

  • 效率问题:标记和清除效率都不高
  • 空间问题:标记清除后会产生大量不连续的内存碎片,会导致在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次GC
    Mark-Sweep

3.3.2 标记-整理算法(Mark-Compact)

 适用于:老年代

 思想:标记过程与Mark-Sweep一样,标记后会将所有存活的对象都向一段移动,然后直接请里掉边界以外的内存

Mark-Compact

3.3.3复制算法

 适用于:新生代

 思想:将内存划分为两块,每次只是用一块,当这一块用完时,将还存活的对象复制到另一块,然后对整块半区进行回收

 现在商业虚拟机大部分都采用这种算法,并将新生代划分为1块Eden和2块Survivor(一块叫from,一块叫to),Eden和Survivor的大小为8:1。

对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中

​ —JVM新生代老年代

缺点:可使用内存变小
Copying

3.3.4 分代收集算法(Generational Collection)

 思想:将java堆分为新生代和老年代,根据各个年代的特点采用最合适的收集算法

  • 新生代:大量对象回收,少量对象存活,选择复制算法
  • 老年代:对象存活率高,选择标记-清除或标记-整理算法

当前的商业虚拟机都采用分代收集

3.4 垃圾收集器

3.4.1 Serial 收集器

  • 最基本、发展最久的收集器,JDk1.3.1之前是虚拟机新生代收集的唯一选择
  • 单线程收集器
  • 工作时会发生STW(Stop The World)
  • 到目前为止仍然是虚拟机运行在Client模式下的默认新生代收集器

3.4.2 ParNew 收集器

  • 是 Serial 的多线程版本(新生代收集器)
  • 是运行在Server模式下的虚拟机中首选的新生代收集器
  • 默认开启的收集线程数与CPU数量相同,可以通过-XX:ParallelGCThreads设置线程数

3.4.3 Parallel Scavenge 收集器

  • 新生代收集器(复制算法)
  • 多线程工作
  • 其目标是达到一个可控制的吞吐量(吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值),提高吞吐量可以高效率地利用CPU时间
    • 吞吐量 = 运行用户代码时间 /(运行用户代码时间+ 垃圾收集时间)
    • 通过两个参数精确控制吞吐量
      • -XX:MaxGCPauseMillis 最大垃圾收集停顿时间
      • -XX:GCTimeRatio 直接设置吞吐量大小,参数范围(0-100)默认为99
  • 适合在后台运算而不需要太多交互的任务场景
  • -XX:UseAdaptiveSizePolicy 开关参数,打开该参数后虚拟机会根据当前系统的运行情况,动态调整其他参数以提供最适合的停顿时间或最大的吞吐量,这种调节方式被称为GC自适应的调节策略

3.4.4 Serial Old 收集器

  • Serial 的老年代版本,单线程,使用标记-整理算法
  • 适用于Client模式下的虚拟机

3.4.5 Parallel Old 收集器

  • Parallel Scavenge的老年代版本,多线程,使用标记-整理算法

3.4.6 CMS 收集器

  • CMS(Concurrent Mark Sweep)以获取最短回收停顿时间为目标的收集器

  • 使用标记-清除算法,整个过程分为四步

    • 初始标记(CMS initial mark)

      标记一下GC Root能直接关联到的对象

    • 并发标记(CMS concurrent mark)

      进行GC Root Tracing(即标记GC Root路线上所有对象)

    • 重新标记(CMS remark)

      修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录

    • 并发清除(CMS concurrent sweep)

    初始标记和重新标记仍然需要STW

  • 默认启动的回收线程数是(CPU数量+3)/ 4

  • 当CMS发生"Concurrent Mode Failure"时,老年代的垃圾收集器从CMS退化为Serial Old,所有应用线程被暂停,停顿时间变长。

关于Concurrent Mode Failure的简介

 缺点:

  • CMS无法处理浮动垃圾(伴随程序运行,出现在标记过程之后的垃圾)只能等待下次GC处理

  • 因为使用标记-清除算法,所以会产生内存碎片,当连续内存无法分配大对象时就不得不提前触发一次FullGC

    CMS提供了一个-XX:UseCMSCompactAtFullCollection开关参数(默认开启),用于在CMS顶不住要进行FullGC时开启内存碎片合并整理过程,但停顿时间不得不边长

    还有另一个参数-XX:CMSFullGCsBeforCompaction,这个参数用于设置执行多少次不压缩的FullGC后进行一次带压缩的(默认是0,即每次都进行碎片整理)

3.4.7 G1 收集器

  • Garbage-First收集器是当今收集器技术发展的最前沿成果之一
  • G1是一款面向服务端应用的垃圾收集器
  • 不需要其他收集器配合

 特点:

  • 并行与并发:充分利用多CPU的优势来缩短STW的停顿时间,部分其他收集器原本需要停顿其他线程的GC动作, G1可以通过并发的方式让其他线程继续执行
  • 分代收集
  • 空间整合:不会产生内存碎片
  • 可预测的停顿:能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒

 使用G1收集器是,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然保留了新生代老年代的概念,但是他们不再是物理隔离的,都是一部分Region(不需要连续)的集合

G1-Heap

 G1回收器执行步骤:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting and Evacuation)

3.5 内存分配策略

3.5.1 对象优先在Eden分配

 大多数情况下,对象在新生代Eden区中分配,当Eden没有足够空间进行分配时,虚拟机发起一次Minor GC

3.5.2 大对象直接进入老年代

 大对象是指,需要大量连续内存空间的Java对象,例如较长的数组或字符串。虚拟机提供了-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配

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

 虚拟机给每个对象定义了一个对象年龄(Age)计数器。对象每熬过一次Minor GC,年龄就增加1岁,当增加到一定程度时(默认15岁),就会被移至老年代,可以通过参数-XX:MaxTenuringThreshold设置这个阈值。

3.5.4 动态对象年龄判定

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

3.5.5 空间分配担保

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间

  • 如果大于,则此次Minor GC是安全的

  • 如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。

    如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值