前言
- 要会阅读GC log,来理解各种垃圾集器。
- 理解内存区域的作用以及调整JVM 内存大小。
- 要会操作创建对象时使用哪个区域。
使用VM参数,阅读GC log
代码及VM参数,触发Minor GC
- VM参数
1 | java复制代码-Xms20M // 堆的最大值 |
- 回顾一下内存划分
- 代码
1 | java复制代码public class GCLogTest { |
- 解释日志含义
前面明明设置了年轻代为 10M 为什么日志却是9216k?
- 年轻代有一个Eden区和两个Serviver,即s0和s1。
- 前面设置Eden区占8 ,那么s0和s1各占1。
- 在使用时只会用s0和s1中的其中一个。另外一个一定是不使用的。所以就少了1024k。
- 即9216+1024 = 10240k = 10M,就是堆的设置大小和实际大小会差1024k。被其中一个Serviver区浪费了。
观察到老年代使用了4104k,如何得来?
- 可以通过GC前后的内存变化来计算。
- 5976 - 840 = 5136k,表示年轻代释放了5136k容量。
- 年轻代释放的内容可能去了老年代或者已经被抹除了。
- 5976 - 4944 = 1032k,表示整个堆释放的空间为1032k。
- 5136 - 1032 = 4104k,表示年轻代释放的内容中抹除了1032k,剩下的4104k去了老年代。
代码,触发Full GC
- 代码
1 | java复制代码public class GCLogTest { |
- 解释一下Full GC
创建对象比上面触发时Full GC 容量大,却没有触发Full GC
上面的代码是11M byte[],触发了Full GC
- 创建13M byte[]
1 | java复制代码public class GCLogTest { |
- 明明创建的内容比较大,为什么没有触发Full GC
- 对象无法在新生代创建时,就直接在老年代创建。
- 不要想着将对象拆分了,一个部分在新生代,另一部分在老年代,这是不可能的。
- 对象无法在新生代创建时,就直接在老年代创建。
- 如下的情况也是会直接在老年代创建
1 | Java复制代码public class GCLogTest { |
image.png
1 | Java复制代码public class GCLogTest { |
阈值
在终端使用java -XX:+PrintCommandLineFlags,查看JVM启动参数。
简单聊一聊UseCompressedOops 和 UseCompressedClassPointers这两个JVM参数。
通过一个VM 参数设置会在老年代创建对象的阈值
- 就是设置一个阈值,当创建的对象大于这个阈值时,不管新生代的空间够不够都去老年代创建。
- 以下为本次VM参数
1 | java复制代码-Xms20M |
- 代码,创建的字节数组为5M
1 | java复制代码public class ThresholdTest { |
要想阈值起作用,垃圾收集器得是单线程的
- 在原来的VM 参基础上启动单线程垃圾收集器
1 | java复制代码-XX:+UseSerialGC |
设置对象在Servivor区年龄的阈值
- Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。
- 新生代几乎是所有 Java 对象出生的地方,新生代是 GC 收集垃圾的频繁区域。
- 当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些 仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1。
- 当年龄到达一定值时对象如果还在Servivor区复制来复制去的,就会被移到老年代。
- -XX:MaxTenuringThreshold:表示当年龄到达设置的最大值时会被移到老年代。
- 该参数的默认值为15,CMS中默认值为6,G1中默认为15(在Jvw中,该数值是由4个bit来表示的,所以最大值 1111,即15)。
- 是设置的最大值,对象有可能在年龄到达最大值时被移到老年代,但是年龄到达设置的最大阈值时一定会被移到老年代。
- 使用 -XX:TargetSurvivorRatio=value JVM 会根据情况动态设置它的大小,但不会超过手动给它设置的值。
- 经历了多次GC后,存活的对象会在From Survivor与To Survivor之间来回存放,而这里面的一个前提则是这两个空间有足够的大小来存放这些数据,
- 在GC算法中,会计算每个对象年龄的大小,如果达到某个年龄后发现总大小已经大于了Survivor(其中一个)空间的50%,那么这时就需要调整阔值,不能再继续等到默认的15次GC后才完成普升(移到老年代),因为这样会导致Survivor空间不足,所以需要调整阈值,让这些存活对象尽快完成晋升。
演示一下,对象从年轻代以到老年代的过程
- 以下为本次代码 VM 参数
1 | Java复制代码-Xmx200M // 堆大小 200M |
本次并没有指定Serivivor占多少
- 代码
1 | java复制代码public class TenuringTest { |
(max 3) 表示动态判断确定的阈值最大值为3,由-XX:MaxTenuringThreshold=3决定。
Desired survivor size 3145728 bytes 怎么来的?
- 因为没有指定Servivor的比例 所以默认8:1:1,-Xmn50M指定新生代大小为50M ,所以 40:5:5。
- 又 -XX:TargetSurvivorRatio=60,表示其中一个Serivior区超过的60%重新判断 阈值(threshold), 即 5 * 60% = 3M = 3145728 bytes,
- 也说在其中一个Serivior区对象大小到3145728 bytes 时,重新判断阈值。
小结
1 | java复制代码-Xms20M // 堆的最大值 |
本文转载自: 掘金