jvm本地实战

Java基础

浏览数:146

2019-3-20

AD:资源代下载服务

前言

​ 由于上次线上full gc,让我这个没有机会实战接触jvm的人,尝到了一定的甜头,同时也觉得自己还有很多东西需要去实战并总结。这是一篇记录jvm配置参数,使用jvisualvm工具来让人对jvm更加熟悉的一篇文章。

jvm参数配置

​ 以jdk1.8为例 ,介绍jvm参数配置之前,先要知道Jvm内存模型。堆,方法区,栈,本地方法栈,程序计数器。如下:图1.0

图1.0

​ jdk1.8 将方法区中的运行时常量池移动到了堆中,类的元数据放到了本地内存。栈,本地方法栈,程序计数器为线程私有。所以jvm内存大小 = 堆的大小。而 堆的大小 = 年轻代 + 老年代;年轻代的大小 = from survivor区 + to survivor 区 + eden 区。

  1. 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
  1. GC日志打印
eg:
    -Xloggc:log.gc : 记录gc日志在当前目录
    -XX:+PrintGC : 输出GC日志
    -XX:+PrintGCDetails : 输出GC详细信息
    -XX:+PrintGCTimestamps : 输出GC日志,以时间戳的形式
    -XX:+PrintGCDateStamps : 输出GC日志,以时间点的信息
  -XX:+PrintHeapAtGC : 输出GC前后,堆栈信息
  1. 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:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

实战

  1. 利用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

  1. 利用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的分享有用的话,还望点个推荐。有问题还望留言探讨。