Java GC

Posted by danner on September 2, 2019

JVM 内存模型如上所示,分为年轻代 (Young) 和老年代 (Old),年轻代又分为Eden 和 Survivior( s0+s1 )

刚 new 出来的对象是存放在 Eden 区,

第一次 GC:扫描 Eden ,把活的对象 移动到 s0 区

第二次 GC:扫描 Eden + s0,把活的对象移动到 s1 区

第三次 GC:扫描 Eden + s1,把活的对象移动到 s0 区

第四次 GC:扫描 Eden + s0,把活的对象移动到 s1 区

s0 、s1 之间可以相当倒腾,每个对象在 JVM 有个 age 属性,倒腾一次则 age + 1。当age 超过阈值(jdk8 默认15)则把对象移动到老年代,这个过程称为 Promotion。以上说的 GC 都还只是发生在年轻代称为 Young GC,不会打断APP 的正常运行。若老年代发生 GC( full GC),则会发生 Stop The World 停止应用(并不绝对,根据GC 算法略微不同) 专注GC 。

总所周知,GC 是回收 JVM 中无用的对象,清理内存空间。那无用的对象是依据是什么?

1、引用计数:无法解决循环引用的问题 (已不使用)

2、枚举根节点可达性:根节点向向下搜寻,没有连接的对象回收 (目前使用)

那为何根节点?

  • 虚拟机栈中的引用对象
  • 本地方法栈中 JNI 的引用对象
  • 类静态属性引用的对象
  • 常量引用的对象

若以上代码在包含在 JAVA 方法 (变量是放在栈里) 中,那么 new Person() 创建的对象的根节点,该对象中包含的其他对象都是有用的,不会被回收。

知道那些对象该被回收,垃圾回收的算法有那些:

标记 - 清除:根据垃圾回收策略,标记有用的对象,对没有标记的对象直接清除即可

缺点:产生很多碎片

标记 - 复制:分为两个区域:活动区、休闲区,将有标记的对象移动到休闲区;

不会产生碎片但内存会有浪费,常用于 Young GC

新生代的对象 98% 都是创建后只使用一小会,就可以被回收。HotSpot 虚拟机默认 Eden:From Survivor:To Survivor = 8:1:1,相当于只有 10% 的空间被浪费。

标记 - 整理:在标记的基础上, 让有用的对象都向一端移动,然后直接清理掉端边界以外的内存

常用于 full GCfull GC 的算法实现有两种:

  • ParallelOldGC:停止应用(Stop The World),多线程并行处理 GC;适用于吞吐率优先的应用
  • CMS:将整个 GC 拆分为众多 小 GC,尽可能不去停止应用;适用于响应时间优先的应用
    • 只回收老年代,需搭配年轻代收集器
    • 耗 cpu
    • 并发收集、低停顿
  • G1面向服务端应用,将内存分成多个等大小的 region,每个region 都包含 Eden、Survivor、Old
    • 同时回收老年代和年轻代
    • 可预测停顿(用户配置)

参数设置

  • -Xmx:默认物理内存的 1/64
  • -Xms:默认物理内存的 1/4

默认空余堆内存小于 40%, JVM 就会增大到 -Xmx 的最大限制;空余堆内存大于 70%,JVM 就会减少直到 -Xms的最小限制。内存的扩大和缩小是会消耗性能,在大数据环境下一般设置 -Xmx = -Xms

  • -Xmn:设置年轻代内存大小
  • -XX:SurvivorRatio:设置Eden 与Survivor 占比;假如值为8,那么 8:1:1

参考:

java jvm 参数 -Xms -Xmx -Xmn -Xss 调优