1 、我们为什么要对jvm做优化?
在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们可能将有下面的需求:
-
运行的应用“卡住了”,日志不输出,程序没有反应
-
服务器的CPU负载突然升高
-
在多线程应用下,如何分配线程的数量?
-
…
说明: 使用的jdk版本为 1. 8 。
2 、jvm的运行参数
在jvm中有很多的参数可以进行设置,这样可以让jvm在各种环境中都能够高效的运行。绝大部分的参数保持默认即可。
2. 1 、三种参数类型
jvm的参数类型分为三类,分别是:
-
标准参数
- help
- version
-
-X参数 (非标准参数)
- Xint
- Xcomp
-
-XX参数(使用率较高)
- XX:newSize
- XX:+UseSerialGC
2. 2 、标准参数
jvm的标准参数,一般都是很稳定的,在未来的JVM版本中不会改变,可以使用java - help检索出所有的标准参数。
[root@node01 ~]# java ‐help
用法: java [‐options] class [args...]
(执行类)
或 java [‐options] ‐jar jarfile [args...]
(执行 jar 文件)
其中选项包括:
‐d 32 使用 32 位数据模型 (如果可用)
‐d 64 使用 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>
使用指定的图像显示启动屏幕
2. 2. 1 、实战
实战 1 :查看jvm版本
[root@node01 ~]# java ‐version
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)
#‐showversion参数是表示,先打印版本信息,再执行后面的命令,在调试时非常有用,
后面会使用到。
实战 2 :通过-D设置系统属性参数
public class TestJVM {
public static void main(String[] args) {
String str = System.getProperty("str");
if (str == null) {
System.out.println("zhangcc");
} else {
System.out.println(str);
}
}
}
进行编译、测试:
[root@node01 test]# javac TestJVM.java
#测试
[root@node01 test]# java TestJVM
zhangcc
[root@node01 test]# java ‐Dstr=123 TestJVM
123
2. 2. 2 、-server与-client参数
可以通过-server或-client设置jvm的运行参数。
-
它们的区别是Server VM的初始堆空间会大一些,默认使用的是并行垃圾回收器,启动慢运行快。
-
Client VM相对来讲会保守一些,初始堆空间会小一些,使用串行的垃圾回收器,它的目标是为了让JVM的启动速度更快,但运行速度会比Serverm模式慢些。
-
JVM在启动的时候会根据硬件和操作系统自动选择使用Server还是Client类型的JVM。
-
32 位操作系统
-
如果是Windows系统,不论硬件配置如何,都默认使用Client类型的JVM
-
如果是其他操作系统上,机器配置有 2 GB以上的内存同时有 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)
zhangcc
[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)
zhangcc
#由于机器是64位系统,所以不支持client模式
2. 3 、-X参数
jvm的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可以通过java -X查看非标准参数。
[root@node 01 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 选项是非标准选项, 如有更改, 恕不另行通知。
2. 3. 1 、-Xint、-Xcomp、-Xmixed
-
在解释模式(interpreted mode)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运行速度,通常低 10 倍或更多。
-
Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把所有的字节码编译成本地代码,从而带来最大程度的优化。
- 然而,很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的少,原因是-xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代码都进行编译的话,对于一些只执行一次的代码就没有意义了。
-
Xmixed是混合模式,将解释模式与编译模式进行混合使用,由jvm自己决定,这是
jvm默认的模式,也是推荐使用的模式。
示例:强制设置运行模式
# 强制设置为解释模式
[root@node 01 test]# java ‐showversion ‐Xint TestJVM
java version " 1. 8. 0 _ 141 "
Java(TM) SE Runtime Environment (build 1. 8. 0 _ 141 ‐b 15 )
Java HotSpot(TM) 64 ‐Bit Server VM (build 25. 141 ‐b 15 , interpreted mode)
zhangcc
#强制设置为编译模式
[root@node 01 test]# java ‐showversion ‐Xcomp TestJVM
java version " 1. 8. 0 _ 141 "
Java(TM) SE Runtime Environment (build 1. 8. 0 _ 141 ‐b 15 )
Java HotSpot(TM) 64 ‐Bit Server VM (build 25. 141 ‐b 15 , compiled mode)
zhangcc
#注意:编译模式下,第一次执行会比解释模式下执行慢一些,注意观察。
#默认的混合模式
[root@node 01 test]# java ‐showversion TestJVM
java version " 1. 8. 0 _ 141 "
Java(TM) SE Runtime Environment (build 1. 8. 0 _ 141 ‐b 15 )
Java HotSpot(TM) 64 ‐Bit Server VM (build 25. 141 ‐b 15 , mixed mode)
zhangcc
2. 4 、-XX参数
-XX参数也是非标准参数,主要用于jvm的调优和debug操作。
-XX参数的使用有 2 种方式,一种是boolean类型,一种是非boolean类型:
-
boolean类型
- 格式:-XX:[±]< name > 表示启用或禁用< name >属性
- 如:-XX:+DisableExplicitGC 表示禁用手动调用gc操作,也就是说调用System.gc()无效
-
非boolean类型
- 格式:-XX:< name >=< value > 表示< name >属性的值为< value >
- 如:-XX:NewRatio= 1 表示新生代和老年代的比值
用法:
[root@node 01 test]# java ‐showversion ‐XX:+DisableExplicitGC TestJVM
java version " 1. 8. 0 _ 141 "
Java(TM) SE Runtime Environment (build 1. 8. 0 _ 141 ‐b 15 )
Java HotSpot(TM) 64 ‐Bit Server VM (build 25. 141 ‐b 15 , mixed mode)
zhangcc
2. 5 、-Xms与-Xmx参数
-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。
-Xmx 2048 m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为 2048 M。
-Xms 512 m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为 512 M。
适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快。
示例:
[root@node 01 test]# java ‐Xms 512 m ‐Xmx 2048 m TestJVM
zhangcc
2. 6 、查看jvm的运行参数
有些时候我们需要查看jvm的运行参数,这个需求可能会存在 2 种情况:
第一,运行java命令时打印出运行参数;
第二,查看正在运行的java进程的参数;
2. 6. 1 、运行java命令时打印参数
运行java命令时打印参数,需要添加-XX:+PrintFlagsFinal参数即可。
[Global flags]
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product}
uintx AdaptiveSizePolicyInitializingSteps = 20 {product}
uintx AdaptiveSizePolicyOutputInterval = 0 {product}
uintx AdaptiveSizePolicyWeight = 10 {product}
uintx AdaptiveSizeThroughPutPolicy = 0 {product}
uintx AdaptiveTimeWeight = 25 {product}
bool AdjustConcurrency = false {product}
bool AggressiveOpts = false {product}
intx AliasLevel = 3 {C2 product}
bool AlignVector = false {C2 product}
intx AllocateInstancePrefetchLines = 1 {product}
intx AllocatePrefetchDistance = 192 {product}
intx AllocatePrefetchInstr = 3 {product}
intx AllocatePrefetchLines = 4 {product}
intx AllocatePrefetchStepSize = 64 {product}
intx AllocatePrefetchStyle = 1 {product}
bool AllowJNIEnvProxy = false {product}
bool AllowNonVirtualCalls = false {product}
bool AllowParallelDefineClass = false {product}
bool AllowUserSignalHandlers = false {product}
bool AlwaysActAsServerClassMachine = false {product}
bool AlwaysCompileLoopMethods = false {product}
bool AlwaysLockClassLoader = false {product}
bool AlwaysPreTouch = false {product}
bool AlwaysRestoreFPU = false {product}
bool AlwaysTenure = false {product}
bool AssertOnSuspendWaitFailure = false {product}
bool AssumeMP = false {product}
intx AutoBoxCacheMax = 128 {C2 product}
uintx AutoGCSelectPauseMillis = 5000 {product}
intx BCEATraceLevel = 0 {product}
intx BackEdgeThreshold = 100000 {pd product}
bool BackgroundCompilation = true {pd product}
uintx BaseFootPrintEstimate = 268435456 {product}
intx BiasedLockingBulkRebiasThreshold = 20 {product}
intx BiasedLockingBulkRevokeThreshold = 40 {product}
intx BiasedLockingDecayTime = 25000 {product}
intx BiasedLockingStartupDelay = 4000 {product}
bool BindGCTaskThreadsToCPUs = false {product}
bool BlockLayoutByFrequency = true {C2 product}
intx BlockLayoutMinDiamondPercentage = 20 {C2 product}
bool BlockLayoutRotateLoops = true {C2 product}
bool BranchOnRegister = false {C2 product}
bool BytecodeVerificationLocal = false {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-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b12, mixed mode)
由上述的信息可以看出,参数有boolean类型和数字类型,值的操作符是=或:=,分别代表默认值和被修改的值。
示例:
java ‐XX:+PrintFlagsFinal ‐XX:+VerifySharedSpaces ‐version
intx ValueMapInitialSize = 11
{C 1 product}
intx ValueMapMaxLoopSize = 8
{C 1 product}
intx ValueSearchLimit = 1000
{C 2 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 ‐b 15 )
Java HotSpot(TM) 64 ‐Bit Server VM (build 25. 141 ‐b 15 , mixed mode)
#可以看到VerifySharedSpaces这个参数已经被修改了。
2. 6. 2 、查看正在运行的jvm参数
如果想要查看正在运行的jvm就需要借助于jinfo命令查看。
首先,在虚拟机上启动一个tomcat用于测试,来观察下运行的jvm参数。
#查看所有的参数,用法:jinfo ‐flags <进程id>
#通过jps 或者 jps ‐l 查看java进程
[root@node 01 bin]# jps
6346 Jps
6219 Bootstrap
[root@node 01 bin]# jps ‐l
6358 sun.tools.jps.Jps
6219 org.apache.catalina.startup.Bootstrap
[root@node 01 bin]# jinfo ‐flags
Attaching to process ID 6219 , please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25. 141 ‐b 15
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@node 01 bin]# jinfo ‐flag MaxHeapSize
‐XX:MaxHeapSize= 488636416
3 、jvm的内存模型
jvm的内存模型在 1.7 和 1.8 有较大的区别,虽然本套课程是以 1.8 为例进行讲解,但是我们也是需要对 1.7 的内存模型有所了解,所以接下里,我们将先学习 1.7 再学习 1.8 的内存模型。
3. 1 、jdk 1. 7 的堆内存模型
-
Young 年轻区(代)
Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。
-
Tenured 年老区
Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。 -
Perm 永久区
Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。 -
Virtual区:
最大内存和初始内存的差值,就是Virtual区。
3. 2 、jdk 1. 8 的堆内存模型
由上图可以看出,jdk 1.8 的内存模型是由 2 部分组成,年轻代 + 年老代。
年轻代:Eden + 2 *Survivor
年老代:OldGen
在jdk 1. 8 中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。
需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与 1. 7 的永久代最大的区别所在。
3. 3 、为什么要废弃 1. 7 中的永久区?
官网给出了解释:http://openjdk.java.net/jeps/ 122
This is part of the JRockit and Hotspot convergence effort. JRockit
customers do not need to configure the permanent generation (since JRockit
does not have a permanent generation) and are accustomed to not
configuring the permanent generation.
移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,
不需要配置永久代。
现实使用中,由于永久代内存经常不够用或发生内存泄露,报出异常java.lang.OutOfMemoryError: PermGen。基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。
3. 4 、通过jstat命令进行查看堆内存使用情况
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
3. 4. 1 、查看class加载统计
[root@node 01 ~]# jps
7080 Jps
6219 Bootstrap
[root@node 01 ~]# jstat ‐class
Loaded Bytes Unloaded Bytes Time
3273 7122. 3 0 0. 0 3. 98
说明:
-
Loaded:加载class的数量
-
Bytes:所占用空间大小
-
Unloaded:未加载数量
-
Bytes:未加载占用空间
-
Time:时间
3. 4. 2 、查看编译统计
[root@node 01 ~]# jstat ‐compiler
Compiled Failed Invalid Time FailedType FailedMethod
2376 1 0 8. 04 1
org/apache/tomcat/util/IntrospectionUtils setProperty
说明:
-
Compiled:编译数量。
-
Failed:失败数量
-
Invalid:不可用数量
-
Time:时间
-
FailedType:失败类型
-
FailedMethod:失败的方法
3. 4. 3 、垃圾回收统计
[root@node 01 ~]# jstat ‐gc
S 0 C S 1 C S 0 U S 1 U EC EU OC OU MC
MU CCSC CCSU YGC YGCT FGC FGCT GCT
9216. 0 8704. 0 0. 0 6127. 3 62976. 0 3560. 4 33792. 0 20434. 9
23808. 0 23196. 1 2560. 0 2361. 6 7 1. 078 1 0. 244 1. 323
#也可以指定打印的间隔和次数,每 1 秒中打印一次,共打印 5 次
[root@node 01 ~]# jstat ‐gc 6219 1000
S 0 C S 1 C S 0 U S 1 U EC EU OC OU MC
MU CCSC CCSU YGC YGCT FGC FGCT GCT
9216. 0 8704. 0 0. 0 6127. 3 62976. 0 3917. 3 33792. 0 20434. 9
23808. 0 23196. 1 2560. 0 2361. 6 7 1. 078 1 0. 244 1. 323
9216. 0 8704. 0 0. 0 6127. 3 62976. 0 3917. 3 33792. 0 20434. 9
23808. 0 23196. 1 2560. 0 2361. 6 7 1. 078 1 0. 244 1. 323
9216. 0 8704. 0 0. 0 6127. 3 62976. 0 3917. 3 33792. 0 20434. 9
23808. 0 23196. 1 2560. 0 2361. 6 7 1. 078 1 0. 244 1. 323
9216. 0 8704. 0 0. 0 6127. 3 62976. 0 3917. 3 33792. 0 20434. 9
23808. 0 23196. 1 2560. 0 2361. 6 7 1. 078 1 0. 244 1. 323
9216. 0 8704. 0 0. 0 6127. 3 62976. 0 3917. 3 33792. 0 20434. 9
23808. 0 23196. 1 2560. 0 2361. 6 7 1. 078 1 0. 244 1. 323
说明:
-
S 0 C:第一个Survivor区的大小(KB)
-
S 1 C:第二个Survivor区的大小(KB)
-
S 0 U:第一个Survivor区的使用大小(KB)
-
S 1 U:第二个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:垃圾回收消耗总时间
4 、jmap的使用以及内存溢出分析
前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总、对内存溢出的定位与分析。
4. 1 、查看内存使用情况
[root@node^01 ~]# jmap ‐heap 6219
Attaching to process ID 6219 , please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25. 141 ‐b 15
using thread‐local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration: #堆内存配置信息
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 488636416 ( 466. 0 MB)
NewSize = 10485760 ( 10. 0 MB)
MaxNewSize = 162529280 ( 155. 0 MB)
OldSize = 20971520 ( 20. 0 MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 ( 20. 796875 MB)
CompressedClassSpaceSize = 1073741824 ( 1024. 0 MB)
MaxMetaspaceSize = 17592186044415 MB
G 1 HeapRegionSize = 0 ( 0. 0 MB)
Heap Usage: # 堆内存的使用情况
PS Young Generation #年轻代
Eden Space:
capacity = 123731968 ( 118. 0 MB)
used = 1384736 ( 1. 320587158203125 MB)
free = 122347232 ( 116. 67941284179688 MB)
1. 1191416594941737 % used
From Space:
capacity = 9437184 ( 9. 0 MB)
used = 0 ( 0. 0 MB)
free = 9437184 ( 9. 0 MB)
0. 0 % used
To Space:
capacity = 9437184 ( 9. 0 MB)
used = 0 ( 0. 0 MB)
free = 9437184 ( 9. 0 MB)
0. 0 % used
PS Old Generation #年老代
capacity = 28311552 ( 27. 0 MB)
used = 13698672 ( 13. 064071655273438 MB)
free = 14612880 ( 13. 935928344726562 MB)
48. 38545057508681 % used
13648 interned Strings occupying 1866368 bytes.
4. 2 、查看内存中对象数量及大小
#查看所有对象,包括活跃以及非活跃的
jmap ‐histo <pid> | more
#查看活跃对象
jmap ‐histo:live <pid> | more
[root@node 01 ~]# jmap ‐histo:live 6219 | more
num #instances #bytes class name
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
1 : 37437 7914608 [C
2 : 34916 837984 java.lang.String
3 : 884 654848 [B
4 : 17188 550016 java.util.HashMap$Node
5 : 3674 424968 java.lang.Class
6 : 6322 395512 [Ljava.lang.Object;
7 : 3738 328944 java.lang.reflect.Method
8 : 1028 208048 [Ljava.util.HashMap$Node;
9 : 2247 144264 [I
10 : 4305 137760
java.util.concurrent.ConcurrentHashMap$Node
11 : 1270 109080 [Ljava.lang.String;
12 : 64 84128
[Ljava.util.concurrent.ConcurrentHashMap$Node;
13 : 1714 82272 java.util.HashMap
14 : 3285 70072 [Ljava.lang.Class;
15 : 2888 69312 java.util.ArrayList
16 : 3983 63728 java.lang.Object
17 : 1271 61008
org.apache.tomcat.util.digester.CallMethodRule
18 : 1518 60720 java.util.LinkedHashMap$Entry
19 : 1671 53472
com.sun.org.apache.xerces.internal.xni.QName
20 : 88 50880 [Ljava.util.WeakHashMap$Entry;
21 : 618 49440 java.lang.reflect.Constructor
22 : 1545 49440 java.util.Hashtable$Entry
23 : 1027 41080 java.util.TreeMap$Entry
24 : 846 40608
..............省略........
#对象说明
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
4. 3 、将内存使用情况dump到文件中
有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对它进行分析,jmap也是支持dump到文件中的。
#用法:
jmap ‐dump:format=b,file=dumpFileName <pid>
#示例
jmap ‐dump:format=b,file=/tmp/dump.dat 6219
可以看到已经在/tmp下生成了dump.dat的文件。
4. 4 、通过jhat对dump文件进行分析
在上一小节中,我们将jvm的内存dump到文件中,这个文件是一个二进制的文件,不方便查看,这时我们可以借助于jhat工具进行查看。
#用法:
jhat ‐port <port> <file>
#示例:
[root@node 01 tmp]# jhat ‐port 9999 /tmp/dump.dat
Reading from /tmp/dump.dat...
Dump file created Mon Sep 10 01 : 04 : 21 CST 2018
Snapshot read, resolving...
Resolving 204094 objects...
Chasing references, expect 40
dots........................................
Eliminating duplicate references........................................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.
打开浏览器进行访问:http:// 192. 168. 40. 133 : 9999 /
在最后面有OQL查询功能。
4. 5 、通过MAT工具对dump文件进行分析
4. 5. 1 、MAT工具介绍
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。
官网地址:https://www.eclipse.org/mat/
4. 5. 2 、下载安装
下载地址:https://www.eclipse.org/mat/downloads.php
将下载得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip进行解压:
4. 5. 3 、使用
查看对象以及它的依赖:
查看可能存在内存泄露的分析:
5 、实战:内存溢出的定位与分析
内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。
如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。
首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap与MAT工具进行定位分析。
接下来,我们模拟内存溢出的场景。
5. 1 、模拟内存溢出
编写代码,向List集合中添加 100 万个字符串,每个字符串由 1000 个UUID组成。如果程序能够正常执行,最后打印ok。
package cn.zhangcc.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 < 10000000 ; 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
5. 2 、运行测试
测试结果如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid 5348 .hprof ...
Heap dump file created [ 8137186 bytes in 0. 032 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java: 3332 )
at
java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuil
der.java: 124 )
at
java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java: 448 )
at java.lang.StringBuilder.append(StringBuilder.java: 136 )
at cn.itcast.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java: 14 )
Process finished with exit code 1
可以看到,当发生内存溢出时,会dump文件到java_pid 5348 .hprof。
5. 3 、导入到MAT工具中进行分析
可以看到,有91.03%的内存由Object[]数组占有,所以比较可疑。
分析:这个可疑是正确的,因为已经有超过 90 %的内存都被它占有,这是非常有可能出现内存溢出的。
查看详情:
可以看到集合中存储了大量的uuid字符串。
6 、jstack的使用
有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析呢?
由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。
这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进行快照,并且打印出来:
#用法:jstack <pid>
[root@node 01 bin]# jstack 2203
Full thread dump Java HotSpot(TM) 64 ‐Bit Server VM ( 25. 141 ‐b 15 mixed mode):
"Attach Listener" # 24 daemon prio= 9 os_prio= 0 tid= 0 x 00007 fabb 4001000
nid= 0 x 906 waiting on condition [ 0 x 0000000000000000 ]
java.lang.Thread.State: RUNNABLE
"http‐bio‐ 8080 ‐exec‐ 5 " # 23 daemon prio= 5 os_prio= 0 tid= 0 x 00007 fabb 057 c 000
nid= 0 x 8 e 1 waiting on condition [ 0 x 00007 fabd 05 b 8000 ]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
‐ parking to wait for < 0 x 00000000 f 8508360 > (a
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java: 175 )
at
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awa
it(AbstractQueuedSynchronizer.java: 2039 )
at
java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java: 44
2 )
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java: 104 )
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java: 32 )
at
java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java: 1074 )
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1134 )
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 624 )
at
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java: 61 )
at java.lang.Thread.run(Thread.java: 748 )
"http‐bio‐ 8080 ‐exec‐ 5 " # 23 daemon prio= 5 os_prio= 0 tid= 0 x 00007 fabb 057 c 000
nid= 0 x 8 e 1 waiting on condition [ 0 x 00007 fabd 05 b 8000 ]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
‐ parking to wait for < 0 x 00000000 f 8508360 > (a
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java: 175 )
at
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awa
it(AbstractQueuedSynchronizer.java: 2039 )
at
java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java: 442 )
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java: 104 )
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java: 32 )
at
java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java: 1074 )
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1134 )
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 624 )
at
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java: 61 )
at java.lang.Thread.run(Thread.java: 748 )
..........略........................
6. 1、线程的状态
在Java中线程的状态一共被分成 6 种:
-
初始态(NEW)
- 创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
-
运行态(RUNNABLE),在Java中,运行态包括 就绪态 和 运行态。
-
就绪态
-
该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。
-
所有就绪态的线程存放在就绪队列中。
-
-
运行态
- 获得CPU执行权,正在执行的线程。
- 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
-
-
阻塞态(BLOCKED)
-
当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
-
而在Java中,阻塞态专指请求锁失败时进入的状态。
-
由一个阻塞队列存放所有阻塞态的线程。
-
处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
-
-
等待态(WAITING)
-
当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
-
也有一个等待队列存放所有等待态的线程。
-
线程处于等待态表示它需要等待其他线程的指示才能继续运行。
-
进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
-
-
超时等待态(TIMED_WAITING)
-
当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;
-
它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;
-
进入该状态后释放CPU执行权^ 和^ 占有的资源。
-
与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
-
-
终止态(TERMINATED)
- 线程执行结束后的状态。
6. 2、实战:死锁问题
如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我们可以借助jstack进行分析,下面我们实战下查找死锁的原因。
6. 2. 1 、构造死锁
编写代码,启动 2 个线程,Thread 1 拿到了obj 1 锁,准备去拿obj 2 锁时,obj 2 已经被Thread 2 锁定,所以发送了死锁。
public class TestDeadLock {
private static Object obj 1 = new Object();
private static Object obj 2 = new Object();
public static void main(String[] args) {
new Thread(new Thread 1 ()).start();
new Thread(new Thread 2 ()).start();
}
private static class Thread1 implements Runnable{
@Override
public void run() {
synchronized (obj 1 ){
System.out.println("Thread 1 拿到了 obj 1 的锁!");
try {
// 停顿 2 秒的意义在于,让Thread 2 线程拿到obj 2 的锁
Thread.sleep( 2000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj 2 ){
System.out.println("Thread 1 拿到了 obj 2 的锁!");
}
}
}
private static class Thread 2 implements Runnable{
@Override
public void run() {
synchronized (obj2 ){
System.out.println("Thread 2 拿到了 obj 2 的锁!");
try {
// 停顿 2 秒的意义在于,让Thread 1 线程拿到obj 1 的锁
Thread.sleep( 2000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (obj 1 ){
System.out.println("Thread 2 拿到了 obj 1 的锁!");
}
}
}
}
6. 2. 2 、在linux上运行
[root@node 01 test]# javac TestDeadLock.java
[root@node 01 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$Thread 1 .class
‐rw‐r‐‐r‐‐. 1 root root 1078 9 月 11 10 : 39 TestDeadLock$Thread 2 .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@node 01 test]# java TestDeadLock
Thread 1 拿到了 obj 1 的锁!
Thread 2 拿到了 obj 2 的锁!
#这里发生了死锁,程序一直将等待下去
6. 2. 3 、使用jstack进行分析
[root@node 01 ~]# jstack 3256
Full thread dump Java HotSpot(TM) 64 ‐Bit Server VM ( 25. 141 ‐b 15 mixed mode):
................略..................
"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)
................略..................
Found 1 deadlock.
在输出的信息中,已经看到,发现了1 个死锁,可以清晰的看到:
Thread 2 获取了 < 0x00000000f655dc50 > 的锁,等待获取 < 0 x 00000000f655dc40 >这个锁
Thread 1 获取了 < 0x00000000f655dc40> 的锁,等待获取 <0x00000000f655dc50>这个锁
由此可见发生了死锁。
7 、VisualVM工具的使用
VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如 100 个String对象分别由哪几个对象分配出来的)。
VisualVM使用简单,几乎 0 配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。
-
内存信息
-
线程信息
-
Dump堆(本地进程)
-
Dump线程(本地进程)
-
打开堆Dump。堆Dump可以用jmap来生成。
-
打开线程Dump
-
生成应用快照(包含内存信息、线程信息等等)
-
性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类 对象占用的内存,检查哪些类占用内存多)
-
…
7. 1 、启动
在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。
7. 2、查看本地进程
7. 3 、查看CPU、内存、类、线程运行信息
7. 4 、查看线程详情
也可以点击右上角Dump按钮,将线程的信息导出,其实就是执行的jstack命令。
7. 5 、抽样器
抽样器可以对CPU、内存在一段时间内进行抽样,以供分析。
7. 6 、监控远程的jvm
VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技 术实现。
7. 6. 1 、什么是JMX?
MX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
7. 6. 2 、监控远程的tomcat
想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:
#在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
保存退出,重启tomcat。
7. 6. 2 、监控远程的tomcat
添加远程主机:
连接成功。使用方法和前面就一样了,就可以和监控本地jvm进程一样,监控远程的tomcat进程。