工作以后, 发觉真的几乎没有像大学那样空闲的时间, 坐下来看看书写写博客了. 最近的一篇博客距离现在已经近一个多月了, 最近也在复习 Java 的东西, 准备校招, 看了看 JVM 的东西, 就当作记笔记.
(一)JVM 参数:
第一类包括了标准参数. 顾名思义, 标准参数中包括功能和输出的参数都是很稳定的, 很可能在将来的 JVM 版本中不会改变. 你可以用 java 命令 (或者是用 java -help) 检索出所有标准参数. 我们在第一部分中已经见到过一些标准参数, 例如:-server.
第二类是 X 参数, 非标准化的参数在将来的版本中可能会改变. 所有的这类参数都以 - X 开始, 并且可以用 java -X 来检索. 注意, 不能保证所有参数都可以被检索出来, 其中就没有 - Xcomp.
第三类是包含 XX 参数(到目前为止最多的), 它们同样不是标准的, 甚至很长一段时间内不被列出来(最近, 这种情况有改变 , 我们将在本系列的第三部分中讨论它们). 然而, 在实际情况中 X 参数和 XX 参数并没有什么不同. X 参数的功能是十分稳定的, 然而很多 XX 参数仍在实验当中(主要是 JVM 的开发者用于 debugging 和调优 JVM 自身的实现).
(1)-Xms20M
表示设置 JVM 启动内存的最小值为 20M, 必须以 M 为单位
(2)-Xmx20M
表示设置 JVM 启动内存的最大值为 20M, 必须以 M 为单位. 将 - Xmx 和 - Xms 设置为一样可以避免 JVM 内存自动扩展. 大的项目 - Xmx 和 - Xms 一般都要设置到 10G,20G 甚至还要高
(3)-verbose:gc
表示输出虚拟机中 GC 的详细情况
(4)-Xss128k
表示可以设置虚拟机栈的大小为 128k
(5)-Xoss128k
表示设置本地方法栈的大小为 128k. 不过 HotSpot 并不区分虚拟机栈和本地方法栈, 因此对于 HotSpot 来说这个参数是无效的
(6)-XX:PermSize=10M
表示 JVM 初始分配的永久代的容量, 必须以 M 为单位
(7)-XX:MaxPermSize=10M
表示 JVM 允许分配的永久代的最大容量, 必须以 M 为单位, 大部分情况下这个参数默认为 64M
(8)-Xnoclassgc
表示关闭 JVM 对类的垃圾回收
(9)-XX:+TraceClassLoading
表示查看类的加载信息
(10)-XX:+TraceClassUnLoading
表示查看类的卸载信息
(11)-XX:NewRatio=4
表示设置年轻代: 老年代的大小比值为 1:4, 这意味着年轻代占整个堆的 1/5
(12)-XX:SurvivorRatio=8
表示设置 2 个 Survivor 区: 1 个 Eden 区的大小比值为 2:8, 这意味着 Survivor 区占整个年轻代的 1/5, 这个参数默认为 8
(13)-Xmn20M
表示设置年轻代的大小为 20M
(14)-XX:+HeapDumpOnOutOfMemoryError
表示可以让虚拟机在出现内存溢出异常时 Dump 出当前的堆内存转储快照
(15)-XX:+UseG1GC
表示让 JVM 使用 G1 垃圾收集器
(16)-XX:+PrintGCDetails
表示在控制台上打印出 GC 具体细节
(17)-XX:+PrintGC
表示在控制台上打印出 GC 信息
(18)-XX:PretenureSizeThreshold=3145728
表示对象大于 3145728(3M)时直接进入老年代分配, 这里只能以字节作为单位
(19)-XX:MaxTenuringThreshold=1
表示对象年龄大于 1, 自动进入老年代
(20)-XX:CompileThreshold=1000
表示一个方法被调用 1000 次之后, 会被认为是热点代码, 并触发即时编译
(21)-XX:+PrintHeapAtGC
表示可以看到每次 GC 前后堆内存布局
(22)-XX:+PrintTLAB
表示可以看到 TLAB 的使用情况
(23)-XX:+UseSpining
开启自旋锁
(24)-XX:PreBlockSpin
更改自旋锁的自旋次数, 使用这个参数必须先开启自旋锁
(二)用法实例:
- import java.util.Date;
- import java.util.concurrent.TimeUnit;
- public class Main {
- public static final int MB = 1024 * 1024;
- public static void main(String[] args) throws InterruptedException {
- TimeUnit.SECONDS.sleep(20);
- byte[] b1, b2, b3, b4;
- b1 = new byte[2 * MB];
- System.out.println("第一个 2M 分配完毕 time =" + new Date());
- TimeUnit.SECONDS.sleep(5);
- b2 = new byte[2 * MB];
- System.out.println("第二个 2M 分配完毕 time =" + new Date());
- TimeUnit.SECONDS.sleep(5);
- b3 = new byte[2 * MB];
- System.out.println("第三个 2M 分配完毕 time =" + new Date());
- TimeUnit.SECONDS.sleep(5);
- b4 = new byte[2 * MB];
- System.out.println("第四个 2M 分配完毕 time =" + new Date());
- TimeUnit.SECONDS.sleep(10);
- }
- }
JVM 的参数设置如下: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
Xms 最小堆, Xmx 最大堆都设置为了 20M.Xmn 年轻代设置为了 10M, 那么老年代也为 10M,SurvivorRatio 年轻带的 Eden 和 Survivor 的比值为 8:1:1.
- [GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->1648K(19456K), 0.0012271 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
- [GC (Allocation Failure) [PSYoungGen: 7446K->1016K(9216K)] 8086K->2352K(19456K), 0.0016344 secs] [Times: user=0.13 sys=0.00, real=0.00 secs]
第一个 2M 分配完毕 time = Sun Jul 29 16:00:40 CST 2018
第二个 2M 分配完毕 time = Sun Jul 29 16:00:45 CST 2018
第三个 2M 分配完毕 time = Sun Jul 29 16:00:50 CST 2018
- [GC (Allocation Failure) [PSYoungGen: 8414K->1000K(9216K)] 9750K->8577K(19456K), 0.0065138 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
- [Full GC (Ergonomics) [PSYoungGen: 1000K->0K(9216K)] [ParOldGen: 7577K->8005K(10240K)] 8577K->8005K(19456K), [Metaspace: 9104K->8987K(1058816K)], 0.0158481 secs] [Times: user=0.13 sys=0.00, real=0.02 secs]
第四个 2M 分配完毕 time = Sun Jul 29 16:00:55 CST 2018
- Heap
- PSYoungGen total 9216K, used 2921K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
- eden space 8192K, 35% used [0x00000000ff600000,0x00000000ff8da738,0x00000000ffe00000)
- from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
- to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
- ParOldGen total 10240K, used 8005K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
- object space 10240K, 78% used [0x00000000fec00000,0x00000000ff3d15f8,0x00000000ff600000)
- Metaspace used 9046K, capacity 9256K, committed 9600K, reserved 1058816K
- class space used 1060K, capacity 1117K, committed 1152K, reserved 1048576K
- Process finished with exit code 0
分析如下:
第二, 第三行可能是 TimeUnit.SECONDS.sleep(20); 这个占了内存, 导致内存不足, 然后就进行了两次年轻代 GC, 主要是为了看 jconsole 的那两幅图.
分配了三个 2M 以后, 发觉 eden 区不够位置了, 此时进行了年轻代的 GC, 但是 担保失败了 GC (Allocation Failure). 可能在老年代没有找到连续的内存, 所以担保失败后, 进行了一次 full gc.
我们看 jconsole 上面的两幅图片, 在 16:01 前面, Eden 区突然下降, Old 区突然上升. 说明 full gc 将 Eden 区的三个 2M 的全部放到了 Old 区. 然后将第四个 2M 的放到了 Eden 区.
所以最后, Eden 区分配了一个 b4,Old 区分配了 b1,b2,b3 三个. 看日志也可以看出来.
下来我们修改下参数, 避免它进行 full gc, 毕竟 full gc 是不太好的.
-verbose:gc -Xms110M -Xmx110M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
我们将老年代调到 100M, 你会发觉就不会进行 full gc 了, 因为担保成功了. 所以新生代, 老年代最好的比列, 并不是 1:1, 而是老年代站绝大多数, 毕竟新生代 "死的快".
最近接触到的 JVM 参数仅限于这几个, 工具用的也就是 jps,jconsole, 不是特别的多. 所以这里就举了一个例子出来. 但这个例子的很好的说明了, 如何避免 Full GC
Full GC 发生大概有以下几种情况:
1: 老年代不足, 比如上面的 full gc. 新生代的对象不能全部进入老年代, 即担保失败, 那么就行一次 full gc. 同理大对象直接进入老年代, 也可能进行 full gc
2:CMS 垃圾回收器. CMS 因为采用的标记清除算法, 所以很容易产生大量的碎片, 所以 full gc 特别容易. 还有一种就是 CMS 是一种并发清理垃圾的, 在并发清理的过程, 有对象进入, 但空间不足, 也会造成一次 full gc. 可能通过调参来避免, 比如调到老年代用到了 90%,85% 等进行 cms 收集来避免.
自己本人由于是学生, 实战经验比较少, 对 JVM 理解也不是特别的深入, 如有错误, 还希望各位大神能指出. 大家一同学习进步.
来源: https://www.cnblogs.com/wenbochang/p/9381762.html