您当前的位置: 首页 >  Java

止步前行

暂无认证

  • 7浏览

    0关注

    247博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

深入理解Java虚拟机(二)——垃圾回收器

止步前行 发布时间:2021-12-29 20:00:00 ,浏览量:7

文章目录
  • 一、垃圾回收
    • 1. JVM中会在以下情况触发垃圾回收
    • 2. 如何确定对象为垃圾对象?
    • 3. 垃圾回收区域
    • 4. 垃圾收集算法
      • 1. 引用计数法
      • 2. 复制算法
      • 3. 标记-清除算法
      • 4. 标记-压缩算法
      • 5. 三种算法对比
    • 5. 分代收集
      • 1. Serial GC
      • 2. ParNew GC
      • 3. Parrallel Scavenge GC
      • 4. Parallel Old GC
      • 5. Serial Old GC
      • 6. CMS GC
      • 7. G1 GC
      • 8. 垃圾回收器总结
  • 二、GC日志
    • 1. 开启GC日志
    • 2. GC日志分析
      • 1. verbose:gc
      • 2. -XX:PrintGCDetails
      • 3. 日志补充说明
      • 4. Minor GC日志
      • 5. Full GC 日志

一、垃圾回收 1. JVM中会在以下情况触发垃圾回收
  • 对象没有被引用

  • 作用域发生未捕捉异常

  • 程序正常执行完毕

  • 程序执行了System.exit()

  • 程序发生意外终止

2. 如何确定对象为垃圾对象?
  • 引用计数法:此方法无法解决两个对象之间循环引用问题

  • 可达性分析:图的遍历,基本思路:

    • 可达性分析算法是以根对象集合GCRoots为起始点,按照从上至下方式搜索被根对象集合所连接的目标对象是否可达
    • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链Reference Chain
    • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象
    • 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象

    哪些元素可以作为GCRoots

    • 虚拟机栈中引用的对象

      比如:各个线程被调用的方法中使用到的参数、局部变量等

    • 本地方法栈内JNI(通常说的本地方法)引用的对象

    • 方法区中类静态属性引用的对象

      比如:字符串常量池(StringTable)里的引用

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

    • Java虚拟机内部的引用

      基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerExceptionOutofMemoryError),系统类加载器

3. 垃圾回收区域

在这里插入图片描述

垃圾回收器可以对年轻代回收,也可以对老年代回收,甚至是全堆和方法区的回收。其中,Java堆是垃圾收集器的工作重点。从次数上讲:频繁收集Young区,较少收集Old区,基本不动Perm

4. 垃圾收集算法 1. 引用计数法

对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。

对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1,只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收

  • 优点:实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟性
  • 缺点:
    • 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销
    • 每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销
    • 引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷导致在Java的垃圾回收器中没有使用这类算法
2. 复制算法

复制算法:该算法是从根集合扫描,并将存活的对象复制到新的空间,这种算法在存活对象少时比较高效。

  • 优点
    • 没有标记和清除过程,实现简单,运行高效
    • 复制过去以后保证空间的连续性,不会出现碎片问题
  • 缺点
    • 此算法的缺点也是很明显的,就是需要两倍的内存空间
    • 对于G1这种分拆成为大量regionGC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小

如果系统中的垃圾对象很多,复制算法不会很理想,因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行。

在这里插入图片描述

3. 标记-清除算法

标记 - 清除算法(Mark-Sweep):该算法是从根集合扫描整个空间,标记存活的对象,然后在扫描整个空间对没有被标记的对象进行回收。这种算法在存活对象较多时比较高效,但会产生内存碎片,缺点:

  • 效率不算高
  • 在进行GC的时候,需要停止整个应用程序,导致用户体验差
  • 这种方式清理出来的空闲内存是不连续的,产生内存碎片,需要维护一个空闲列表

在这里插入图片描述

4. 标记-压缩算法

标记 - 压缩算法(Mark-Compress):标记整理算法和标记清除算法一样都会扫描并标记存活对象,在回收未标记对象的同时会整理被标记的对象,解决了内存碎片的问题

  • 执行过程
    • 第一阶段和标记清除算法一样从根节点开始标记所有被引用对象
    • 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放
    • 之后,清理边界外所有的空间

在这里插入图片描述

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep- Compact)算法

二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策

5. 三种算法对比 标记-清除标记-压缩复制算法速度中等最慢最快空间开销少(但会堆积碎片)少(不堆积碎片)通常需要活对象的2倍大小(不堆积碎片)移动对象否是是
  • 效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存
  • 而为了尽量兼顾上面提到的三个指标,标记-整理算法相对来说更平滑一些,但是效率上不尽如人意,它比复制算法多了一个标记的阶段,比标记-清除多了一个整理内存的阶段。
5. 分代收集

JVM中,不同的内存区域作用和性质不一样,使用的垃圾回收算法也不一样,所以JVM中又定义了几种不同的垃圾回收器,图中连线代表两个回收器可以同时使用:

在这里插入图片描述

新生代收集器:SerialParNewParallel Scavenge

老年代收集器:Serial OldParallel OldCMS

整堆收集器:G1

上图说明:

  • 两个收集器有连线,表明它们可以搭配使用: Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、ParallelScavenge/Serial Old、Parallel Scavenge/Parallel Old、G1
    • (红色虚线)由于维护和兼容性测试的成本,在JDK8时将Serial+CMS ParNew+Serial Old这两个组合声明为废弃,并在JDK9中完全取消了这些组合的支持,即移除。
  • (绿色虚线)JDK14中弃用ParallelScavenge和Serial0ldGC组合
    • (青色虚线)JDKI14中删除CMS垃圾回收器
1. Serial GC

Serial GC。从名字上看,串行GC意味着是一种单线程的,所以它要求收集的时候所有的线程暂停(Stop The World)。这对于高性能的应用是不合理的,所以串行GC一般用于Client模式的JVM中。(过时) 在这里插入图片描述

2. ParNew GC

ParNew GC。并行回收器。是在SerialGC的基础上,增加了多线程机制。但是如果机器是单CPU的,这种收集器是比SerialGC效率低的。

Par是Parallel的缩写,New表示只能处理新生代

ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别

ParNew收集器在年轻代中同样也是采用复制算法、Stop-the-World机制

ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器

在这里插入图片描述

对于新生代,回收次数频繁,使用并行方式高效

对于老年代,回收次数少,使用串行方式节省资源。(CPU并行需要切换线程,串行可以省去切换线程的资源)

3. Parrallel Scavenge GC

Parrallel Scavenge GC。这种收集器又叫吞吐量优先收集器,而吞吐量 = 程序运行时间/(JVM执行回收的时间+程序运行时间)。假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%Parallel Scavenge GC由于可以提供比较不错的吞吐量,所以被作为了server模式JVM的默认配置。

4. Parallel Old GC

ParallelOld是老生代并行收集器的一种,使用了标记整理算法,是JDK1.6中引进的,在之前老生代只能使用串行回收收集器。

5. Serial Old GC

Serial Old是老生代client模式下的默认收集器,单线程执行,同时也作为CMS收集器失败后的备用收集器。(过时)

6. CMS GC

CMS(Concurrent-Mark-Sweep)又称响应时间优先回收器,使用标记清除算法,并且也会stop-the-world。它的回收线程数为(CPU核心数+3)/4,所以当CPU核心数为2时比较高效些。CMS分为4个过程:初始标记、并发标记、重新标记、并发清除。

在这里插入图片描述

在JDK14中,删除了CMS垃圾回收器

7. G1 GC

GarbageFirst(G1)。比较特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation。它是在JDK6的某个版本中才引入的,性能比较高,同时注意了吞吐量和响应时间。

对于垃圾收集器的组合使用可以通过下表中的参数指定:

指定方式新生代GC方式老年代GC方式-XX:+UseSerialGC串行GC串行GC-XX:+UseParallelGC并行GC并行GC-XX:+UseConcMarkSweepGC并行GC并发GC-XX:+UseParNewGC并行GC串行GC-XX:+UseParallelOldGC并行GC并行GC-XX:+UseConcMarkSweepGC-XX:+UseParNewGC串行GC并发GC

默认的GC种类可以通过jvm.cfg或者通过jmap dumpheap来查看,一般我们通过jstat -gcutil [pid] 1000可以查看每秒gc的大体情况,或者可以在启动参数中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log来记录GC日志。

8. 垃圾回收器总结 垃圾收集器分类作用位置使用算法特点Serial串行运行新生代复制算法响应速度优先ParNew并行运行新生代复制算法响应速度优先Parallel并行运行新生代复制算法吞吐量优先Serial Old串行运行老年代标记-压缩算法响应速度优先Parallel Old并行运行老年代标记-压缩算法吞吐量优先CMS并发运行老年代标记-清除算法响应速度优先G1并发、并行运行新生代、老年代标记-压缩算法复制算法响应速度优先 二、GC日志 1. 开启GC日志
  • -XX:+PrintGC

输出GC日志。类似:-verbose:gc

  • -XX:+PrintgcDetails

输出GC的详细日志

  • -xX:+PrintGCTimeStamps

输出GC的时间戳,以基准时间的形式

  • -xx:+PrintGCDatestamps

输出GC的时间,以日期的形式,如2021-12-26T21:53:59234+0800

  • -XX:+PrintHeapAtGo

在进行GC的前后打印出堆的信息

  • -Xloggc:./logs/gc.log

日志文件的输出路径

2. GC日志分析 1. verbose:gc
  • 打开GC日志:-verbose:gc

  • 查看GC内容: 在这里插入图片描述

  • GC内容解析

    • GCFull GCGC的类型,GC只在新生代上进行,Full GC包括永生代, 新生代, 老年代

    • Allocation FailureGC发生的原因

    • 80832K->19298K:堆在GC前的大小和GC后的大小。228840k: 现在堆的总大小

    • 0.0084018 secsGC持续的时间

2. -XX:PrintGCDetails
  • 打开GC日志:-verbose:gc -XX:PrintGCDetails
  • 查看GC内容:

在这里插入图片描述

  • GC内容解析
    • GC,Full FC:同样是GC的类型
    • Allocation FailureGC原因
    • PSYoungGen:使用了Parallel Scavenge并行垃圾收集器的新生代GC前后大小的变化
    • ParoldGen:使用ParallelOld并行垃圾收集器的老年代GC前后大小的变化
    • Metaspace:元空间GC前后大小的变化,JDK1.8中引入了元空间以替代永久代
    • xxx secs:指GC花费的时间
    • Times:user:指的是垃圾收集器花费的所有CPU时间,sys花费在等待系统调用或系统事件的时间, real表示GC从开始到结束的时间,包括其他进程占用时间片的实际时间
3. 日志补充说明
  • [GC[Full GC说明了这次垃圾收集的停顿类型,如果有Full则说明GC发生了Stop The World
  • 使用Serial收集器在新生代的名字是DefaultNewGeneration,因此显示的是[DefNew
  • 使用ParNew收集器在新生代的名字会变成[ParNew,意思是Parallel New Generation
  • 使用ParallelScavenge收集器在新生代的名字是[PSYoungGen
  • 老年代的收集和新生代道理一样,名字也是收集器决定的
  • 使用G1收集器的话,会显示为garbage-first heap
  • Allocation Failure表明本次引起GC的原因是因为在年轻代中没有足够的空间能存储新的数据了
  • [PSYoungGen:5986K->696K(8704K)]5986K->704K(9216K)中括号内:GC回收前年轻代大小,回收后大小,(年轻代总大小) 括号外:GC回收前年轻代和老年代大小,回收后大小,(年转代和老年代总大小)
  • user代表用户态回收耗时,sys内核态回收耗时,real实际耗时。由于多核的原因,时间总和可能会超过real时间
4. Minor GC日志

在这里插入图片描述

5. Full GC 日志

在这里插入图片描述

关注
打赏
1657848381
查看更多评论
立即登录/注册

微信扫码登录

0.0473s