Jvm调优/学习笔记

Posted by Jaimin on February 1, 2020

JVM参数

  • 标准参数

-version -help -cp

  • -X参数

    非标准参数,也就是在JDK各个版本中可能会变动

-Xint 解释执行 -Xcomp 第一次使用就编译成本地代码 -Xmixed 混合模式,JVM自己来决定

  • -XX参数

    使用得最多的参数类型,非标准化参数, 主要用于JVM调优和Debug

a.Boolean类型 格式:-XX:[+-] +或-表示启用或者禁用name属性 比如:

-XX:+UseConcMarkSweepGC 表示启用CMS类型的垃圾回收器 -XX:+UseG1GC 表示启用G1类型的垃圾回收器

b.非Boolean类型 格式:-XX=表示name属性的值是value 比如:-XX:MaxGCPauseMillis=500

  • 其他参数(也相当于-XX参数)

-Xms1000等价于-XX:InitialHeapSize=1000 -Xmx1000等价于-XX:MaxHeapSize=1000 -Xss100等价于-XX:ThreadStackSize=100

  • 查看参数

java -XX:+PrintFlagsFinal -version > flags.txt

1594536890192

值得注意的是”=”表示默认值,”:=”表示被用户或JVM修改后的值 ,一般要设置参数,可以先查看一下当前参数是什么,然后进行修改

  • 设置参数的方式

开发工具中设置比如IDEA,eclipse 运行jar包的时候:java -XX:+UseG1GC xxx.jar web容器比如tomcat,可以在脚本中的进行设置,例如tomcat的catalina.sh 通过jinfo实时调整某个java进程的参数(参数只有被标记为manageable的flags可以被实时修改)

常用参数含义

参数 含义 说明
-XX:CICompilerCount=3 最大并行编译数 如果设置大于1,虽然编译速度会提高,但是同样影响系 统稳定性,会增加JVM崩溃的可能
-XX:InitialHeapSize=100M 初始化堆大小 简写-Xms100M
-XX:MaxHeapSize=100M 最大堆大小 简写-Xmx100M
-XX:NewSize=20M 设置年轻代的大小  
-XX:MaxNewSize=50M 年轻代最大大小  
-XX:OldSize=50M 设置老年代大小  
-XX:MetaspaceSize=50M 设置方法区大小  
-XX:MaxMetaspaceSize=50M 方法区最大大小  
-XX:+UseParallelGC 使用UseParallelGC 新生代,吞吐量优先
-XX:+UseParallelOldGC 使用UseParallelOldGC 老年代,吞吐量优先
-XX:+UseConcMarkSweepGC 使用CMS 老年代,停顿时间优先
-XX:+UseG1GC 使用G1GC 新生代,老年代,停顿时间优先
-XX:NewRatio 新老生代的比值 比如-XX:Ratio=4,则表示新生代:老年代=1:4,也就是新 生代占整个堆内存的1/5
-XX:SurvivorRatio 两个S区和Eden区的比值 比如-XX:SurvivorRatio=8,也就是(S0+S1):Eden=2:8, 也就是一个S占整个新生代的1/10
-XX:+HeapDumpOnOutOfMemoryError 启动堆内存溢出打印 当JVM堆内存发生溢出时,也就是OOM,自动生成dump 文件
-XX:HeapDumpPath=heap.hprof 指定堆内存溢出打印目录 表示在当前目录生成一个heap.hprof文件
XX:+PrintGCDetails - XX:+PrintGCTimeStamps - XX:+PrintGCDateStamps Xloggc:$CATALINA_HOME/logs/gc.log 打印出GC日志 可以使用不同的垃圾收集器,对比查看GC情况
-Xss128k 设置每个线程的堆栈大小 经验值是3000-5000最佳
-XX:MaxTenuringThreshold=6 提升年老代的最大临界值 默认值为 15
-XX:InitiatingHeapOccupancyPercent 启动并发GC周期时堆内存使用占比 G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆 的使用率,而不只是某一代内存的使用比. 值为 0 则表 示”一直执行GC循环”. 默认值为 45.
-XX:G1HeapWastePercent 允许的浪费堆空间的占比 默认是10%,如果并发标记可回收的空间小于10%,则不 会触发MixedGC。
-XX:MaxGCPauseMillis=200ms G1最大停顿时间 暂停时间不能太小,太小的话就会导致出现G1跟不上垃 圾产生的速度。最终退化成Full GC。所以对这个参数的 调优是一个持续的过程,逐步调整到最佳状态。
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量 默认值随JVM运行的平台不同而不同
-XX:G1MixedGCLiveThresholdPercent=65 混合垃圾回收周期中要包括的旧区域设置 占用率阈值 默认占用率为 65%
-XX:G1MixedGCCountTarget=8 设置标记周期完成后,对存活数据上限为 G1MixedGCLIveThresholdPercent 的旧 区域执行混合垃圾回收的目标次数 默认8次混合垃圾回收,混合回收的目标是要控制在此目 标次数以内
- XX:G1OldCSetRegionThresholdPercent=1 描述Mixed GC时,Old Region被加入到 CSet中 默认情况下,G1只把10%的Old Region加入到CSet中

常用命令

jps

查看java进程

jps -l 查看明细

jinfo

  • 查看某个java进程的name属性的值

jinfo -flag name PID

例如:

jinfo -flag MaxHeapSize PID jinfo -flag UseG1GC PID

1594537853203

  • 修改,参数只有被标记为manageable的flags可以被实时修改

jinfo -flag [+|-] PID jinfo -flag = PID

  • 查看曾经赋过值的一些参数

jinfo -flags PID

jstat

  • 查看类装载信息

jstat -class PID 1000 10 查看某个java进程的类装载信息,每1000毫秒输出一次,共输出10次 ;

jstat -gc PID 1000 10 查看垃圾收集信息

jstack

jstack PID 查看线程堆栈信息

  • 死锁案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class DeadLockDemo {
    public static void main(String[] args)
    {
        DeadLock d1=new DeadLock(true);
        DeadLock d2=new DeadLock(false);
        Thread t1=new Thread(d1);
        Thread t2=new Thread(d2);
        t1.start();
        t2.start();
    }
}
//定义锁对象
class MyLock{
    public static Object obj1=new Object();
    public static Object obj2=new Object();
}
//死锁代码
class DeadLock implements Runnable {
    private boolean flag;

    DeadLock(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        if (flag) {
            while (true) {
                synchronized (MyLock.obj1) {
                    System.out.println(Thread.currentThread().getName() + "----if 获得obj1锁");
                    synchronized (MyLock.obj2) {
                        System.out.println(Thread.currentThread().getName() + "----if获得obj2锁");
                    }
                }
            }
        } else {
            while (true) {
                synchronized (MyLock.obj2) {
                    System.out.println(Thread.currentThread().getName() + "----否则获得obj2锁");
                    synchronized (MyLock.obj1) {
                        System.out.println(Thread.currentThread().getName() + "----否则获得obj1锁");
                    }
                }
            }
        }
    }
}

通过jstack分析,把打印信息拉到最后可以发现

1594539633734

jmap

  • 打印出堆内存相关信息

通过idea 设置VM options -XX:+PrintFlagsFinal -Xms300M -Xmx300M 执行 jmap -heap PID

1594539993261

  • dump出堆内存相关信息

jmap -dump:format=b,file=heap.hprof PID

  • 要是在发生堆内存溢出的时候,自动dump出该文件

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof

常用工具

jconsole

jconsole工具是JDK自带的可视化监控工具。查看java应用程序的运行概况、监控堆信息、永久区使用情况、类加载情况等。

命令行中输入:jconsole

jvisualvm

监控本地Java进程

可以监控本地的java进程的CPU,类,线程等

监控远端Java进程

(1)在jvisualvm中选中“远程”,右击“添加” (2)主机名上写服务器的ip地址,比如31.100.39.63,然后点击“确定” (3)右击该主机“31.100.39.63”,添加“JMX”[也就是通过JMX技术具体监控远端服务器哪个Java进程] (4)要想让服务器上的tomcat被连接,需要改一下 bin/catalina.sh 这个文件

1
2
3
4
5
6
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -
Djava.rmi.server.hostname=31.100.39.63 -Dcom.sun.management.jmxremote.port=8998
-Dcom.sun.management.jmxremote.ssl=false -
Dcom.sun.management.jmxremote.authenticate=true -
Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access -
Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password"

(5)在 ../conf 文件中添加两个文件jmxremote.access和jmxremote.password

  • jmxremote.access 文件

    guest readonly manager readwrite

  • jmxremote.password 文件

    guest guest manager manager

    注:授予权限 : chmod 600 jmxremot

Arthas

Arthas 是Alibaba开源的Java诊断工具,采用命令行交互模式,是排查jvm相关问题的利器。

1594540544751

  • 下载安装

    curl -O https://alibaba.github.io/arthas/arthas-boot.jar java -jar arthas-boot.jar

    然后可以选择一个Java进程

  • 常用命令

    version:查看arthas版本号
    help:查看命名帮助信息
    cls:清空屏幕
    session:查看当前会话信息
    quit:退出arthas客户端
    ---
    dashboard:当前进程的实时数据面板
    thread:当前JVM的线程堆栈信息
    jvm:查看当前JVM的信息
    sysprop:查看JVM的系统属性
    ---
    sc:查看JVM已经加载的类信息
    dump:dump已经加载类的byte code到特定目录
    jad:反编译指定已加载类的源码
    ---
    monitor:方法执行监控
    watch:方法执行数据观测
    trace:方法内部调用路径,并输出方法路径上的每个节点上耗时
    stack:输出当前方法被调用的调用路径
    

MAT

Java堆分析器,用于查找内存泄漏 Heap Dump,称为堆转储文件,是Java进程在某个时间内的快照

  • 获取Dump文件

    手动 : jmap -dump:format=b,file=heap.hprof PID

    自动 : -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof

  • 使用

    • Histogram

    Histogram可以列出内存中的对象,对象的个数及其大小

    Class Name:类名称,java类名 Objects:类的对象的数量,这个对象被创建了多少个 Shallow Heap:一个对象内存的消耗大小,不包含对其他对象的引用 Retained Heap:是shallow Heap的总和,即该对象被GC之后所能回收到内存的总和

    右击类名—>List Objects—>with incoming references—>列出该类的实例

    右击Java对象名—>Merge Shortest Paths to GC Roots—>exclude all …—>找到GCRoot以及原因

    • Leak Suspects

    查找并分析内存泄漏的可能原因

    Reports—>Leak Suspects—>Details

    • Top Consumers

    列出大对象

GC日志分析工具

拿到GC日志文件

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:gc.log

在线工具:http://gceasy.io

GCViewer

GC优化分析

垃圾收集发生的时机

一般以下几种情况会发生垃圾回收

(1)当Eden区或者S区不够用了 (2)老年代空间不够用了 (3)方法区空间不够用了 (4)System.gc()

System.gc()只是通知要回收,什么时候回收由JVM决定。 但是不建议手动调用该方法,因为消耗的资源比较大。

GC日志文件分析

  • 使用gceasy

1594541485491

1594541527583

  • GCViewer

1594541570788

G1调优与最佳指南

是否选用G1垃圾收集器的判断依据

(1)50%以上的堆被存活对象占用 (2)对象分配和晋升的速度变化非常大 (3)垃圾回收时间比较长

  • 使用G1GC垃圾收集器: -XX:+UseG1GC

修改配置参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间

  • 调整内存大小再获取gc日志分析

-XX:MetaspaceSize=100M -Xms300M -Xmx300M

比如设置堆内存的大小,获取到gc日志,使用GCViewer分析吞吐量和响应时间

  • 调整最大停顿时间

-XX:MaxGCPauseMillis=20 设置最大GC停顿时间指标

比如设置最大停顿时间,获取到gc日志,使用GCViewer分析吞吐量和响应时间

  • 启动并发GC时堆内存占用百分比

-XX:InitiatingHeapOccupancyPercent=45 G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为 0 则表示“一直执行GC循环)’. 默认值为 45 (例如, 全部的 45% 或者使用了45%).

比如设置该百分比参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间

最佳指南

官网建议 :https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations

(1)不要手动设置新生代和老年代的大小,只要设置整个堆的大小

G1收集器在运行过程中,会自己调整新生代和老年代的大小 其实是通过adapt代的大小来调整对象晋升的速度和年龄,从而达到为收集器设置的暂停时间目标 如果手动设置了大小就意味着放弃了G1的自动调优

(2)不断调优暂停时间目标

一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满足。

(3)使用-XX:ConcGCThreads=n来增加标记线程的数量

IHOP如果阀值设置过高,可能会遇到转移失败的风险,比如对象进行转移时空间不足。如果阀值设置过低,就会使标记周期运行过于频繁,并且有可能混合收集期回收不到空间。 IHOP值如果设置合理,但是在并发周期时间过长时,可以尝试增加并发线程数,调高ConcGCThreads。

(4)适当增加堆内存大小

JVM性能优化思路

1594527126628

常见问题汇总

(1)内存泄漏与内存溢出的区别 内存泄漏:对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。 内存溢出:内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的。 (2)young gc会有stw吗? 不管什么 GC,都会有 stop-the-world,只是发生时间的长短。 (3)major gc和full gc的区别 major gc指的是老年代的gc,而full gc等于young+old+metaspace的gc。 (4)G1与CMS的区别是什么 CMS 用于老年代的回收,而 G1 用于新生代和老年代的回收。 G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生。 (5)什么是直接内存 直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于Java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。 (6)不可达的对象一定要被回收吗?

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。 被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关 联,否则就会被真的回收。 (7)方法区中的无用类回收方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?

判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是无用的类 :

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。 (8)不同的引用

JDK1.2以后,Java对引用进行了扩充:强引用、软引用、弱引用和虚引用