JVM垃圾收集器与内存分配策略

垃圾收集器与内存分配策略

对象存活判断

引用计数算法

  • 给对象添加一个计数器,每有一个引用+1,当引用失效-1,若为0则不在被使用.

可达性分析算法

  • 对象是否可到达GC roots
    或者说GC roots 是否是对象的上层节点(祖父节点,父节点)

    • GC roots

      • 虚拟机栈(栈中的本地变量表)中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中引用的对象

引用

  • 如果reference类型的数据中存储的数值代表另一块的起始地址就称这块内存代表着一个引用(白话:栈中的reference保存的是一个内存起始地址)

    • 强引用

      • 永远不会被回收,最常用的
    • 软引用

      • SoftReference 在将要发生内存溢出前被标记,下次GC时回收
    • 弱引用

      • Weakreference每次GC都回收
    • 虚引用

      • PhantomReference 无法通过虚引用得到一个对象的实例,唯一目的,当回收时收到一个系统通知

生存与死亡

  • 可达性分析--不可达-->标记---->筛选--1.是否覆盖了finalize();方法 2.finalize()是否被调用过--->(1.未覆盖 2.非首次调用) 没必要执行

常见的垃圾收集算法

标记-清除算法

  • 概念: 标记出需要回收的对象,统一回收标记对象

  • 缺点

    • 1.标记与清除效率都不高
    • 2.清除后会有大量不连续的空间碎片,当分配内存需要的连续空间不足又会导致提前GC

复制算法

  • 概念:将内存分为两块(即为A,B),先在A中存储,当A满了将回收,回收后将保留对象复制到B,清空A,后续在B中存储新的对象,B满了.往复 (S0,S1的实现)

  • 缺点

    • 1.将内存一分为2,假设10M内存则无法保存5M以上 10M以下的对象
    • 2.存活率高的对象会导致来回复制,消耗性能,效率变低

标记-整理算法

  • 概念:标记需要回收的对象,非标记对象像一端移动,清理端点边界值后的内存

分代收集算法

  • 概念:根据对象存活周期的不同将内存划分为不同的块.(新生代,老年代),在根据每个块的特性选择最合适的收集算法

  • 新生代

    • 对象存活率低,需要复制的存活对象少

      • 复制算法
  • 老年代

    • 对象存活率高,复制开销大

      • 标记-清理(整理)算法

HotSpot的算法实现

为了保持一致性,GC进行是必须停顿所有java执行线程,即 Stop The World

枚举根节点

  • 在HotSpot的实现中,当类加载完成是,HotSpot将对象内偏移量上具体的数据计算出来,在特定的位置记录下栈和寄存器中哪些位置是引用,使用一组称为OopMap的数据结构,GC在扫描时就可直接扫描这些信息,而不需要全量扫描

安全点 safePoint

  • 程序执行并非任何地方都可停顿,只有当达到安全点时才能暂停.(Stop The World)

  • 中断方案

    • 抢先中断

      • GC执行时,将所有线程中断,若发现不在安全点中断的线程就恢复这条线程,让其运行至安全点. 几乎已没有虚拟机采用这种方式
    • 主动中断

      • 不直接操作线程,而是对线程做标志,每个线程主动去轮询这个标志,当发现这个中断标志时自己中断挂起.

安全区域 safe Region

  • 在一段代码片段中,引用关系不会发生变化,这个区域中任意地方开始GC都是安全的

  • 线程--执行safe Region中代码--> 标记自己已进入safeRegion---离开SafeRegion-->检查系统是否完成跟节点枚举(或整个GC过程)

    • 完成了线程继续进行
    • 未完成,等待,直到收到可以离开SafeRegion的信号为止

垃圾收集器

Serial

  • JDK1.3.1以前,年轻代唯一的选择
  • 单线程收集器,必须暂停其它所有工作线程,直到它收集结束
  • Client模式下默认的新生代收集器
  • 优点:简单高效,没有线程交互开销

ParNew

  • 多线程版本的Serial
  • Server模式下新生代首选收集器
  • 优点:除了Serial以外,只有parNew可以与CMS配合工作
  • -XX:+UseConcMarkSweepGC 默认新生代收集器
    -XX:+UserParNewGC 强制指定

Parallel Scavenge

  • 使用复制算法,并行的多线程收集器

  • 更关注于吞吐量,其它收集器关注缩短停顿时间(Stop The World)

    • 吞吐量=运行用户代码时长/(运行用户代码时长+垃圾收集时长)
  • -XX:MaxGCPauseMillis 设置最大停顿时间(毫秒)
    -XX:GCTimeRatio 设置吞吐量大小(0-100)

  • -XX:UseAdaptiveSizePolicy
    配合最大停顿时间,或吞吐量来使用.仅需要设置-Xmx

    • GC自适应调节策略(与其它收集器的重要区别)

      • 开启这个参数将不在需要手工指定
        -Xmn,
        -XX:SurvivorRatio,
        -XX:PretenureSizeThreshold等细节参数,虚拟机会根据当前系统运行情况进行调节

Serial Old

  • 单线程,老年代serial收集器,采用标记-整理算法

    • JDK1.5及以前与parallel Scavenge配合使用
    • 作为CMS备选方案,当CMS发生 Concurrent Mode Failure时使用

Parallel Old

  • JDK1.6开始提供
  • Parallel Scavenge老年代版本,多线程,采用标记-整理算法
  • 注重吞吐量及CPU资源敏感时使用

CMS

  • HotSpot第一款并发收集器,以获取最短回收停顿时间为目标的收集器

  • 采用标记-清除算法

  • 运作流程

    • 1.初始标记

      • 仅标记GC roots能够直接关联到的对象,速度快(Stop the World) 单线程
    • 2.并发标记

      • 进行GC roots Tracing
    • 3.重新标记

      • 修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(Stop the World) 比初始标记时间长,比并发标记时间短 多线程
    • 4.并发清除

  • 缺点

    • 对CPU资源非常敏感.默认启动回收线程数(CPU数量+3)/4,4个以上的CPU则会占用25%以上资源.占用资源过多

    • 无法处理浮动垃圾,可能因Concurrent Mode Failure而导致一次Full GC的触发

      • 浮动垃圾:并发时又产生的新垃圾
    • 并发处理,则垃圾回收时需要预留内存供用户线程使用,JDK1.5默认68%即会触发GC,JDK1.6默认值为92%,
      -XX:CMSInitiatingOccupancyFraction
      来设置阀值

    • 标记-清除算法

      • 提供了一个参数:
        -XX:+UseCMSCompactAtFullCollection
        默认开启,FullGC后进行内存整理
        -XX:CMSFullGCsBeforeCompaction
        默认0即每次 设置多少次fullGC后整理一次内存

G1

  • JDK7u4正式商用

  • G1新生代,老年代的区分与其它收集器模式不同,它将内存划分成更多的Region,虽保留新生代,老年代概念,但已不在是物理隔离了,它们现在都是由Region(无需连续)组成的集合

  • 特点

    • 并行,并发

    • 分代收集

    • 空间整理

      • 采用了标记-整理算法
    • 可预测的停顿

      • G1将跟踪每一个Region中垃圾堆积的价值大小.在后台维护一个优先列表,每次根据允许的手机时间,优先回收价值最大的Region,以此来保证可预测的停顿时间,并达到最高的收集效率
  • 在G1收集器中,有一个关键的概念,Remembered Set,每一个Region都一个对应的Remembered Set,每当程序对Reference类型数据进行了写操作时,虚拟机会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region中,如果是,则会通过CardTable将相关引用信息记录到被引用对象所属的Region中的Remembered Set中.
    当进行内存回收时,GC跟节点枚举范围中加入Remembered Set 以保证不全堆扫描也不会遗漏

  • 运作流程

    • 初始标记

      • 与CMS相同
    • 并发标记

      • 对堆中对象进行可达性分析,找出可达对象
    • 最终标记

      • 修正在并发标记期间用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,虚拟机将这些对象变化记录在Remembered Set Logs中,并将Remembered Set Logs数据合并到Remembered Set中,停顿执行,但是可以并行执行
    • 筛选回收

      • 对每个Region的回收价值和成本进行排序,根据用户指定的停顿时间来制定回收计划,进行回收

内存分配及回收策略

进入Eden区,若不足触发minor GC

大对象直接进入老年代

  • 大对象:一般指大于Survivor一半以上

长期存活的对象进入老年代(即对象年龄达到阀值默认15)

当一组对象年龄相同的对象大于Survivor区一半时,年龄大于或等于这个年龄的对象都将直接进入老年代

空间分摊担保

垃圾收集器常用参数

UseSerialGC

  • 使用Serial+Serial Old 收集器

UseParNewGC

UseConcMarkSweepGC

  • 使用parNew + CMS + Serial Old

UseParallelGC

UseParallelOldGC

SurvivorRatio

  • 8:1

PretenureSizeThreshold

  • 设置直接进入老年代对象的大小阀值

MaxTenuringThreshold

  • 设置进入老年代的对象年龄.默认15

UseAdaptiveSizePolicy

  • 由虚拟机动态调整java堆大小分配和进入老年代的对象年龄

HandlePromotionFailure

  • 是否允许分配担保失败

ParallelGCThreads

  • 设置并行GC时进行内存回收的线程数

GCTimeRatio

  • 设置GC时间占比,默认99即允许GC占用1%时间
    仅Parallel Scavenge 有效

MaxGCPauseMillis

  • 最大停顿时间
    仅Parallel Scavenge 有效

CMSInitiatingOccupancyFraction

  • 设置GMS在老年代被占用X时启动,默认68(JDK1.5) 92(JDK1.6)

UseCMSCompactAtFullCollection

  • CMS收集后是否整理

CMSFullGCsBeforeCompaction

JVM垃圾收集器与内存分配策略

以上内容总结于《深入理解JAVA虚拟机》第二版 周志明 著

高清思维导图版请关注公众号‘伊人网络’ 回复 ‘收集器’ 即可领取

JVM垃圾收集器与内存分配策略

【版权声明】

本文版权归作者和博客园共有,欢迎转载,未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。

相关推荐