JVM优化

  |   0 评论   |   0 浏览

1.我们为什么要对JVM做优化?

在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们可能将有下面

的需求:

运行的应用“卡住了”,日志不输出,程序没有反应

服务器的CPU负载突然升高

在多线程应用下,如何分配线程的数量?

……

以下针对jdk1.8.

2、jvm的运行参数

2.1、三种参数类型

jvm的参数类型分为三类,分别是:

  1. 标准参数

-help

-version

  1. -X参数 (非标准参数)

-Xint

-Xcomp

  1. -XX参数(使用率较高)

-XX:newSize

-XX:+UseSerialGC

2.2、标准参数

2.2.1、参数介绍

jvm的标准参数,一般都是很稳定的,在未来的JVM版本中不会改变,可以使用java -help 检索出所

有的标准参数。

$ java -help
用法: java [-options] class [args...]
           (执行类)
   或  java [-options] -jar jarfile [args...]
           (执行 jar 文件)
其中选项包括:
    -d32	  使用 32 位数据模型 (如果可用)
    -d64	  使用 64 位数据模型 (如果可用)
    -server	  选择 "server" VM
                  默认 VM 是 server,
                  因为您是在服务器类计算机上运行。


    -cp <目录和 zip/jar 文件的类搜索路径>
    -classpath <目录和 zip/jar 文件的类搜索路径>
                  用 : 分隔的目录, JAR 档案
                  和 ZIP 档案列表, 用于搜索类文件。
    -D<名称>=<值>
                  设置系统属性
    -verbose:[class|gc|jni]
                  启用详细输出
    -version      输出产品版本并退出
    -version:<值>
                  警告: 此功能已过时, 将在
                  未来发行版中删除。
                  需要指定的版本才能运行
    -showversion  输出产品版本并继续
    -jre-restrict-search | -no-jre-restrict-search
                  警告: 此功能已过时, 将在
                  未来发行版中删除。
                  在版本搜索中包括/排除用户专用 JRE
    -? -help      输出此帮助消息
    -X            输出非标准选项的帮助
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  按指定的粒度启用断言
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  禁用具有指定粒度的断言
    -esa | -enablesystemassertions
                  启用系统断言
    -dsa | -disablesystemassertions
                  禁用系统断言
    -agentlib:<libname>[=<选项>]
                  加载本机代理库 <libname>, 例如 -agentlib:hprof
                  另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
    -agentpath:<pathname>[=<选项>]
                  按完整路径名加载本机代理库
    -javaagent:<jarpath>[=<选项>]
                  加载 Java 编程语言代理, 请参阅 java.lang.instrument
    -splash:<imagepath>
                  使用指定的图像显示启动屏幕
有关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。

2.2.2、实战

实战1:-showversion参数是表示,先打印版本信息,再执行后面的命令,在调试时非常有用, 后面会使用 到

实战2:通过-D设置系统属性参数

public class TestJVM { 
  public static void main(String[] args) { 
    String str = System.getProperty("str"); 
    if (str == null) { 
      System.out.println("test"); 
    } else { 
      System.out.println(str); 
    } 
  } 
}
#编译 
[root@node01 test]# javac TestJVM.java 
#测试 
[root@node01 test]# java TestJVM 
test
[root@node01 test]# java -Dstr=zhiding TestJVM 
zhiding

2.2.3、-server与-client参数

可以通过-server或-client设置jvm的运行参数。

它们的区别是Server VM的初始堆空间会大一些,默认使用的是并行垃圾回收器,启动慢运行快。

Client VM相对来讲会保守一些,初始堆空间会小一些,使用串行的垃圾回收器,它的目标是为了

让JVM的启动速度更快,但运行速度会比Server模式慢些。

JVM在启动的时候会根据硬件和操作系统自动选择使用Server还是Client类型的JVM。

32位操作系统

如果是Windows系统,不论硬件配置如何,都默认使用Client类型的JVM。

如果是其他操作系统上,机器配置有2GB以上的内存同时有2个以上CPU的话默认使用server模式,否则使用client模式。

64位操作系统,只有server类型,不支持client类型。

[root@node01 test]# java -client -showversion TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
test 
[root@node01 test]# java -server -showversion TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
test 

#由于机器是64位系统,所以不支持client模式

2.3、-X参数

2.3.1、参数介绍

jvm的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可以通过java - X查看非标

准参数。

$ java -X
    -Xmixed           混合模式执行 (默认)
    -Xint             仅解释模式执行
    -Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件>
                      设置搜索路径以引导类和资源
    -Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件>
                      附加在引导类路径末尾
    -Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件>
                      置于引导类路径之前
    -Xdiag            显示附加诊断消息
    -Xnoclassgc       禁用类垃圾收集
    -Xincgc           启用增量垃圾收集
    -Xloggc:<file>    将 GC 状态记录在文件中 (带时间戳)
    -Xbatch           禁用后台编译
    -Xms<size>        设置初始 Java 堆大小
    -Xmx<size>        设置最大 Java 堆大小
    -Xss<size>        设置 Java 线程堆栈大小
    -Xprof            输出 cpu 配置文件数据
    -Xfuture          启用最严格的检查, 预期将来的默认值
    -Xrs              减少 Java/VM 对操作系统信号的使用 (请参阅文档)
    -Xcheck:jni       对 JNI 函数执行其他检查
    -Xshare:off       不尝试使用共享类数据
    -Xshare:auto      在可能的情况下使用共享类数据 (默认)
    -Xshare:on        要求使用共享类数据, 否则将失败。
    -XshowSettings    显示所有设置并继续
    -XshowSettings:all
                      显示所有设置并继续
    -XshowSettings:vm 显示所有与 vm 相关的设置并继续
    -XshowSettings:properties
                      显示所有属性设置并继续
    -XshowSettings:locale
                      显示所有与区域设置相关的设置并继续

-X 选项是非标准选项, 如有更改, 恕不另行通知。


以下选项为 Mac OS X 特定的选项:
    -XstartOnFirstThread
                      在第一个 (AppKit) 线程上运行 main() 方法
    -Xdock:name=<应用程序名称>"
                      覆盖停靠栏中显示的默认应用程序名称
    -Xdock:icon=<图标文件的路径>
                      覆盖停靠栏中显示的默认图标

2.3.2、-Xint、-Xcomp、-Xmixed

在解释模式(interpreted mode)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运

行速度,通常低10倍或更多。

-Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把所有的字节码编译成本地代码,从

而带来最大程度的优化。

然而,很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的少,原因是-

xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代

码都进行编译的话,对于一些只执行一次的代码就没有意义了。

-Xmixed是混合模式,将解释模式与编译模式进行混合使用,由jvm自己决定,这是jvm默认的模

式,也是推荐使用的模式。

示例:强制设置运行模式

#强制设置为解释模式 
[root@node01 test]# java -showversion -Xint TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, interpreted mode) 
test 

#强制设置为编译模式 
[root@node01 test]# java -showversion -Xcomp TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, compiled mode) 
test 

#注意:编译模式下,第一次执行会比解释模式下执行慢一些,注意观察。 

#默认的混合模式 
[root@node01 test]# java -showversion TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
test

2.4、-XX参数

-XX参数也是非标准参数,主要用于jvm的调优和debug操作。

-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型:

  • boolean类型

    格式:-XX:[+-]

    如:-XX:+DisableExplicitGC 表示禁用手动调用gc操作,也就是说调用System.gc()无效

  • 非boolean类型

    格式:-XX:

    如:-XX:NewRatio=1 表示新生代和老年代的比值

用法:

[root@node01 test]# java -showversion -XX:+DisableExplicitGC TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
test

2.5、-Xms与-Xmx参数

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。

-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。

-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。

适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快。

示例:

[root@node01 test]# java -Xms512m -Xmx2048m TestJVM test

2.6、查看jvm的运行参数

有些时候我们需要查看jvm的运行参数,这个需求可能会存在2种情况:

第一,运行java命令时打印出运行参数;

第二,查看正在运行的java进程的参数;

2.6.1、运行java命令时打印参数

运行java命令时打印参数,需要添加-XX:+PrintFlagsFinal参数即可。

[root@node01 test]# java -XX:+PrintFlagsFinal -version

参数有boolean类型和数字类型,值的操作符是=或:=,分别代 表默认值和被

修改的值。

java -XX:+PrintFlagsFinal -XX:+VerifySharedSpaces -version 
	intx ValueMapInitialSize = 11 {C1 product} 
	intx ValueMapMaxLoopSize = 8 {C1 product} 
	intx ValueSearchLimit = 1000 {C2 product} 
	bool VerifyMergedCPBytecodes = true {product} 
	bool VerifySharedSpaces := true {product}
	intx WorkAroundNPTLTimedWaitHang = 1 {product} 
	uintx YoungGenerationSizeIncrement = 20 {product} 
	uintx YoungGenerationSizeSupplement = 80 {product} 
	uintx YoungGenerationSizeSupplementDecay = 8 {product} 
	uintx YoungPLABSize = 4096 {product} 
	bool ZeroTLAB = false {product} 
	intx hashCode = 5 {product} 
	
	java version "1.8.0_141" 
	Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
	Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 

#可以看到VerifySharedSpaces这个参数已经被修改了。

2.6.2、查看正在运行的jvm参数

如果想要查看正在运行的jvm就需要借助于jinfo命令查看。

#查看所有的参数,用法:jinfo -flags <进程id>
#通过jps 或者 jps -l 查看java进程 
[root@node01 bin]# jps 
6346 Jps 
6219 Bootstrap 

[root@node01 bin]# jps -l 
6358 sun.tools.jps.Jps 
6219 org.apache.catalina.startup.Bootstrap 

[root@node01 bin]# jinfo -flags 6219 
Attaching to process ID 6219, please wait... 
Debugger attached successfully. 
Server compiler detected. 
JVM version is 25.141-b15 
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 -XX:MaxHeapSize=488636416 -XX:MaxNewSize=162529280 - XX:MinHeapDeltaBytes=524288 -XX:NewSize=10485760 -XX:OldSize=20971520 - XX:+UseCompressedClassPointers -XX:+UseCompressedOops - XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC Command line: -Djava.util.logging.config.file=/tmp/apache-tomcat- 7.0.57/conf/logging.properties - Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager - Djava.endorsed.dirs=/tmp/apache-tomcat-7.0.57/endorsed - Dcatalina.base=/tmp/apache-tomcat-7.0.57 -Dcatalina.home=/tmp/apache- tomcat-7.0.57 -Djava.io.tmpdir=/tmp/apache-tomcat-7.0.57/temp 

#查看某一参数的值,用法:jinfo -flag <参数名> <进程id> 
[root@node01 bin]# jinfo -flag MaxHeapSize 6219 
-XX:MaxHeapSize=488636416

3、jvm的内存模型

jdk1.7和jdk1.8内存模型有较大的区别.

3.1 jdk1.7的堆内存模型

image.png

  • Young 年轻代

Young区被划分为Eden区以及两个严格相同的Survivor区,其中Survivor区间中某一个时刻只有其中一个是被使用的,另外一个留作垃圾收集时复制对象用,在Eden区间变满的时候,GC就会将存活的对象移到空闲的Survivor区间中,根据JVM策略,在经过几次垃圾收集后,仍然存活于Survivor的对象将被移动到Tenured年老区间。

  • Tenured 年老区

Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。

  • Perm 永久区

Perm代主要保存class,method,filed对象,这部分的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemory:PermGen space的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm区中,这种情况下,一般重新启动应用服务器可以解决问题.

  • Virtual区

最大内存和初始内存的差值,就是virtual区.

3.2 jdk1.8的堆内存模型

image.png

jdk1.8内存模型由两部分构成, 年轻代 + 年老代。

年轻代: Eden + 2个Survivor

年老代: OldGen

在jdk1.8中用元数据空间Metaspace替换了永久代Perm区;元数据空间所占用的内存空间不在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别。
image.png

3.3 为什么要废弃jdk1.7中的永久区

由于永久代内存经常不够用或发生内存泄露,产生异常java.lang.OutOfMemoryError:PermGen,所以使用了元空间,改为使用本次内存空间。

3.4 jstat命令查看堆内存使用情况

可以查:

  • 编译统计,jstat -compiler 2060

    Compiled:编译数量。

    Failed:失败数量

    Invalid:不可用数量

    Time:时间

    FailedType:失败类型

    FailedMethod:失败的方法

  • 垃圾回收统计,jstat -gc 2060

    S0C:第一个Survivor区的大小(KB)

    S1C:第二个Survivor区的大小(KB)

    S0U:第一个Survivor区的使用大小(KB)

    S1U:第二个Survivor区的使用大小(KB)

    EC:Eden区的大小(KB)

    EU:Eden区的使用大小(KB)

    OC:Old区大小(KB)

    OU:Old使用大小(KB)

    MC:方法区大小(KB)

    MU:方法区使用大小(KB)

    CCSC:压缩类空间大小(KB)

    CCSU:压缩类空间使用大小(KB)

    YGC:年轻代垃圾回收次数

    YGCT:年轻代垃圾回收消耗时间

    FGC:老年代垃圾回收次数

    FGCT:老年代垃圾回收消耗时间

    GCT:垃圾回收消耗总时间

  • 堆内存统计,jstat -gccapacity 2060

  • 新生代垃圾回收统计,jstat -gcnew 7172

  • 新生代内存统计,jstat -gcnewcapacity 7172

  • 老年代垃圾回收统计,jstat -gcold 7172

  • 老年代内存统计,jstat -gcoldcapacity 7172

  • 元数据空间统计,jstat -gcmetacapacity 7172

  • 总垃圾回收统计,jstat -gcutil 7172

  • JVM编译方法统计 jstat -printcompilation 7172

命令格式:

jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]

命令选项可以通过jstat -options查看

参考: https://www.cnblogs.com/lizhonghua34/p/7307139.html

https://blog.csdn.net/qq_34504318/article/details/107465766

3.5 jmap的使用以及内存溢出分析

3.5.1查看内存使用情况

jmap -heap 6219
sh-4.2# jmap -heap 1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.211-b12

using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 6442450944 (6144.0MB)
   NewSize                  = 2147483648 (2048.0MB)
   MaxNewSize               = 2147483648 (2048.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 134217728 (128.0MB)
   CompressedClassSpaceSize = 260046848 (248.0MB)
   MaxMetaspaceSize         = 268435456 (256.0MB)
   G1HeapRegionSize         = 2097152 (2.0MB)

Heap Usage:
G1 Heap:
   regions  = 3072
   capacity = 6442450944 (6144.0MB)
   used     = 2362114600 (2252.6880264282227MB)
   free     = 4080336344 (3891.3119735717773MB)
   36.66484418014685% used
G1 Young Generation:
Eden Space:
   regions  = 540
   capacity = 2126512128 (2028.0MB)
   used     = 1132462080 (1080.0MB)
   free     = 994050048 (948.0MB)
   53.25443786982248% used
Survivor Space:
   regions  = 62
   capacity = 130023424 (124.0MB)
   used     = 130023424 (124.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 527
   capacity = 4185915392 (3992.0MB)
   used     = 1099629096 (1048.6880264282227MB)
   free     = 3086286296 (2943.3119735717773MB)
   26.26974014098754% used

35455 interned Strings occupying 3962304 bytes.

3.5.2 查看内存中对象数量及大小

#查看所有对象,包括活跃以及非活跃的
jmap -histo <pid> | more
#查看活跃对象
jmap -histo:live <pid> | more
说明:
 B byte C char D double F float I int J long Z boolean [ 数组,如[I表示int[] [L+类名 其他对象

3.5.3 将内存使用情况dump到文件中

jmap -dump:format=b,file=dumpFileName <pid>
#示例 
jmap -dump:format=b,file=/tmp/dump.dat 6219

3.5.4 通过jhat对dump文件进行分析

jhat -port <port> <file>
#示例
jhat -port 9999 /tmp/dump.dat

3.6 用MAT工具对dump文件进行分析

MAT(Memory Analyzer Tool) 是基于eclipse的内存分析工具 https://www.eclipse.org/mat/

下载地址:https://www.eclipse.org/mat/downloads.php

3.7.内存溢出的定位与分析

不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出

package club.jvm;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TestJVMOutOfMemory {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            String str = "";
            for (int j = 0; j < 1000; j++) {
                str += UUID.randomUUID().toString();
            }
            list.add(str);
        }
        System.out.println("ok");
    }
}
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3223.hprof ...
Heap dump file created [8152331 bytes in 0.042 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at club.jvm.TestJVMOutOfMemory.main(TestJVMOutOfMemory.java:13)

Process finished with exit code 1

会在项目根目录下生产 java_pid3223.hprof文件

3.8 jstack查看线程执行情况

jstack 2203

4、线程状态

image.png

线程一共有6种状态:

  • 初始态(New)

创建一个Thread对象,但还未调用start()启动线程,这时处于初始态。

  • 运行态(Running), 又分为: 就绪态 和 运行态
    • 就绪态:该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行,所有就绪态的线程存放在就绪队列中
    • 运行态: 获得CPU执行权并正在执行的线程,由于同一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程
  • 阻塞态(Blocked)

当一条正在执行的线程请求某一资源失败时,就会进入阻塞态,

而在Java中,阻塞态专指请求锁失败时进入的状态

由一个阻塞队列存放所有阻塞态的线程

处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。

  • 等待态(Waiting)

当前线程中调用 wait, join,park函数时,当前线程就会进入等待态。

也有一个等待队列存放所有等待态的线程。

线程处于等待态表示它需要等待其他线程的指示才能继续运行。

进入等待态的线程会释放CPU执行权,并释放资源(如:锁)

  • 超时等待态(Timed_waiting)

当运行中的线程调用sleep(time), wait, join, parkNanos, parkUntil时,就会进入该状态;它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;进入该状态也会释放CPU执行权和占用的资源,与等待态的区别: 到了超时时间后自动进入阻塞队列,开始竞争锁

  • 终止态

线程执行结束后的状态。

4.1死锁

启动两个线程,thread1拿到了obj1的锁,准备去拿obj2的锁,obj2已经被thread2锁定,所以发生死锁。

package club.jvm;

public class TestDeadLock {
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();

    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable {
        @Override
        public void run() {
            synchronized (obj1) {
                System.out.println("Thread1 拿到了obj1的锁");
                try {
                    //停顿2秒, 让thread2线程拿到obj2的锁
                    Thread.sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (obj2) {
                    System.out.println("thread1 拿到了obj2的锁");
                }
            }
        }
    }

    private static class Thread2 implements Runnable {
        @Override
        public void run() {
            synchronized (obj2) {
                System.out.println("Thread2 拿到了obj2的锁");
                try {
                    //停顿2秒, 让thread1线程拿到obj1的锁
                    Thread.sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (obj1) {
                    System.out.println("thread2 拿到了obj1的锁");
                }
            }
        }
    }

}

运行打印

Thread1 拿到了obj1的锁
Thread2 拿到了obj2的锁
。。。这里就发生了死锁,一直等待

4.2使用jstack打印线程快照

$ jps
2817 RemoteMavenServer36
2786
2809 Launcher
3722 Jps
3678 Launcher
3679 TestDeadLock

# apple @ wujingjian in ~/coding/huihu/hh-phone-aggregator/hh-phone-consumer [10:46:17]
$ jstack 3679
2021-04-29 10:46:21
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode):

"Attach Listener" #14 daemon prio=9 os_prio=31 tid=0x00007fe068873000 nid=0xa603 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #13 prio=5 os_prio=31 tid=0x00007fe068801800 nid=0x2603 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #12 prio=5 os_prio=31 tid=0x00007fe068872800 nid=0xa803 waiting for monitor entry [0x0000700002587000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at club.jvm.TestDeadLock$Thread2.run(TestDeadLock.java:42)
	- waiting to lock <0x000000076ac85678> (a java.lang.Object)
	- locked <0x000000076ac85688> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

"Thread-0" #11 prio=5 os_prio=31 tid=0x00007fe069014800 nid=0x5603 waiting for monitor entry [0x0000700002484000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at club.jvm.TestDeadLock$Thread1.run(TestDeadLock.java:24)
	- waiting to lock <0x000000076ac85688> (a java.lang.Object)
	- locked <0x000000076ac85678> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

"Service Thread" #10 daemon prio=9 os_prio=31 tid=0x00007fe068800800 nid=0x4303 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #9 daemon prio=9 os_prio=31 tid=0x00007fe06606b000 nid=0x4003 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007fe06606a800 nid=0x3f03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007fe066069800 nid=0x3d03 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007fe066069000 nid=0x4603 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007fe06605f800 nid=0x4803 runnable [0x0000700001d6f000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	- locked <0x000000076adcc568> (a java.io.InputStreamReader)
	at java.io.InputStreamReader.read(InputStreamReader.java:184)
	at java.io.BufferedReader.fill(BufferedReader.java:161)
	at java.io.BufferedReader.readLine(BufferedReader.java:324)
	- locked <0x000000076adcc568> (a java.io.InputStreamReader)
	at java.io.BufferedReader.readLine(BufferedReader.java:389)
	at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fe066803000 nid=0x4903 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fe06900d800 nid=0x3403 in Object.wait() [0x0000700001b69000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076ab08ec0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
	- locked <0x000000076ab08ec0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fe06900d000 nid=0x3303 in Object.wait() [0x0000700001a66000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076ab06b68> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x000000076ab06b68> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=31 tid=0x00007fe06600c000 nid=0x4f03 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fe069007800 nid=0x2007 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fe069008800 nid=0x2a03 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fe069009000 nid=0x5403 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fe06880a800 nid=0x2d03 runnable

"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007fe06880b000 nid=0x2f03 runnable

"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007fe06880b800 nid=0x3103 runnable

"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007fe069009800 nid=0x3203 runnable

"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007fe06900a000 nid=0x5103 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007fe06702b000 nid=0x5503 waiting on condition

JNI global references: 33


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007fe06a00d6a8 (object 0x000000076ac85678, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fe06a00ae18 (object 0x000000076ac85688, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at club.jvm.TestDeadLock$Thread2.run(TestDeadLock.java:42)
	- waiting to lock <0x000000076ac85678> (a java.lang.Object)
	- locked <0x000000076ac85688> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)
"Thread-0":
	at club.jvm.TestDeadLock$Thread1.run(TestDeadLock.java:24)
	- waiting to lock <0x000000076ac85688> (a java.lang.Object)
	- locked <0x000000076ac85678> (a java.lang.Object)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.
可以发现:
Thread1 获取了 <0x000000076ac85688> 的锁,等待 <0x000000076ac85678>
Thread2 获取了 <0x000000076ac85678> 的锁, 等待 <0x000000076ac85688>

5、VisualVM工具

VisualVM能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。

  • 内存信息
  • 线程信息
  • Dump堆(本地进程)
  • Dump线程(本地进程)
  • 打开堆Dump,堆Dump可以用jmap来生产
  • 打开线程Dump
  • 生成应用快照(包含内存,线程信息等等)
  • 性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类对象占用的内存,检查哪些类占用内存多)

5.1监控远程tomcat

#在tomcat的bin目录下,修改catalina.sh,添加如下的参数 
JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" 

#这几个参数的意思是: 
#-Dcom.sun.management.jmxremote :允许使用JMX远程管理 #-Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口 #-Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用户都可 以连接 #-Dcom.sun.management.jmxremote.ssl=false :不使用ssl

6,Arthas

https://arthas.aliyun.com/doc/

#!/usr/bin/env bash
PIDS=`ps -ef | grep java | grep "arthas-boot.jar" |awk '{print $2}'`
echo -e "Stopping the arthas-boot ...\c"
for PID in $PIDS ; do
    kill $PID > /dev/null 2>&1
done

COUNT=0
while [ $COUNT -lt 1 ]; do
    echo -e ".\c"
    sleep 1
    COUNT=1
    for PID in $PIDS ; do
        PID_EXIST=`ps -f -p $PID | grep java`
        if [ -n "$PID_EXIST" ]; then
            COUNT=0
            break
        fi
    done
done

echo "OK!"

if [ ! -n "$1" ] ;then
    APP_PID=`netstat -nlp | grep '8080' | awk '{print $7}' | awk -F"/" '{print $1}'`
else
    APP_PID=`netstat -nlp | grep $1 | awk '{print $7}' | awk -F"/" '{ print $1 }'`
fi
#echo $APP_PID

if [ "$APP_PID" = '' ] ;then
    echo 'pid not found!'
    exit;
fi

if [ ! -n "$2" ] ;then
   #  echo '$2 is null'
   java -jar /root/.arthas/lib/3.1.1/arthas/arthas-boot.jar $APP_PID
else
   java -jar /root/.arthas/lib/3.1.1/arthas/arthas-boot.jar $APP_PID -c "$2" | grep -A 100 "$2"
fi

标题:JVM优化
作者:码农路上
地址:http://wujingjian.club/articles/2021/04/29/1619683563094.html