JVM优化
1.我们为什么要对JVM做优化?
在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们可能将有下面
的需求:
运行的应用“卡住了”,日志不输出,程序没有反应
服务器的CPU负载突然升高
在多线程应用下,如何分配线程的数量?
……
以下针对jdk1.8.
2、jvm的运行参数
2.1、三种参数类型
jvm的参数类型分为三类,分别是:
- 标准参数
-help
-version
- -X参数 (非标准参数)
-Xint
-Xcomp
- -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的堆内存模型
- 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的堆内存模型
jdk1.8内存模型由两部分构成, 年轻代 + 年老代。
年轻代: Eden + 2个Survivor
年老代: OldGen
在jdk1.8中用元数据空间Metaspace替换了永久代Perm区;元数据空间所占用的内存空间不在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别。
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、线程状态
线程一共有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