1. mark: 标记 sweep: 清除、清扫
  2. Mutator: 图片 Collector: 收集器
  3. compacting: 压实的,把……紧压在一起(或压实);(物质)变紧密;紧密地形成
  4. generational: 分代 Threshold: 阈值
  5. Ratio: 比率、比例
  6. Tenured Spacegenerationendensurvivorratio
  7. serial

1. 概念认识

1.1 垃圾回收解决了什么问题?

  1. 对象内存分配的问题
  2. 回收分配给对象的内存的问题

    1.2 简单的认识

    一个对象称为垃圾之后,暂时的存储在(也可以说是堆内存)内存中,当存储到一定的量之后,虚拟机会自动启动垃圾回收将这些对象释放,可以主动调用system.gc()方法,开启垃圾回收,并且当一个对象被回收之后会自动调用finalize() ```java public class Person { public void finalize() {
    1. System.out.println("我被GC回收了,finalize被调用");
    } }

public class Ex() { public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person();

  1. p1 = null;
  2. p2 = null;
  3. System.gc();
  4. for (int i = 0; i < 100000; i++) {
  5. // 为了让系统运行时间长一点
  6. }
  7. }

}

  1. <a name="nczdR"></a>
  2. # 2. 标记算法
  3. <a name="dg4Xd"></a>
  4. ## 2.1 什么是垃圾对象
  5. 1. 对象被判定为垃圾的标准,当一个对对象没有被其它对象所引用,那么就可以判断它是垃圾
  6. <a name="cc1Ih"></a>
  7. ## 2.2 引用计数算法
  8. 1. 通过判断对象的引用数量来决定对象是否可被`GC`
  9. 1. 每个对象实例都有一个引用计数器,被引用则`+1`,完成引用则`-1`
  10. 1. 任何计数器为`0`的对象实例都可以被`GC`回收
  11. 1. 优点: 执行效率高,程序受影响较小
  12. 1. 缺点: 无法检测出循环引用的情况,导致内存泄漏
  13. 1. 举例:在某个方法中定义了引用变量指向该对象实例,当方法结束的时候,由于该引用变量是局部变量,它存储在虚拟机栈中,方法结束后就自动销毁,此时该实例对象的引用计数器便会`-1`
  14. ```java
  15. // 如下所示,两个对象循环引用,则引用计数器就无法检测出来
  16. MyObject o1 = new MyObjec();
  17. MyObject o2 = new MyObjec();
  18. o1.childNode = o2;
  19. o2.childNode = o1;

2.3 可达性分析算法

2.3.1 定义

  • 通过判断对象的引用链是否可达来决定对象是否可被回收,遍历的起点是GC Root

    2.3.2 可以作为GC Root的对象

  1. 虚拟机栈中引用的对象(栈帧中的本地变量表)
  2. 方法区的常量引用对象
  3. 方法区中的类静态引用的对象
  4. 本地方法栈中的引用对象
  5. 活跃线程的引用对象

    3. 回收算法

    3.1 标记-清除算法

  6. 英文名称mark and sweep

  7. 标记:通过可达性分析算法从根对象进行扫描,对存活对象进行标记
  8. 清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存
  9. 缺点:容易碎片化,易产生大量碎片化的内存地址块,当一个较大对象需要存储空间时,会不断的触发垃圾回收,知道产生足够存储的内存地址
  10. 举例:如下图所示,目前空间最大的内存为2个单位,现在需要为一个3个单位的对象赋值,那么Mutator(指的是当前的应用程序)一直是暂停状态,Collector一直在尝试进行收集,导致当前应用程序在GC时处于停滞状态,直到系统提示内存溢出
  11. image.png

    3.2 复制算法

  12. 英文名称:copying

  13. 个人理解:镜面复制,拿空间换时间,特点一半一半,适合年轻代
  14. 算法逻辑:
    1. 将内存空间分为较大额度的对象块空闲块对象只在在对象快上建立
    2. 当系统触发垃圾回收的时候,将对象块的对象全部按照顺序复制到空闲块
    3. 时空闲块变成下一次的对象块
    4. 然后直接清除对象快中所有的对象,此时对象块变成空闲块
  15. 优点:
    1. 解决碎片化问题
    2. 顺序分配内存,简单高效
    3. 该算法适用于,声明周期较短,存活率底的对象区域,这样复制的东西较少,比如年轻代
    4. 事实上现在的商用虚拟机都是采用,复制算法回收年轻代,研究表明,年轻代,每次回收只有10%左右的对象存活
  16. image.png

    3.3 标记-整理算法

  17. 英文名称:compacting

  18. 标记: 从跟集合进行扫描,对存活对象进行标记
  19. 清除: 移动所有存活对象,且按照内存地址依次排列,然后将末端内存地址以后的内存全部收回
  20. 优点
    1. 避免内存的不连续
    2. 不用设置两块内存块互换,浪费内存
    3. 适用于存活率较高的场景,比如老年代
  21. image.png

    3.4 分代收集算法

  22. 将堆内存进行模块划分:按照声明周期不同,划分区域以采用不同的垃圾回收算法

  23. 分代收集算法是一套组合拳,不同的区域采用不同的收集算法
  24. 目的:为了提高JVM的回收效率

    4. 常见的GC

    4.1 对象内存划分

    JDK 8之前是如下图,JDK 8之后没有了永久代
    image.png

    4.2 年轻代GC

    4.2.1 基础认识

  25. 基本思想:尽可能快速的收集掉那些声明周期短的对象

  26. 年轻代的垃圾回收,也叫Minor GC
  27. 年轻代的区域划分
    1. 一个Eden Space区域,占比80%
    2. 两个Survivor区域,分别是From Space占比10%To Space占比10%
    3. 为什么Eden Space占比特别大,因为年轻代每次GC之后存活的对象特别少,基本上小于10%
  28. Minor GC触发的时间

    1. Eden Space空间空间不足的时候

      4.2.2 Minor GC的流程

  29. 视频介绍:翔仔面试课7-2的第10分钟

  30. 第一次Minor GC
    1. 这时全部的对象都在Eden Space区域,通过可达性分析算法,标记出所有的垃圾对象
    2. 通过复制算法,将不是垃圾的对象复制到From Space空间,并且复制的对象年龄+1
    3. 然后直接回收Eden Space区域的垃圾
  31. 第二次Minor GC
    1. 上一次的信息介绍:这时候Eden Space区域会产生新的对象,From Space存留上次GC对象
    2. 通过可达性分析算法,标记出Eden SpaceFrom Space所有的垃圾对象
    3. 通过复制算法,将不是垃圾的对象复制到To Space空间,并且复制的对象年龄+1
    4. 然后直接回收Eden SpaceFrom Space区域的垃圾
  32. 之后就是无限的Minor GC循环
  33. To SpaceFrom Space区域的空间不足怎么办?

    1. 查老年代中最大连续可用空间是否大于了当前新生代所有对象的总大小
    2. 如果大于,则直接执行Minor GC(这个时候执行是没有风险的)。
    3. 如果小于了,JVM会检查是否开启了空间分配担保机制,如果没有开启则直接改为执行Full GC

      4.2.3 如何晋升为老年代

  34. 多次Minor GC存活下来的对象

    1. 一次Minor GC存活下来的对象,年龄会+1
    2. 直到达到某个阈值默认是15,这些对象就会成为老年代对象
    3. 这个阈值可以通过这个参数设置-XX:MaxTenuringThreshold,但是不能超过15
  35. 年轻代的内存区域存不下的对象,例如Edne SpaceSurvuvor装不下的对象
  36. 新生成的大对象-xx:+PretenuerSizeThreshold
  37. 这个阈值为什么是15次,25次可以吗?

    1. 首先要清除对象在堆内存中的存储结构,查看笔记:并发编程 - 锁相关中4.2.5章节的描述
    2. 对象的分代年龄是存储在对象头的Mark Word
    3. 它占据了4bit位置,所以它能存储的最大数值是15

      4.2.4 常见的参数设置

  38. -XX:SurvivorRatio:用来设置Eden Space和其中一个Survivor区域的比值

  39. -XX:NowRatio:老年代和年轻代的内存比例
    1. 默认是2:1
    2. 总内存是如何设置的呢?是由前面的-Xms -Xmx -Xss控制
  40. -XX:MaxTenuringThreshold:设置新生代晋升到老年代最大Minor GC次数的阈值

    4.3 老年代GC

    4.3.1 基础认识

    老年代一般是标记整理算法、标记整理算法,由于对老年代的垃圾收集,一般会伴随着年轻代的Minor GC,所以老年代的垃圾收集叫Full/Major GC一般这两个描述是一样的,但是由于各种解释不确定Major GC也可以特指老年代的垃圾回收

    4.3.2 触发Full GC的条件

  41. 老年代空间不足

  42. 针对JDK8之前的,永久代空间不足也会触发Full GC
  43. CMS GC时出现promotion failedconcurrent mode failure,使用CMS GC的时候要注意日志中是否有这两个错误
    1. promotion failed:中文“晋升失败”,是在进行Minor GC的时候,Survivor区域放不下了,对象只能放入老年代,而老年代也放不下了
    2. concurrent mode failure:中文“并发模式失败”,是指在进行CMS GC的过程中,同时有对象要放入老年代中,而此时老年代空间不足
  44. Minor GC晋升到老年代的平均大小大于老年代的剩余空间
  45. 调用System.gc()
  46. 使用RMI来进行RPC或管理的JDK应用,每小时执行一次Full GC

    5. 垃圾搜集器

    5.1 前置概念

    5.1.1 stop the world

  47. 虚拟机由于要执行GC而停止了应用程序的执行

  48. 这个情况可能在任何一种GC算法中发生
  49. stop the world发生的时候,处理GC线程所有的线程都是处于阻塞状态
  50. 多数情况下的GC优化,都是值减少stop the world发生的时候来提高程序的性能

    5.1.2 safe point

  51. 举例说明:GC就像保洁打扫卫生,不允许出现,边打扫,边扔垃圾的情况,所以保洁打扫的时候就和所有人说,我开始打扫了,不要扔垃圾,这个时间点就是安全点

  52. GC的可达性分析算法中,要保证分析程序不会产生新的垃圾,所以所有的线程在这个点都被阻塞了
  53. 安全点产生的地方:方法调用、循环跳转、异常跳转等地方
  54. 安全点数量得适中

    5.1.3 虚拟机的运行模式

  55. server模式:采用重量级的虚拟机,启动慢,但是程序平稳运行之后速度快

  56. client模式:采用轻量级的虚拟机,启动快,但是程序平稳运行之后速度慢
  57. java -version即可查看当前jvm采用的模式

image.png

5.2 年轻代常见垃圾收集器

5.2.1 serial

  1. 中文意思:顺序排列的,串行的、单行的
  2. 启动设置:-XX:+UseSerialGC
  3. 采用的算法:复制算法
  4. 该收集器是虚拟机历史最悠久的年轻代垃圾搜集器,在JDK3之前是年轻代的唯一选择
  5. 单线程搜集,进行垃圾搜集时,必须阻塞所有工作线程
  6. 简单高效,它依然是client模式下的默认年轻代搜集器
  7. image.png

    5.2.2 ParNew

  8. 启动设置:-XX:UseParNewGC

  9. 使用算法:复制算法
  10. 它是多线程搜集,其它的行为和serial是一样的
  11. 它是server模式下的虚拟机首选的年轻代搜集器
  12. 单核执行效率不如serial,多核情况下才会突出优势,因为存在可用线程的切换开销,但是随着cpu数量的增多,多核情况下会抹除这些不足
  13. 默认开启的线程数是和cpu的数量相同,可以使用ParNewGCThread 限制垃圾搜集的线程数
  14. 可以和CMS搜集器配合工作
  15. image.png

    5.2.3 parallel scavenge

    吞吐量:运行用户代码的时间/(运行用户代码的时间+垃圾回收的时间)

  16. 中文意思:并行清除

  17. 启动设置:-XX:UseParallelGC
  18. 使用算法:复制算法
  19. 使用多线程垃圾回收
  20. 相对前两面两个搜集器,减少用户线程停顿时间,本收集器更关注系统的吞吐量
    1. 减少停顿时间:适用于和用户交互的程序,提高用户体验
    2. 高吞吐量:则会高效利用cpu时间,尽可能快的完成运算任务,主要适合在后台运算的程序
  21. 该搜集器是Server模式下的默认年轻代垃圾收集器,注意ParNew的描述是首选搜集器

    5.3 老年代常见的垃圾搜集器

    5.3.1 serial old

  22. 启动设置:-XX:UseSerialOldGC

  23. 使用算法:标记-整理算法
  24. 单线程搜集,进行垃圾搜集时,必须阻塞所有工作线程
  25. 简单高效,它依然是client模式下的默认老年代搜集器
  26. image.png

    5.3.2 parallel old

  27. 启动设置:-XX:UseParallelOldGC

  28. 使用算法:标记-整理算法
  29. 多线程,吞吐量优先
  30. 它出现的意义
    1. 它是JDK6才提供的垃圾搜集器
    2. 在此之前,年轻代的parallel scavenge处于比较尴尬的地位
    3. 原因是,如果年轻代选择parallel scavenge,老年代只能选择serial old
    4. 由于serial old是单线程的,它会拖累parallel scavenge
    5. 导致这种组合也未必能在整体上获取到最大吞吐量的效果,一定情况下还不如ParNew + cms性能高
  31. image.png

    5.3.3 cms

  32. 启动配置:-XX:UseConcMarkSweepGC

  33. 使用算法:标记-清除算法
  34. 简单认知
    1. 它是JDK5中提供的,一出生它几乎占据了老年代垃圾回收的半壁江山
    2. 它划时代的意义,就是垃圾回收线程几乎可以和用户线程同时进行工作
    3. 几乎就是指它还是不能做到完全不需要stop the world,只是它尽可能的缩短了停顿时间
    4. 如果你的应用程序对停顿比较敏感,并且在程序运行的时候提供流弊的内存,使用cms是更好的选择
    5. 如果在JVM中有相对较多存活时间较长的对象会更适合cms
  35. 执行流程

    1. 初始化标记:标记处可以被回收的对象,stop the world
    2. 并发标记:并发追溯标记,这个时候应用程序线程和并发标记程序线程,并发执行
    3. 并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象
    4. 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象
    5. 并发清理:清理垃圾对象,程序不会停顿
    6. 并发重置:重置CMS收集器的数据结构

      5.4 Garbage First

  36. 启动配置:-XX:UseG1GC

  37. 使用算法:复制算法 + 标记-整理算法

image.png