在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们
可能将有下面的需求:
运行的应用“卡住了”,日志不输出,程序没有反应
服务器的CPU负载突然升高
在多线程应用下,如何分配线程的数量?
……
在本次课程中,我们将对jvm有更深入的学习,我们不仅要让程序能跑起来,而且是可以跑的更快!可以分析解决在生产环境中所遇到的各种“棘手”的问题。(本文使用java版本为1.8)
jvm的参数类型分为三类,分别是:
jvm的标准参数,一般都是很稳定的,在未来的JVM版本中不会改变,可以使用java -help检索出所有的标准参数。
[root@meimei ~]# 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。
[root@meimei test]# java -version
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
public class TestJVM {
public static void main(String[] args) {
String s = System.getProperty("str");
if (s == null) {
System.out.println("i am xxx");
}else {
System.out.println(s);
}
}
}
进行编译、测试:
[root@meimei test]# javac TestJVM.java
[root@meimei test]# ll
总用量 8
-rw-r--r--. 1 root root 575 2月 17 02:37 TestJVM.class
-rw-r--r--. 1 root root 254 2月 17 02:37 TestJVM.java
[root@meimei test]# java TestJVM.class
错误: 找不到或无法加载主类 TestJVM.class
[root@meimei test]# java TestJVM
i am xxx
[root@meimei test]# java -Dstr=helloJVM TestJVM
helloJVM
可以通过-server或-client设置jvm的运行参数。
showversion
命令)jvm的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可以通过java -X查看非标准参数。
[root@meimei test]# 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 选项是非标准选项, 如有更改, 恕不另行通知。
在解释模式(interpreted mode)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运行速度,通常低10倍或更多。
-Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把所有的字节码编译成本地代码,从而带来最大程度的优化。
然而,很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的
少,原因是-xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代码都进行编译的话,对于一些只执行一次的代码就没有意义了。
-Xmixed是混合模式,将解释模式与编译模式进行混合使用,由jvm自己决定,这是jvm默认的模式,也是推荐使用的模式。
示例:强制设置运行模式
#强制设置为编译模式
[root@meimei test]# java -showversion -Xcomp TestJVM
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, compiled mode)
i am xxx
#注意:编译模式下,第一次执行会比解释模式下执行慢一些,注意观察。
#强制设置为解释模式
[root@meimei test]# java -showversion -Xint TestJVM
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, interpreted mode)
i am xxx
#默认的混合模式
[root@meimei test]# java -showversion TestJVM
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
i am xxx
-XX参数也是非标准参数,主要用于jvm的调优和debug操作。
-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型:
[root@meimei test]# java -showversion -XX:+DisableExplicitGC TestJVM
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
i am xxx
-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。
-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。
-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。
适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快。
示例:
[root@meimei test]# java -Xms64m -Xmx1024m TestJVM
i am xxx
[root@meimei test]# java -Xms2m -Xmx24m TestJVM
i am xxx
[root@meimei test]# java TestJVM
i am xxx
现在程序比较小,设置不同的大小看不出效果来,以后程序大了,这个就大有用处了!
有些时候我们需要查看jvm的运行参数,这个需求可能会存在2种情况:
第一,运行java命令时打印出运行参数;
第二,查看正在运行的java进程的参数;
运行java命令时需要打印参数,添加-XX:+PrintFlagsFinal参数即可。
[root@meimei test]# java -XX:+PrintFlagsFinal -version
[Global flags]
bool UseCompressedClassPointers := true {lp64_product}
bool UseCompressedOops := true {lp64_product}
bool UseConcMarkSweepGC = false {product}
bool UseCondCardMark = false {C2 product}
bool UseContainerSupport = true {product}
bool UseCountLeadingZerosInstruction = true {ARCH product}
bool UseCountTrailingZerosInstruction = true {ARCH product}
bool UseCountedLoopSafepoints = false {C2 product}
bool UseCounterDecay = true {product}
bool UseDivMod = true {C2 product}
bool UseDynamicNumberOfGCThreads = false {product}
bool UseFPUForSpilling = true {C2 product}
bool UseFastAccessorMethods = false {product}
bool UseFastEmptyMethods = false {product}
bool UseFastJNIAccessors = true {product}
bool UseFastStosb = true {ARCH product}
bool UseG1GC = false {product}
bool UseGCLogFileRotation = false {product}
bool UseGCOverheadLimit = true {product}
bool UseGCTaskAffinity = false {product}
bool UseGHASHIntrinsics = true {product}
bool UseHeavyMonitors = false {product}
bool UseHugeTLBFS = false {product}
bool UseInlineCaches = true {product}
bool UseInterpreter = true {product}
bool UseJumpTables = true {C2 product}
bool UseLWPSynchronization = true {product}
bool UseLargePages = false {pd product}
bool UseLargePagesInMetaspace = false {product}
bool UseLargePagesIndividualAllocation = false {pd product}
bool UseLinuxPosixThreadCPUClocks = true {product}
bool UseLockedTracing = false {product}
....
由上述的信息可以看出,参数有boolean类型和数字类型,值的操作符是= 或 :=,分别代表默认值和被修改的值。
这里来手动修改UseLockedTracing 的值:
[root@meimei test]# java -XX:+UseLockedTracing -XX:+PrintFlagsFinal -version
[Global flags]
bool UseJumpTables = true {C2 product}
bool UseLWPSynchronization = true {product}
bool UseLargePages = false {pd product}
bool UseLargePagesInMetaspace = false {product}
bool UseLargePagesIndividualAllocation = false {pd product}
bool UseLinuxPosixThreadCPUClocks = true {product}
bool UseLockedTracing := true {product}
bool UseLoopCounter = true {product}
bool UseLoopInvariantCodeMotion = true {C1 product}
bool UseLoopPredicate = true {C2 product}
bool UseMathExactIntrinsics = true {C2 product}
bool UseMaximumCompactionOnSystemGC = true {product}
bool UseMembar = false {pd product}
bool UseMontgomeryMultiplyIntrinsic = true {C2 product}
bool UseMontgomerySquareIntrinsic = true {C2 product}
如果想要查看正在运行的jvm就需要借助于jinfo命令查看。
首先,启动一个tomcat用于测试,来观察下运行的jvm参数。
注意:需要先在本地安装一个tomcat!linux的安装看:tomcat安装
然后启动tomcat:
cd apache‐tomcat‐7.0.57
cd bin/
./startup.sh
ip+端口号访问tomcat:
查看tomcat(一个java程序)运行时的所有参数:
首先查看tomcat运行时的进程id:
#传统的:查看进程可以使用传统的ps命令去查看
[root@meimei bin]# ps -ef | grep tomcat
root 8222 1 0 03:39 pts/1 00:00:03 /usr/local/jdk/jdk1.8.0_241/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/apache-tomcat-7.0.57/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/usr/local/tomcat/apache-tomcat-7.0.57/endorsed -classpath /usr/local/tomcat/apache-tomcat-7.0.57/bin/bootstrap.jar:/usr/local/tomcat/apache-tomcat-7.0.57/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat/apache-tomcat-7.0.57 -Dcatalina.home=/usr/local/tomcat/apache-tomcat-7.0.57 -Djava.io.tmpdir=/usr/local/tomcat/apache-tomcat-7.0.57/temp org.apache.catalina.startup.Bootstrap start
root 8287 7467 0 03:47 pts/1 00:00:00 grep tomcat
# 查看java进程最常用的就是这个jps命令了!
[root@meimei bin]# jps
8289 Jps
8222 Bootstrap
# 使用jps命令查看java进程的详细信息
[root@meimei bin]# jps -l
8299 sun.tools.jps.Jps
8222 org.apache.catalina.startup.Bootstrap
# 查看某一个java进程的详细信息使用 jinfo -flags 进程id名称
[root@meimei bin]# jinfo -flags 8222
Attaching to process ID 8222, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.241-b07
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=16777216 -XX:MaxHeapSize=257949696 -XX:MaxNewSize=85983232 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=5570560 -XX:OldSize=11206656 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps
Command line: -Djava.util.logging.config.file=/usr/local/tomcat/apache-tomcat-7.0.57/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/usr/local/tomcat/apache-tomcat-7.0.57/endorsed -Dcatalina.base=/usr/local/tomcat/apache-tomcat-7.0.57 -Dcatalina.home=/usr/local/tomcat/apache-tomcat-7.0.57 -Djava.io.tmpdir=/usr/local/tomcat/apache-tomcat-7.0.57/temp
# 查看某一个java进程的某一参数的值 使用 jinfo ‐flag <参数名> <进程id>
[root@meimei bin]# jinfo -flag MaxNewSize 8222
-XX:MaxNewSize=85983232
jvm的内存模型在1.7和1.8有较大的区别,虽然本套课程是以1.8为例进行讲解,但是我们
也是需要对1.7的内存模型有所了解,所以接下里,我们将先学习1.7再学习1.8的内存模型。
由上图可以看出,jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。
年轻代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。
需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存
空间中,这也是与1.7的永久代最大的区别所在。
官网给出了解释:http://openjdk.java.net/jeps/122
大概:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
现实使用中,由于永久代内存经常不够用或发生内存泄露,爆出异常
java.lang.OutOfMemoryError: PermGen。
基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
[root@meimei ~]# jps -l
8222 org.apache.catalina.startup.Bootstrap
8558 sun.tools.jps.Jps
[root@meimei ~]# jstat -class 8222
Loaded Bytes Unloaded Bytes Time
2782 5510.3 1 1.5 1.16
说明:
Loaded:加载class的数量
Bytes:所占用空间大小
Unloaded:未加载数量
Bytes:未加载占用空间
Time:时间
[root@meimei ~]# jstat -compiler 8222
Compiled Failed Invalid Time FailedType FailedMethod
1905 0 0 2.04 0
说明:
Compiled:编译数量。
Failed:编译失败数量
Invalid:不可用数量
Time:时间
FailedType:失败类型
FailedMethod:失败的方法
[root@meimei ~]# jstat -gc 8222
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1152.0 1152.0 0.0 0.0 9280.0 6952.7 23084.0 13848.7 17664.0 17108.5 2048.0 1881.6 24 0.090 2 0.033 0.123
#也可以指定打印的间隔和次数,每1秒中打印一次,共打印5次
[root@meimei ~]# jstat -gc 8222 1000 5
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1152.0 1152.0 0.0 0.0 9280.0 7549.3 23084.0 13848.7 17664.0 17108.5 2048.0 1881.6 24 0.090 2 0.033 0.123
1152.0 1152.0 0.0 0.0 9280.0 7549.3 23084.0 13848.7 17664.0 17108.5 2048.0 1881.6 24 0.090 2 0.033 0.123
1152.0 1152.0 0.0 0.0 9280.0 7549.3 23084.0 13848.7 17664.0 17108.5 2048.0 1881.6 24 0.090 2 0.033 0.123
1152.0 1152.0 0.0 0.0 9280.0 7549.3 23084.0 13848.7 17664.0 17108.5 2048.0 1881.6 24 0.090 2 0.033 0.123
1152.0 1152.0 0.0 0.0 9280.0 7549.3 23084.0 13848.7 17664.0 17108.5 2048.0 1881.6 24 0.090 2 0.033 0.123
说明:
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可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总、对内存溢出的定位与分析。
[root@meimei ~]# jmap -heap 8222
Attaching to process ID 8222, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.241-b07
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration:#堆内存配置信息
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 257949696 (246.0MB)
NewSize = 5570560 (5.3125MB)
MaxNewSize = 85983232 (82.0MB)
OldSize = 11206656 (10.6875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:# 堆内存的使用情况
New Generation (Eden + 1 Survivor Space):#年轻代
capacity = 10682368 (10.1875MB)
used = 1084848 (1.0345916748046875MB)
free = 9597520 (9.152908325195312MB)
10.1555011023773% used
Eden Space:
capacity = 9502720 (9.0625MB)
used = 979560 (0.9341812133789062MB)
free = 8523160 (8.128318786621094MB)
10.308206492456897% used
From Space:
capacity = 1179648 (1.125MB)
used = 105288 (0.10041046142578125MB)
free = 1074360 (1.0245895385742188MB)
8.925374348958334% used
To Space:
capacity = 1179648 (1.125MB)
used = 0 (0.0MB)
free = 1179648 (1.125MB)
0.0% used
tenured generation: #年老代
capacity = 23638016 (22.54296875MB)
used = 14181096 (13.524147033691406MB)
free = 9456920 (9.018821716308594MB)
59.99275066063074% used
12445 interned Strings occupying 1755088 bytes.
#查看所有对象,包括活跃以及非活跃的
jmap ‐histo <pid> | more
#查看活跃对象
jmap ‐histo:live <pid> | more
[root@meimei ~]# jmap -histo 8222 | more
num #instances #bytes class name
----------------------------------------------
1: 36247 7601952 [C
2: 7384 2330680 [B
3: 34130 819120 java.lang.String
4: 4450 798552 [I
5: 17182 549824 java.util.HashMap$Node
6: 3096 352168 java.lang.Class
7: 5540 339600 [Ljava.lang.Object;
8: 3738 328944 java.lang.reflect.Method
9: 1038 212112 [Ljava.util.HashMap$Node;
10: 2500 143712 [Ljava.lang.String;
11: 3442 110144 java.util.concurrent.ConcurrentHashMap$Node
12: 2157 86280 java.util.LinkedHashMap$Entry
13: 1686 80928 java.util.HashMap
14: 1762 70480 java.util.HashMap$ValueIterator
15: 3284 70048 [Ljava.lang.Class;
16: 2899 69576 java.util.ArrayList
17: 70 67584 [Ljava.util.concurrent.ConcurrentHashMap$Node;
18: 1271 61008 org.apache.tomcat.util.digester.CallMethodRule
19: 1767 56544 java.io.File
20: 1671 53472 com.sun.org.apache.xerces.internal.xni.QName
21: 96 49984 [Ljava.util.WeakHashMap$Entry;
22: 1553 49696 java.util.Hashtable$Entry
......
#对象说明
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对它进行分析,jmap也是支持dump到文件中的。
用法:
jmap ‐dump:format=b,file=dumpFileName <pid>
示例:
[root@meimei ~]# jmap -dump:format=b,file=test/dump.dat 8222
Dumping heap to /root/test/dump.dat ...
Heap dump file created
[root@meimei ~]# cd test
[root@meimei test]# ll
总用量 23796
-rw-------. 1 root root 24358304 2月 17 05:00 dump.dat
-rw-r--r--. 1 root root 575 2月 17 02:37 TestJVM.class
-rw-r--r--. 1 root root 254 2月 17 02:37 TestJVM.java
可以看到已经在/test下生成了dump.dat的文件。
在上一小节中,已经将jvm的内存dump到文件中,这个文件是一个二进制的文件,不方便查看,这时我们可以借助于jhat工具进行查看。
#用法:
jhat ‐port <port> <file>
示例:
[root@meimei test]# jhat -port 9999 ./dump.dat
Reading from ./dump.dat...
Dump file created Mon Feb 17 05:00:56 CST 2020
Snapshot read, resolving...
Resolving 199293 objects...
Chasing references, expect 39 dots.......................................
Eliminating duplicate references.......................................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.
注意这里的9999端口需要开放才能够使用:
/sbin/iptables -I INPUT -p tcp --dport 9999 -j ACCEPT
/etc/rc.d/init.d/iptables save
/etc/init.d/iptables status
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。
官网地址:https://www.eclipse.org/mat/
下载略。。。
查看对象以及它的依赖:
查看可能存在内存泄露的分析:
内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。
如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。
首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap与MAT工具进行定位分析。
接下来,我们模拟内存溢出的场景。
编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。
public class TestJvmOutOfMemory {
public static void main(String[] args) {
//在一个list集合中天机一百万条数据,每条数据由1000条字符串组成
List<String> list = new ArrayList<String>();
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");
}
}
为了演示效果,我们将设置执行的参数,这里使用的是Idea编辑器。
#参数如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
测试结果如下:
可以看到,当发生内存溢出时,会dump文件到java_pid15072.hprof。
可以看到,有91.03%的内存由Object[]数组占有,所以比较可疑。
分析:这个可疑是正确的,因为已经有超过90%的内存都被它占有,这是非常有可能出现内存溢出的。
查看详情:
可以看到集合中存储了大量的uuid字符串。
有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析呢?
由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。
这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进行快照,并且打印出来:
#用法:jstack <pid>
[root@meimei bin]# jstack 2203
Full thread dump Java HotSpot(TM) 64‐Bit Server VM (25.141‐b15 mixed
mode):
"Attach Listener" #24 daemon prio=9 os_prio=0 tid=0x00007fabb4001000
nid=0x906 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE......
如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我们可以借助jstack进行分析,下面我们实战下查找死锁的原因。
编写代码,启动2个线程,Thread1拿到了obj1锁,准备去拿obj2锁时,obj2已经被
Thread2锁定,所以发送了死锁。
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 (InterruptedException 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 (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println("Thread2 拿到了 obj1 的锁!");
}
}
}
}
}
[root@node01 test]# javac TestDeadLock.java
[root@node01 test]# ll
总用量 28
‐rw‐r‐‐r‐‐. 1 root root 184 9月 11 10:39 TestDeadLock$1.class
‐rw‐r‐‐r‐‐. 1 root root 843 9月 11 10:39 TestDeadLock.class
‐rw‐r‐‐r‐‐. 1 root root 1567 9月 11 10:39 TestDeadLock.java
‐rw‐r‐‐r‐‐. 1 root root 1078 9月 11 10:39 TestDeadLock$Thread1.class
‐rw‐r‐‐r‐‐. 1 root root 1078 9月 11 10:39 TestDeadLock$Thread2.class
‐rw‐r‐‐r‐‐. 1 root root 573 9月 9 10:21 TestJVM.class
‐rw‐r‐‐r‐‐. 1 root root 261 9月 9 10:21 TestJVM.java
[root@node01 test]# java TestDeadLock
Thread1 拿到了 obj1 的锁!
Thread2 拿到了 obj2 的锁!
#这里发生了死锁,程序一直将等待下去
[root@node01 ~]# jps
...
[root@node01 ~]# jstack 3256
Full thread dump Java HotSpot(TM) 64‐Bit Server VM (25.141‐b15 mixed
mode):
"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f5bfc001000
nid=0xcff waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007f5c2c008800 nid=0xcb9
waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
。。。。。。。
在输出的信息中,已经看到,发现了1个死锁,关键看这个:
"Thread‐1":
at TestDeadLock$Thread2.run(TestDeadLock.java:47)
‐ waiting to lock <0x00000000f655dc40> (a java.lang.Object)
‐ locked <0x00000000f655dc50> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread‐0":
at TestDeadLock$Thread1.run(TestDeadLock.java:27)
‐ waiting to lock <0x00000000f655dc50> (a java.lang.Object)
‐ locked <0x00000000f655dc40> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
可以清晰的看到:
VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出的)。
VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。
在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。