前言
由于上次线上full gc,让我这个没有机会实战接触jvm的人,尝到了一定的甜头,同时也觉得自己还有很多东西需要去实战并总结。这是一篇记录jvm配置参数,使用jvisualvm工具来让人对jvm更加熟悉的一篇文章。
jvm参数配置
以jdk1.8为例 ,介绍jvm参数配置之前,先要知道Jvm内存模型。堆,方法区,栈,本地方法栈,程序计数器。如下:图1.0
图1.0
jdk1.8 将方法区中的运行时常量池移动到了堆中,类的元数据放到了本地内存。栈,本地方法栈,程序计数器为线程私有。所以jvm内存大小 = 堆的大小。而 堆的大小 = 年轻代 + 老年代;年轻代的大小 = from survivor区 + to survivor 区 + eden 区。
- jvm内存区域
eg:
-Xmx100m : jvm最大内存大小为100M
-Xms100m : jvm每次full gc后,jvm可用最大内存,也称初始jvm可用内存大小;建议设置和Xmx一样大小
-Xmn50m : 年轻代最大,而且初始可用内存大小为50m (设置了这个,-XX:NewSize=20M和-XX:MaxNewSize=20 就没必要设置了。设置年轻代大小的时候,建议用-Xmn50m)
-XX:NewSize=20M : 年轻代初始可用内存大小20M
-XX:MaxNewSize=50M : 年轻代最大可用内存大小为50M
-XX:NewRatio=4 : 设置年轻代可用内存大小和老年代的比值为4,年轻代:老年代=1:4 那么年轻代占jvm内存可用大小为1/5
-XX:SurvivorRatio=8 : 设置survivor与eden区的比值为8,年轻代中有两个survivor区,所以2个survivor : eden = 2:8 ,那么一个survivor区占年轻代1/10。整个年轻代可用内存大小为9/10
- GC日志打印
eg:
-Xloggc:log.gc : 记录gc日志在当前目录
-XX:+PrintGC : 输出GC日志
-XX:+PrintGCDetails : 输出GC详细信息
-XX:+PrintGCTimestamps : 输出GC日志,以时间戳的形式
-XX:+PrintGCDateStamps : 输出GC日志,以时间点的信息
-XX:+PrintHeapAtGC : 输出GC前后,堆栈信息
- jvm垃圾回收器
图2.0
年轻代:
Serial :串行年轻代垃圾收集器 单线程
ParNew : 并发年轻代垃圾收集器 多线程
Parallel Scavenge : 并行年轻代垃圾收集器 多cpu
老年代:
Serial old 相对于Serial的老年代
CMS 相对于ParNew的老年代 多线程
Parallel old 相当于Parallel Scavenge的。
下面是参考网上的,用来作为笔记
参数设置
eg:
吞吐量优先
-XX:+UseParallelGC : 年轻代使用 并行垃圾收集器
-XX:+UseParallelOldGC : 老年代使用 并行垃圾收集器
-XX:+ParallelGCThreads=20 并行的线程为20 建议最好是和处理器数目一样
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
响应时间优先
-XX:+UseConcMarkSweepGC : 设置老年代并发收集
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
-XX:CMSFullGCsBeforeCompaction=5:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
实战
- 利用idea开发工具,设置如下jvm参数,如下:图3.0
图3.0
代码如下:
public class Test{
public static void main(String[] args) {
byte[] a = new byte[2*1024*1024];
byte[] b = new byte[2*1024*1024];
byte[] c = new byte[2*1024*1024];
byte[] d = new byte[2*1024*1024];
byte[] e = new byte[2*1024*1024];
byte[] f = new byte[2*1024*1024];
byte[] g = new byte[2*1024*1024];
byte[] h = new byte[2*1024*1024];
}
}
运行出现:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
查看该目录下的loggc.gc文件,我拿出部分出来分析一下
{Heap before GC invocations=2 (full 1):
PSYoungGen total 13824K, used 10729K [0x00000000ff100000, 0x0000000100000000, 0x0000000100000000)
eden space 12288K, 87% used [0x00000000ff100000,0x00000000ffb7a7b0,0x00000000ffd00000)
from space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
to space 1536K, 51% used [0x00000000ffd00000,0x00000000ffdc6030,0x00000000ffe80000)
ParOldGen total 5120K, used 4104K [0x00000000fec00000, 0x00000000ff100000, 0x00000000ff100000)
object space 5120K, 80% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff100000)
Metaspace used 3469K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 380K, capacity 388K, committed 512K, reserved 1048576K
2018-12-11T16:20:02.537+0800: 0.133: [Full GC (Ergonomics) [PSYoungGen: 10729K->4756K(13824K)] [ParOldGen: 4104K->4097K(5120K)] 14833K->8853K(18944K), [Metaspace: 3469K->3469K(1056768K)], 0.0050671 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap after GC invocations=2 (full 1):
PSYoungGen total 13824K, used 4756K [0x00000000ff100000, 0x0000000100000000, 0x0000000100000000)
eden space 12288K, 38% used [0x00000000ff100000,0x00000000ff5a5238,0x00000000ffd00000)
from space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
to space 1536K, 0% used [0x00000000ffd00000,0x00000000ffd00000,0x00000000ffe80000)
ParOldGen total 5120K, used 4097K [0x00000000fec00000, 0x00000000ff100000, 0x00000000ff100000)
object space 5120K, 80% used [0x00000000fec00000,0x00000000ff0004f8,0x00000000ff100000)
Metaspace used 3469K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 380K, capacity 388K, committed 512K, reserved 1048576K
}
先看Heap上的信息 再GC前heap上年轻代的内存可用大小为13824K,但是我设置的15M,这是为什么呢,因为年轻代的大小 = survivor + eden ;还有一块survivor要空着。所以PSYoungGen = eden space + from/to space = 12288K + 1536K = 13824K 老年代大小为5120K=5M,这个是没错的。
然后看下PrintGCDetials输出的详细的内容
2018-12-11T16:20:02.537+0800: 0.133: [Full GC (Ergonomics) [PSYoungGen: 10729K->4756K(13824K)] [ParOldGen: 4104K->4097K(5120K)] 14833K->8853K(18944K), [Metaspace: 3469K->3469K(1056768K)], 0.0050671 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2018-12-11T16:20:02.537+0800: 0.133 时间戳
Full GC
[PSYoungGen: 10729K->4756K(13824K)] 年轻代已用内存10729K,经过这次full gc,年轻代已用4756K,说明回收了10729K-4756K=5973K
[ParOldGen: 4104K->4097K(5120K)] 老年代已用内存4104K,经过这次full gc,老年代已用4097 ,说明回收了4104K-4097K= 7K
14833K->8853K(18944K) 堆内已用内存14833K,经过这个full GC ,堆内已用内存8853K ,说明回收了14833K-8853K= 5980K 等与年轻代回收内存大小+老年代回收内存大小 5973K+7K=5980K
- 利用jvisualvm.exe进行代码监控
这个是jdk自带的程序,打开jdk安装目录,点进去打开bin目录,下图4.0 是我的安装目录
图4.0
双击打开如 图5.0
图5.0
可以看到左上角是Java中的进程,可以用再cmd 下用jps 查看,其实是一样的 如下图6.0
图6.0
运行如下代码:
public class Test{
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 8; i++) {
byte[] a = new byte[2*1024*1024];
Thread.sleep(2*1000); //有时间查看Test进程
}
}
}
jvisualvm捕捉到了该Test进程 如下图7.0
图7.0
可以看到该进程id和我设置的jvm参数配置
也可以查看相应的GC 如图8.0
图8.0
和对应的GC监控 如图9.0
图9.0
哈哈哈,算是写完了,补充了上一篇欠下的东西。上次多谢 不如隐茶去的提醒,我的博客图片资源全部挂了,后面才发现公司内网才能访问该图片资源。如果LZ的分享有用的话,还望点个推荐。有问题还望留言探讨。