【JVM】之 垃圾回收


内存布局图:
在这里插入图片描述


一、垃圾回收算法



(1) 标记-清除法

该算法会从每个 GC Roots出发, 依次标记引用关系的对象, 最后将没有被标记的对象清除

此算法带来的后果:

  1. 带来大量的空间碎片

若需要分配一个较大连续空间时容易触发 FGC

就好比在操作系统中对碎片空间的整理


(2) 标记-整理法(Mark-Copy)

此类算法类似计算机的磁盘整理

步骤如下: (不考虑存活对象超过S0S1)

  1. 第一次 Minior GC, Eden区被清空, Eden存活对象移至S0
  2. 第二次 Minior GC, Eden区被清空, Eden存活对象和S0存活对象移至S1, S0区清空
  3. 之后, 存活对象在S0S1中交换

EdenS0S1内存容量分配
Eden 占 80%, S0 占 10%, S1 占 10%

为什么这样区分?
为了更大利用内存容量.

扫描二维码关注公众号,回复: 8769236 查看本文章


二、垃圾回收器(Garbage Collector)


垃圾回收器(Garbage Collector)是实现垃圾回收算法并应用在JVM环境中的内存管理模块


(1) Serial回收器

Serial回收器是一个主要应用于 YGC 的垃圾回收器, 采用串行单线程的方式完成 GC 任务

“Stop The World” 简称 STW, 即垃圾回收的某个阶段会暂停整个应用程序的执行

现在写后台 Java 系统几乎不用

主要流程如图:
在这里插入图片描述


(2) ParNew回收器

ParNew回收器主要用于新生代, 采用多线程机制

理论上4核CPU, 支持4个垃圾回收线程并行执行

执行Minor GC, 也采用 STW, 会把系统程序的工作线程全部停掉, 禁止程序继续运行创建新的对象, 采用多个垃圾回收线程去进行垃圾回收

-XX:+UseParNewGC: 表示JVM启动之后对新生代进行垃圾回收

-XX:ParallelGCThreads: 调节ParNew的垃圾回收线程数量

一般采用默认, 例如: 4核CPU 8核CPU 16核CPU, 对应ParNew线程数为 4、8、16


(3) CMS回收器(Concurrent Mark Sweep Collector)

CMS回收器 是回收停顿时间比较短、用于老年代的垃圾回收器, 采用多线程机制, 性能较好

四个步骤完成垃圾回收:

  1. 初始化标记(Initial Mark)
  2. 并发标记(Concurrent Mark)
  3. 重新标记(Remark)
  4. 并发清除(Concurrent Sweep)

1、3 步的初始化标记和重新标记阶段依然会引发 STW,
而 2、4步的并发标记和并发清除两个阶段可以和应用程序并发执行, 也是比较耗时的操作, 但并不影响应用程序的正常执行

CMS 采用 “标记 - 清除算法”, 因此产生大量的空间碎片

-XX:+UseCMSCompactAtFullCollection 强制 JVMFGC完成后对老年代进行压缩, 执行一次空间碎片整理, 但是空间碎片整理阶段也会引发 STW
-XX:+CMSFullGCsBeforeCompaction=n: 在执行了 n 次 FGC后, JVM再在老年代执行空间碎片整理, 减少 STW 次数.


(4) G1回收器(Garbage-First Garbage Collector)

目的:为了减少Stop the World

Hotspot 在 JDK7 中推出了新一代 G1, 通过-XX:+UseG1GC 开启

优点:

  1. 和 CMS 相比, G1 具备压缩功能, 能避免碎片问题
  2. G1 的暂停时间(STW)更加可控
  3. 统一收集新生代和老年代, 采用了更加优秀的算法和设计机制
  4. 可预测的停顿时间,能够尽可能地在指定时间内完成空间碎片,-XX:MaxGCPauseMills参数来设定,默认值为 200ms

如图:G1回收模型内存布局

G1Java堆空间分割成了若干相同大小的区域,即 region

  1. region包括EdenSurvivorOldHumongous四种类型
  2. Humongous 是特殊的Old类型,专门放置大型对象。
  3. 这样划分意味着不需要一个连续的内存空间管理对象

G1采用 Mark-Copy

G1执行时使用 4个 worker并发执行,在初始标记时,还是会触发STW


1. G1 是如何工作的?

G1 需要知道每个Region有多少垃圾,处理这些垃圾需要多长时间?

根据需要回收对象的大小和回收预估时间,进行选择回收Region

如图:
在这里插入图片描述


2. 对象什么时候进入新生代的Region?什么时候对象进入老年代 Region

刚开始,Region可能谁都不属于。

  1. 当有对象产生,则分配给了新生代
  2. 触发 GC
  3. 下一次,同一个Region可能又被分配给了老年代,用来存放老年代的对象

如图:
在这里插入图片描述

新生代进入老年代的条件:

  1. 对象在新生代躲过了很多此的垃圾回收,达到了一定的年龄,可通过-XX:MaxTenuringThreshold设置年龄
  2. 动态年龄判定规则,如果一旦发现某次新生代 GC 过后,存货对象超过了 Survivor的50%

3. 什么时候触发Region GC?什么时候触发老年代的Region GC

G1将内存划分为多个Region,但还是有 新生代、老年代的区分
新生代里还是有 EdenSurvivor划分
触发垃圾回收的机制也相类似


4. Humongous 大对象Region

G1提供了专门的Region来存放大对象,而不是让大对象进入老年代OldRegion

大对象的判定规则:

一个大对象超过一个Region大小的 50%,就会被放入大对象专门的Region
比如:
每个Region 2MB,只要一个大对象超过 1MB,就会被放入大对象专门的Region

如图:
在这里插入图片描述



三、问题



(1) 单线程与多线程进行垃圾回收比较

单CPU运行多线程会导致频繁的线上切换上下文, 有效率开销

即:

  1. 若在客户端(客户端程序), 采用单线程垃圾回收器
  2. 若在服务端(有多核CPU资源), 采用多线程垃圾回收器

(2) ParNew + CMSGC, 如何保证只做 YGC, JVM参数如何配置?

  1. 加大分代年龄, 比如默认 15 加到 30
  2. 修改新生代和老年代的比例, 比如新生代:老年代 = 2:1
  3. 修改 Eden区 和 S0 S1区比例, 例如 6: 2: 2

(3) 如何做到FullGC次数为0, 只做 YGC?

关键点: 让Survivor区能放下, 不能因为动态年龄判断规则直接升入老年代

观察上线系统, 每秒会新增多少对象在新生代里, 多长时间触发一次 Minior GC, 平均每次 Minor GC 之后会有多少对象存活, Survivor区是否可以放下


(4) 有哪些参数需要了解?

2核4G机器, 可提供 JVM 最大内存 2G

  1. 方法区(元空间)
  2. 老年代
  3. 新生代

(5) 为什么老年代的 FGC 要比 新生代Minior GC 慢很多, 一般10倍以上?

  1. 并发标记阶段, 老年代存活对象多, 追踪GC Roots 花费要久
  2. 并发清理阶段, 老年代不是一次性回收一大片内存, 而是零散
  3. 内存碎片整理, 把大量的存活对象给挪在一起, 空出来连续内存空间, 这个过程需要 STW

(6) 几个触发老年代 GC 的时机?

  1. 老年代可用内存小于新生代全部对象的大小, 如果没开启空间担保参数, 会直接触发FGC, 所有一般空间担保参数都会打开

  2. 老年代可用内存小于历次新生代GC后进入老年代的平均对象大小, 此时会提前FGC

  3. 新生代Minior GC后的存活对象大于Survivor, 那么就会进入老年代, 此时老年内存若不足

  4. 如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小, 但是老年代已经使用的内存空间超过了这个参数指定的比例(-XX:CMSInitiatingOccupancyFaction), 也会自动触发FGC


(7) 多大的对象直接进入老年代?

大对象可以直接进入老年代, 因为大对象很可能是要长期存活和使用的

一般设置超过 1MB 的对象为大对象


(8) JVM 参数标准格式?

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGc -XX:+UseConcSweepGC

发布了404 篇原创文章 · 获赞 270 · 访问量 42万+

猜你喜欢

转载自blog.csdn.net/fanfan4569/article/details/101205084