【JVM】压力测试与调优

5 篇文章 6 订阅

黑马JVM

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

  • 运行的应用“卡住了”,日志不输出,程序没有反应
  • 服务器的CPU负载突然升高
  • 在多线程应用下,如何分配线程的数量?
  • ……

因此,我们需要对JVM有更深入的学习,分析解决在生产环境中所遇到的各种棘手的问题。

2 jvm的运行参数

在jvm中有很多的参数可以进行设置,这样可以让jvm在各种环境中都能够高效的运行。绝大部分的参数保持默认即可。

2.1 三种参数类型

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

标准参数

代表在以后的JVM版本中也会保留

 -help
 -version
-X参数(非标准参数)

不稳定,在未来一些版本中可能被改变

 -Xint
 -Xcomp
-XX参数(使用率较高)

也是非标准参数,往往用于JVM调优和debug

 -XX:newSize
 -XX:+UseSerialGC

2.2 标准参数

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

root@ubuntu64:~/test# 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>
              使用指定的图像显示启动屏幕
root@ubuntu64:~/test# java -version

‐showversion参数是表示,先打印版本信息,再执行后面的命令,在调试时非常有用,

通过-D设置系统属性参数(环境变量),然后通过System.getProperty("str")获得参数

public class TestJVM {
    public static void main(String[] args) {
        String str = System.getProperty("str");
        if (null == str) {
            System.out.println("没拿到参数");
        } else {
            System.out.println("拿到参数了"+str);
        }
    }
}

进行编译、测试

#编译
[root@node01 test]# javac TestJVM.java
#测试
[root@node01 test]# java TestJVM
没拿到参数
[root@node01 test]# java ‐Dstr=123 TestJVM
拿到参数了123

D和str是挨着的,通过System.getProperty("str")获取
ouc-13@ouc-13:~/hanfeng$ java -D
Usage: java [-options] class [args...]
           (to execute a class)
   or  java [-options] -jar jarfile [args...]
           (to execute a jar file)
where options include:
    -d32	  use a 32-bit data model if available
    -d64	  use a 64-bit data model if available
    -server	  to select the "server" VM
                  The default VM is server,
                  because you are running on a server-class machine.


    -cp <class search path of directories and zip/jar files>
    -classpath <class search path of directories and zip/jar files>
                  A : separated list of directories, JAR archives,
                  and ZIP archives to search for class files.
    -D<name>=<value> # 这里
                  set a system property 
    -verbose:[class|gc|jni]
                  enable verbose output
    -version      print product version and exit
    -version:<value>
                  Warning: this feature is deprecated and will be removed
                  in a future release.
                  require the specified version to run
    -showversion  print product version and continue
    -jre-restrict-search | -no-jre-restrict-search
                  Warning: this feature is deprecated and will be removed
                  in a future release.
                  include/exclude user private JREs in the version search
    -? -help      print this help message
    -X            print help on non-standard options
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  enable assertions with specified granularity
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  disable assertions with specified granularity
    -esa | -enablesystemassertions
                  enable system assertions
    -dsa | -disablesystemassertions
                  disable system assertions
    -agentlib:<libname>[=<options>]
                  load native agent library <libname>, e.g. -agentlib:hprof
                  see also, -agentlib:jdwp=help and -agentlib:hprof=help
    -agentpath:<pathname>[=<options>]
                  load native agent library by full pathname
    -javaagent:<jarpath>[=<options>]
                  load Java programming language agent, see java.lang.instrument
    -splash:<imagepath>
                  show splash screen with specified image
See http://www.oracle.com/technetwork/java/javase/documentation/index.html for more details.
2.2.2 -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类型

测试:

# `‐showversion`参数是表示,先打印版本信息,再执行后面的命令,在调试时非常有用,
root@ubuntu64:~/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)
没拿到参数
root@ubuntu64:~/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)
没拿到参数
# 为什么一样?
# 由于机器是64位系统,所以不支持client模式

2.3 -X参数

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

root@ubuntu64:~/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 model)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运行速度,通常低10倍或更多。
  • -Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把所有的字节码编译成本地代码,从而带来最大程度的优化。
    • 然而。很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的少,原因是-Xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代码都进行编译的话,对于一些只执行一次的代码就没有意义了。
  • -Xmixed是混合模式(默认):将解释模式与编译模式进行混合使用,由jvm自己决定,这是jvm默认的模式,也是推荐使用的模式。

示例:强制设置运行模式

#强制设置为解释模式
root@ubuntu64:~/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)

没拿到参数

#强制设置为编译模式
root@ubuntu64:~/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)

没拿到参数
2.4 -XX参数

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

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

  • boolean类型

    • 格式:-XX:[+-]
      如:-XX:+DisableExplicitGC表示禁用手动调用gc操作,也就是说调用System.gc()无效
      
  • 非boolean类型

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

用法:

root@ubuntu64:~/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)
2.5 -Xms-Xmx参数

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。可以两个同时设置,不冲突

  • -Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。
  • -Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M.

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

示例:

root@ubuntu64:~/test# java -Xms512m -Xmx2048m TestJVM  
没拿到参数
2.6 查看jvm的运行参数

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

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

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

2.6.1 运行java命令时打印参数-XX:+PrintFlagsFinal

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

root@ubuntu64:~/test# java -XX:+PrintFlagsFinal -version 
[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                               = true                                {C2 product}
     intx AllocateInstancePrefetchLines             = 1                                   {product}
     intx AllocatePrefetchDistance                  = 256                                 {product}
     intx AllocatePrefetchInstr                     = 0                                   {product}
     intx AllocatePrefetchLines                     = 3                                   {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}
-------------略-------------
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)

由上述的信息可以看出,参数有boolean类型和数字类型,值的操作符是=或:=,

  • = 代表默认值
  • := 代表被修改过的值。
2.6.2 查看正在运行的jvm参数jinfo

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

语法:

jinfo -flags 进程ID
jinfo ‐flags <参数名> <进程id>

首先,启动一个tomcat用于测试,来观察下运行的jvm参数。

cd /tmp/
rz 上传
tar ‐xvf apache‐tomcat‐7.0.57.tar.gz
cd apache‐tomcat‐7.0.57
cd bin/
./startup.sh

#http://192.168.101.130:8080/ 进行访问
访问成功:
#查看所有的参数,用法:jinfo ‐flags <进程id>

#通过jps 或者 jps ‐l 查看java进程
root@ubuntu64:~/test# jps
9939 Bootstrap
11723 Jps
root@ubuntu64:~/test# jps -l
9939 org.apache.catalina.startup.Bootstrap
11733 sun.tools.jps.Jps
root@ubuntu64:~/test# jinfo -flags 9939
Attaching to process ID 9939, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=33554432  # 这里
-XX:+ManagementServer -XX:MaxHeapSize=520093696  #这里
-XX:MaxNewSize=173342720 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=11141120 -XX:OldSize=22413312 #这里
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps 
Command line:  -Djava.util.logging.config.file=/root/test/apache-tomcat-7.0.57/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.101.130 -Djava.endorsed.dirs=/root/test/apache-tomcat-7.0.57/endorsed -Dcatalina.base=/root/test/apache-tomcat-7.0.57 -Dcatalina.home=/root/test/apache-tomcat-7.0.57 -Djava.io.tmpdir=/root/test/apache-tomcat-7.0.57/temp

#查看某一参数的值,用法:jinfo ‐flag <参数名> <进程id>
root@ubuntu64:~/test# jinfo -flag MaxHeapSize 9939
-XX:MaxHeapSize=520093696

不成功的话输入echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

这是因为新版的Linux系统加入了 ptrace-scope 机制. 这种机制为了防止用户访问当前正在运行的进程的内存和状态, 而一些调试软件本身就是利用 ptrace 来进行获取某进程的内存状态的(包括GDB),所以在新版本的Linux系统, 默认情况下不允许再访问了. 可以临时开启. 如:

echo 0 > /proc/sys/kernel/yama/ptrace_scope

永久写到文件来持久化:

emacs /etc/sysctl.d/10-ptrace.conf
添加或修改为以下这一句:(0:允许, 1:不允许)kernel.yama.ptrace_scope = 0 

3 jvm的内存模型

3.1 jdk1.7的堆内存模型

img

Perm永久区

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

Virtual区:

​ 最大内存和初始内存的差值,就是Virtual区。

3.2 jdk1.8的堆内存模型

img

在jdk1.8中变化最大的是Perm区,用Metaspace(元数据空间)进行了替换。

需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地方法中(逻辑在本地方法,但实际在堆),这也是JDK8与jdk1.7的永久代最大的区别所在

img

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@ubuntu64:~/test# jps
9939 Bootstrap
11766 Jps

root@ubuntu64:~/test# jstat -class 9939
Loaded  Bytes  Unloaded  Bytes     Time   
2843    5562.0     28    39.6       8.16
说明:

Loaded:加载class的数量
Bytes:所占空间大小
Unloaded:未加载数量
Bytes:未加载占用空间
Time:时间
3.4.2 查看编译统计
root@ubuntu64:~/test# jstat -compiler 9939
Compiled Failed Invalid   Time   FailedType FailedMethod
    3083      1       0     9.89          1 org/apache/tomcat/util/IntrospectionUtils setProperty


说明:

Compiled :编译数量。
Failed:失败数量
Invalid:不可用数量
Time:时间
FailedType:失败类型
FailedType:失败类型
FailedMethod:失败的方法
3.4.2 垃圾回收统计
root@ubuntu64:~/test# jstat -gc 9939
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1088.0 1088.0  0.0   1088.0  8832.0   7367.8   21888.0    12523.7   19456.0 18703.0 2304.0 2034.2   2019    7.188  11      0.496    7.684

#也可以指定打印的间隔和次数,每1秒中打印一次,共打印5次
root@ubuntu64:~/test# jstat -gc 9939 1000 5
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1088.0 1088.0  0.0   1088.0  8832.0   7505.3   21888.0    12523.7   19456.0 18703.0 2304.0 2034.2   2019    7.188  11      0.496    7.684
1088.0 1088.0  0.0   1088.0  8832.0   7505.3   21888.0    12523.7   19456.0 18703.0 2304.0 2034.2   2019    7.188  11      0.496    7.684
1088.0 1088.0  0.0   1088.0  8832.0   7505.3   21888.0    12523.7   19456.0 18703.0 2304.0 2034.2   2019    7.188  11      0.496    7.684
1088.0 1088.0  0.0   1088.0  8832.0   7505.3   21888.0    12523.7   19456.0 18703.0 2304.0 2034.2   2019    7.188  11      0.496    7.684
1088.0 1088.0  0.0   1088.0  8832.0   7505.3   21888.0    12523.7   19456.0 18703.0 2304.0 2034.2   2019    7.188  11      0.496    7.684

说明:

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:垃圾回收消耗总时间

4 jmap命令查看堆内存

前面通过jstat可以对jvm堆的内存进行统计,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总、对内存溢出的定位与分析。

jps
jmap -heap 进程ID
4.1 jmap -heap 进程ID查看内存使用情况
root@ubuntu64:~/test# jmap -heap 9939
Attaching to process ID 9939, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.141-b15

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration: #堆内存配置信息
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 520093696 (496.0MB)
   NewSize                  = 11141120 (10.625MB)
   MaxNewSize               = 173342720 (165.3125MB)
   OldSize                  = 22413312 (21.375MB)
   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 = 10158080 (9.6875MB)
   used     = 9221560 (8.794364929199219MB)
   free     = 936520 (0.8931350708007812MB)
   90.7805412046371% used
Eden Space:
   capacity = 9043968 (8.625MB)
   used     = 8107456 (7.73187255859375MB)
   free     = 936512 (0.89312744140625MB)
   89.64489923007247% used
From Space:
   capacity = 1114112 (1.0625MB)
   used     = 1114104 (1.0624923706054688MB)
   free     = 8 (7.62939453125E-6MB)
   99.99928193933823% used
To Space:
   capacity = 1114112 (1.0625MB)
   used     = 0 (0.0MB)
   free     = 1114112 (1.0625MB)
   0.0% used
tenured generation: #年老代
   capacity = 22413312 (21.375MB)
   used     = 12824312 (12.230216979980469MB)
   free     = 9589000 (9.144783020019531MB)
   57.21738938002559% used

13973 interned Strings occupying 1903320 bytes.
4.2 jmap ‐histo <pid> | more查看内存中对象数量及大小
#查看所有对象,包括活跃以及非活跃的  # history
jmap ‐histo <pid> | more
# 因为对象很多,所以可以使用管道符

#查看活跃对象 
jmap ‐histo:live <pid> | more

root@ubuntu64:~/test# jmap -histo:live 11927 | more


 num     #instances         #bytes  class name
----------------------------------------------
   1:         39526        7919536  [C
   2:          3402        1497192  [I
   3:          1383        1328576  [B
   4:         37052         889248  java.lang.String
   5:         18904         604928  java.util.HashMap$Node
   6:          4019         464664  java.lang.Class
   7:          4902         431376  java.lang.reflect.Method
   8:          6295         387024  [Ljava.lang.Object;
   9:          1343         231008  [Ljava.util.HashMap$Node;
  10:          4653         148896  java.util.concurrent.ConcurrentHashMap$Node
  11:          1426         114248  [Ljava.lang.String;
  12:          4783         104576  [Ljava.lang.Class;
  13:          2039          97872  java.util.HashMap
  14:            79          86672  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  15:          2096          83840  java.util.LinkedHashMap$Entry
  16:          2815          67560  java.util.ArrayList
  17:          4049          64784  java.lang.Object
  18:          1271          61008  org.apache.tomcat.util.digester.CallMethodRule
  19:           760          60800  java.lang.reflect.Constructor
  20:           141          53584  [Ljava.util.WeakHashMap$Entry;
  21:          1671          53472  com.sun.org.apache.xerces.internal.xni.QName
  22:          1624          51968  java.util.Hashtable$Entry

--------------------------略---------------------------
#对象说明
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象
4.3 jmap ‐dump:format=b,file=dumpFileName名字 <pid进程号>将内存使用情况dump到文件中

有些时候我们需要将当前内存中的情况dump到文件中,然后对它进行分析,jmap也是支持dump到文件中的。

#用法:
jmap ‐dump:format=b,file=dumpFileName名字 <pid进程号>

#示例
jmap ‐dump:format=b,file=/tmp/dump.dat 11927

已经在/tmp下生成了dump.dat的文件jvisualvm生成

也可以使用

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

在上一小节中,我们将jvm的内存dump到文件中,这个文件是一个二进制的文件,不方便查看,这时我们可以借助jhat工具进行查看。

#用法:
jhat ‐port <port> <file>
#示例:

root@ubuntu64:/tmp# jhat -port 9999 /tmp/dump.dat 
Reading from /tmp/dump.dat...
Dump file created Fri Aug 23 04:20:31 EDT 2019
Snapshot read, resolving...
Resolving 234190 objects...
Chasing references, expect 46 dots..............................................
Eliminating duplicate references..............................................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.

打开浏览器进行访问:http://192.168.101.130:9999

img

在最后面有OQL查询功能。

img

select s from java.lang.String s where s.value.length>=100
4.5 通过MAT工具对dump文件进行分析

4.5.1 MAT工具介绍
MAT(Memery Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。可以看GC ROOTS

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

img

将下载得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip进行解压:

img

4.5.3 使用

可以在shell中先使用sz 文件名命令把文件从远程下载下来

img

这里注意点击下面AllFiles才能找到

img

img

点击Overview,点击下方Actions/HistoGram,显示类的数量等

img

点击Dominator_tree,查看对象以及它的依赖:

img

再点击上方的default_report可以查看哪里错了,我们通过第5章分析具体。

查看可能存在内存泄露的分析:

img

5 内存溢出的定位与分析

内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。

如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。

首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap和MAT工具进行定位分析。

接下来,我们模拟内存溢出的场景。

5.1 模拟内存溢出

编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。

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

public class TestJvmOutOfMemory {
    //实现,向集合中添加100万个字符串,每个字符串由1000个UUID组成

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            String str = "";
            for (int j = 0; j < 1000; j++) {
                str += UUID.randomUUID().toString();//不断new String添加到list
            }
            list.add(str);
        }
        System.out.println("ok");
    }

}

为了演示效果,我们将设置执行的参数,这里使用的是IDEA编辑器

img

在这里我们添加VM参数让他输出错误信息

#参数如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError 

5.2 运行测试
测试结果如下:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid15228.hprof ...
Heap dump file created [8298009 bytes in 0.024 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 cn.没拿到参数.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:20)

Process finished with exit code 1

可以看到,当发生内存溢出时,会dump文件到java_pid15228.hprof

img

5.3 导入.hprof文件到MAT工具中进行分析

img

可以看到,有91.03%的内存由Object[]数组占有,所以比较可疑。

分析:这个可疑是正确的,因为已经有超过90%的内存都被它占有,这是非常有可能出现内存溢出的。

查看详情:可以看到最后一列91%被一个List占据

img

可以看到集合中存储了大量的uuid字符串。

6 jstack的使用

有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁死循环等,我们该如何分析呢?

由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。

这个时候,就需要借助于jstack命令,jstack的作用是将正在运行的jvm的线程情况进行快照,并且打印出来:

#用法:jstack <pid>

root@ubuntu64:/tmp# jstack 11927
2019-08-28 03:45:11
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):

"http-bio-8080-exec-9" #31 daemon prio=5 os_prio=0 tid=0x00007f357c416000 nid=0x2f3f waiting on condition [0x00007f3548af8000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ec0c7ac8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)

----省略...

"VM Periodic Task Thread" os_prio=0 tid=0x00007f357c1c9800 nid=0x2ea3 waiting on condition 

JNI global references: 233
6.1 线程的状态

img

阻塞与等待的区别:阻塞是获取不到锁,等待是等待其他线程通知他执行。

  • 等待态(WAITING):当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
    也有一个等待队列存放所有等待态的线程。
    线程处于等待态表示它需要等待其他线程的指示才能继续运行。
    进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
  • 超时等待态(TIMED_WAITING):当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUnit时,就会进入该状态。
    它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒。
    进入该状态后释放CPU执行权和占有的资源。(错了吧,不会释放吧?)
    与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
6.2 死锁问题

如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我们可以借助jstack进行分析,下面我们实战下查找死锁的原因。

6.2.1 构造死锁

编写代码,启动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的锁!");
                }
            }
        }
    }
}
6.2.2、在linux上运行
root@ubuntu64:/test# javac TestDeadLock.java 
root@ubuntu64:/test# ll
total 28
drwxr-xr-x  2 root root 4096 8月  26 02:44 ./
drwxr-xr-x 26 root root 4096 8月  23 04:06 ../
-rw-r--r--  1 root root  184 8月  28 03:51 TestDeadLock$1.class
-rw-r--r--  1 root root  843 8月  28 03:51 TestDeadLock.class
-rw-r--r--  1 root root 1516 8月  26 02:43 TestDeadLock.java
-rw-r--r--  1 root root 1076 8月  28 03:51 TestDeadLock$Thread1.class
-rw-r--r--  1 root root 1076 8月  28 03:51 TestDeadLock$Thread2.class
root@ubuntu64:/test# java TestDeadLock 
Thread1 拿到了 obj1的锁!
Thread2 拿到了obj2 的锁!

#这里发生了死锁,程序一直将等待下去
6.2.3 使用jstack进行分析jstack 进程号
root@ubuntu64:~/test/apache-tomcat-7.0.57/bin# jps
12183 Jps
11927 Bootstrap
12171 TestDeadLock
root@ubuntu64:~/test/apache-tomcat-7.0.57/bin# jstack 12171
2019-08-28 03:54:46
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=0x00007ffb28001000 nid=0x2fab waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

...省略


Found one Java-level deadlock:
=============================

"Thread-1":
  waiting to lock monitor 0x00007ffb300062c8 (object 0x00000000e1089b38, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0": # 获取不到锁2
  waiting to lock monitor 0x00007ffb30004e28 (object 0x00000000e1089b48, a java.lang.Object),
  which is held by "Thread-1" # 获取不到锁1

Java stack information for the threads listed above:
===================================================

"Thread-1":
        at TestDeadLock$Thread2.run(TestDeadLock.java:48)
        - waiting to lock <0x00000000e1089b38> (a java.lang.Object) # 获取不到锁2【38】
        - locked <0x00000000e1089b48> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748) # 抱着锁1【48】
"Thread-0":
        at TestDeadLock$Thread1.run(TestDeadLock.java:27)
        - waiting to lock <0x00000000e1089b48> (a java.lang.Object) # 获取不到锁1【48】
        - locked <0x00000000e1089b38> (a java.lang.Object)# 抱着锁2【38】
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock. # 发现了一个死锁

 在输出的信息中,已经看到,发现了1个死锁

可以清晰的看到:

  • Thread2获取了 <0x00000000e1089b48> 的锁,等待获取 <0x00000000e1089b38>这个锁
  • Thread1获取了 <0x00000000e1089b38> 的锁,等待获取 <0x00000000e1089b48>这个锁

由此可见,发生了死锁。

7 JVisualVM的使用

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

VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其他JDK自带命令的所有功能。

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

7.1 JVisualVM介绍

在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。(命令行直接输入jvisualvm

img

可以看到正在运行的进程

7.2 查看本地进程

img

7.3 查看CPU、内存、类、线程运行信息

img

7.4 查看线程详情

img

也可以点击右上角Dump按钮,将线程的信息导出,其实就是执行的jstack命令。

img

发现,显示的内容是一样的。

7.5 抽样器

抽样器可以对CPU、内存在一段时间内进行抽样,以供分析。(点击CPU或者内存,过几秒再点击停止,就可以看到这段时间的占用情况)

img

7.6 监控远程的jvm

VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术实现。

7.6.1 什么是JMX?

JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系 统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。

7.6.2 监控远程的tomcat

想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:

#在tomcat的bin目录下,修改catalina.sh

1、在catalina.sh中添加配置如下:

在其中“# ----- Execute The Requested Command -------------”之前插入一行(中间没有换行):

CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=机器IP"

#这几个参数的意思是:
#‐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

特别注意:-Djava.rmi.server.hostname=机器IP这一行需要加上,不然可能本机能连上,远程连不上。很多帖子中的介绍配置没有这一行。


2、关闭防火墙 sudo ufw disable

3、lsof -i:8999查看端口状态

img

7.6.3 使用JVisualVM连接远程tomcat

添加远程主机:

img

在一个主机下可能会有很多的jvm需要监控,所以接下来要在该主机上添加需要监控的jvm:

img

img

连接成功。使用方法和前面就一样了,就可以和监控本地jvm进程一样,监控远程的 tomcat进程。

不过远程的堆dump需要先下载到本地,然后再打开

jvisualVM查看内存溢出

OutOfMemoryError:由于内存空间不足,导致虚拟机无法为对象分配内存,垃圾回收期也不能进行回收,抛出错误。OutOfMemoryError继承了VirtualMachineError

相当于刚才的MAT工具

/**
修改文件运行的jvm参数:
-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError
打印堆转储的信息
*/
public class Test1{
    public static void main(String[] args){
        List<Test1> list=new ArrayList();
        for(;;) {
            list.add(new Test1());
            //System.gc();  //主动建议垃圾回收器进行垃圾回收
        }
    }
}
运行结果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid16280.hprof ... 
Heap dump file created [9178675 bytes in 0.152 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
       at java.util.Arrays.copyOf(<u>Arrays.java:3210</u>)
       at java.util.Arrays.copyOf(<u>Arrays.java:3181</u>)
       at java.util.ArrayList.grow(<u>ArrayList.java:265</u>)
       at java.util.ArrayList.ensureExplicitCapacity(<u>ArrayList.java:239</u>)
       at java.util.ArrayList.ensureCapacityInternal(<u>ArrayList.java:231</u>)
       at java.util.ArrayList.add(<u>ArrayList.java:462</u>)
       at com.hisense.Test1.main(<u>Test1.java:11</u>)
       java_pid51776.hprof是一个转储文件

在windows终端中输入jvisualvm,直接打开软件, java_pid16280.hprof转储文件(在项目根目录下),装入时候需要选择格式堆dump。类似下图。

在这里插入图片描述

  基本信息:
    生成的日期: Wed Apr 01 20:19:16 CST 2020
    文件: F:\JVMtest\java_pid16280.hprof
    文件大小: 8.9 MB
    字节总数: 6,422,862
    类总数: 582
    实例总数: 248,038
    类加载器: 2

    在出现 OutOfMemoryError 异常错误时进行了堆转储
    导致 OutOfMemoryError 异常错误的线程: main//这里表明了导致超存的线程是main线程

  环境:...
  系统属性:...
  堆转储上的线程:
  
"Finalizer" daemon prio=8 tid=3 WAITING
    at java.lang.Object.wait(Native Method)
...

  
"main" prio=5 tid=1 RUNNABLE
    at java.lang.OutOfMemoryError.<init>(Out
...
       Local Variable: JVMtest.OutOfMemory#64664
    at JVMtest.OutOfMemory.main(OutOfMemory.java:10)
       Local Variable: java.util.ArrayList#3

  
"Signal Dispatcher" daemon prio=9 tid=4 RUNNABLE

下图可以看到Test1类中有97%的对象,这个类中可能出现了内存溢出

在这里插入图片描述

在这里插入图片描述

修改设置的VM内存大小,然后运行程序,可以在VisualVM中查看进程情况。

jvisualVM栈溢出

虚拟机栈:

/**
 * 模拟虚拟机栈溢出,StackOverFlow//递归
 * 设置虚拟机栈的大小,-Xss100k //StackSize//最少160k
 */
public class Test2 {
    private int length;
    public int getLength() {
        return length;
    }
    public void test() {
        length+=1;
        try{
            test();//递归调用
            Thread.sleep(300);
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Test2 t2=new Test2();
        try {
            t2.test();
        }catch(Throwable ex) {
            System.out.println(t2.getLength());
            ex.printStackTrace();
        }
    }
}
//要想在jvisualVM中看到状态,需要改得大一些,让他运行时间长一些。点击进程后,点击监视
运行结果:
    987

    java.lang.StackOverflowError // 显示栈溢出
    at com.hisense.Test2.test(<u>Test2.java:13</u>)
    at com.hisense.Test2.test(<u>Test2.java:14</u>)
    at com.hisense.Test2.test(<u>Test2.java:14</u>)
    at com.hisense.Test2.test(<u>Test2.java:14</u>)
    ................

使用jvisualvm工具dump主线程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

线程死锁检测:

package JVMtest;
// 演示死锁
// 下面有2个线程,1,2
// 有2个资源类A,B,他们都有一个静态方法method,method方法同时被synchronized修饰。而且互相调用了另外一个类的method
// 线程1先调用A.method();,线程2先调用B.method();
// 如果线程1拿到了A.class对象,那么可以执行A.method(),他里面又调用了

public class DeadThread{
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                A.method();
            }
        },"A-Thread").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                B.method();
            }
        },"B-Thread").start();
        //Thread.sleep(40000);
    }//为什么在jconsole中看不到main进程:因为执行完两个线程后,main就结束了,是那两个线程还在执行而已
}

class A {
    public static synchronized void method(){
        try {
            Thread.sleep(1000);//等待一会另外一个线程也拿到锁了
        } catch (InterruptedException e) {e.printStackTrace();}
        B.method();
    }
}

class B {
    public static synchronized void method(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {e.printStackTrace();}
        A.method();
    }
}

再使用jvisualvm工具查看线程:

jvisualVM直接会提示:检测到死锁!生成一个线程 Dump 以获取更多信息。
点击右侧线程dump,可以看到如下内容://与jconsole输出内容相似
"B-Thread" #13 prio=5 os_prio=0 tid=0x000000001f12c000 nid=0x430c waiting for monitor entry [0x00000000200ee000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at JVMtest.A.method(DeadThread.java:23)
        - waiting to lock <0x000000076bf16508> (a java.lang.Class for JVMtest.A)
        at JVMtest.B.method(DeadThread.java:37)
        - locked <0x000000076c1a09c8> (a java.lang.Class for JVMtest.B)
        at JVMtest.DeadThread$2.run(DeadThread.java:14)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None
-----------------
"A-Thread" #12 prio=5 os_prio=0 tid=0x000000001f129000 nid=0x3018 waiting for monitor entry [0x000000001ffef000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at JVMtest.B.method(DeadThread.java:33)
        - waiting to lock <0x000000076c1a09c8> (a java.lang.Class for JVMtest.B)
        at JVMtest.A.method(DeadThread.java:27)
        - locked <0x000000076bf16508> (a java.lang.Class for JVMtest.A)
        at JVMtest.DeadThread$1.run(DeadThread.java:8)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

----------------------------
Found one Java-level deadlock:
=============================
"B-Thread":
  waiting to lock monitor 0x000000001d1c2188 (object 0x000000076bf16508, a java.lang.Class),
  which is held by "A-Thread"
"A-Thread":
  waiting to lock monitor 0x000000001d1c0b88 (object 0x000000076c1a09c8, a java.lang.Class),
  which is held by "B-Thread"

Java stack information for the threads listed above:
===================================================
"B-Thread":
        at JVMtest.A.method(DeadThread.java:23)
        - waiting to lock <0x000000076bf16508> (a java.lang.Class for JVMtest.A)
        at JVMtest.B.method(DeadThread.java:37)
        - locked <0x000000076c1a09c8> (a java.lang.Class for JVMtest.B)
        at JVMtest.DeadThread$2.run(DeadThread.java:14)
        at java.lang.Thread.run(Thread.java:748)
"A-Thread":
        at JVMtest.B.method(DeadThread.java:33)
        - waiting to lock <0x000000076c1a09c8> (a java.lang.Class for JVMtest.B)
        at JVMtest.A.method(DeadThread.java:27)
        - locked <0x000000076bf16508> (a java.lang.Class for JVMtest.A)
        at JVMtest.DeadThread$1.run(DeadThread.java:8)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock. // 发现了死锁

jconsole也可以查看线程使用情况,可以jdk内置的。他是分进程查看的。

使用jconsole工具查看死锁线程:线程/点击指定的线程,可以查看总组阻止数(阻止了多少别人的线程),等待数(等待别人的线程)。还会看到A-Thread持有锁,所以B线程阻塞。点击下面的检测死锁还可以直接检测出来死锁

jconsole输出内容:线程
-------------------------------------------
名称: A-Thread
状态: java.lang.Class@2b12cf6上的BLOCKED, 拥有者: B-Thread//2b这个类阻塞了,他的持有者是B线程
总阻止数: 1, 总等待数: 1

堆栈跟踪: 
JVMtest.B.method(DeadThread.java:33)
JVMtest.A.method(DeadThread.java:26)
   - 已锁定 java.lang.Class@41fc54c4//A线程持有了41class,B线程拿不到
JVMtest.DeadThread$1.run(DeadThread.java:8)
java.lang.Thread.run(Thread.java:748)

--------------------------------------------
名称: B-Thread
状态: java.lang.Class@41fc54c4上的BLOCKED, 拥有者: A-Thread
总阻止数: 1, 总等待数: 1

堆栈跟踪: 
JVMtest.A.method(DeadThread.java:24)
JVMtest.B.method(DeadThread.java:35)
   - 已锁定 java.lang.Class@2b12cf6
JVMtest.DeadThread$2.run(DeadThread.java:14)
java.lang.Thread.run(Thread.java:748)

元空间深度解析:

/**
 * 方法区产生内存溢出错误
 * :jdk8中引入元空间,默认的初始大小为21m,如果超过21m,元空间虚拟机会进行垃圾回收,如果还不够就进行空间扩容,扩容的上限为物理内存的上限。
 * 本次测试使用cglib进行元空间内存错误演示。
 * 启动时,修改vm参数: -XX:MaxMetaspaceSize=10m。
 */
public class Test4 {
       public static void main(String[] args) {
             //程序在运行过程中,会不断的创建Test.class的子类,并放置到元空间中
             for(;;) {
                    Enhancer enhancer=new Enhancer();
                    enhancer.setSuperclass(Test4.class);
                    enhancer.setUseCache(false);
                    enhancer.setCallback((MethodInterceptor)(obj,method,arg1,proxy)-> 
                                        proxy.invokeSuper(obj, arg1));
                    System.out.println("hello world");
                    enhancer.create();
             }
       }
}

程序运行结果报错:
java.lang.OutOfMemoryError: Metaspace     元空间内存溢出

使用jvisualvm工具观察元空间的变化

元空间相关文章:
Java永久代去哪了?https://www.infoq.cn/article/Java-PERMGEN-Removed/
-XX:MaxMetaspaceSize
‑XX:MinMetaspaceFreeRatio
‑XX:MaxMetaspaceFreeRatio

参考
  • https://blog.csdn.net/qq_37977218/article/details/100015785

原来内容

JDK自带的工具:

  • jconsole
  • jvisualvm

jmap -clstats PID :打印【类加载器】数据

> jmap -clstats 13548
Attaching to process ID 13548, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.231-b11
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness........done.
class_loader    classes bytes   parent_loader   alive?  type

<bootstrap>     606     1135618   null          live    <internal>
0x000000076ba901b0      0       0         null          live    sun/misc/Launcher$ExtClassLoader@0x00000007c000fd00
0x000000076baa4740      8       8615    0x000000076ba901b0      live    sun/misc/Launcher$AppClassLoader@0x00000007c000f958
0x000000076c3710a8      0       0       0x000000076baa4740      live    java/util/ResourceBundle$RBClassLoader@0x00000007c00650a8

total = 4       614     1144233     N/A         alive=4, dead=0     N/A

jmap -heap PID 打印堆空间数据

C:\Users\HAN>jmap -heap 13548
Attaching to process ID 13548, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.231-b11

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MaxHeapSize              = 4246732800 (4050.0MB)
   OldSize                  = 177733632 (169.5MB)
   MetaspaceSize            = 21807104 (20.796875MB)
   ...

Heap Usage:...
PS Young Generation
Eden Space:
   capacity = 66584576 (63.5MB)
   used     = 10654120 (10.160560607910156MB)
   free     = 55930456 (53.339439392089844MB)
   16.00088284710261% used
From Space:...
To Space:...
PS Old Generation...

3164 interned Strings occupying 259664 bytes.

jstat -gc LVMID 用来打印元空间的信息

C:\Users\HAN>jstat -gc 13548
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
10752.0 10752.0  0.0    0.0   65024.0  10404.4   173568.0     0.0     4480.0 776.7  384.0   76.6       0    0.000   0      0.000    0.000

其中:MC: Current Metaspace Capacity (KB); MU: Metaspace Utilization (KB)
如果使用cgilib不断在运行期生成class,则MC和MU会不断的增加,如下图:

在这里插入图片描述

jps :查看 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息

C:\Users\HAN>jps
1384
13548 DeadThread
7148 Launcher
7340 Jps

C:\Users\HAN>jps -l
1384
2360 sun.tools.jps.Jps
13548 JVMtest.DeadThread
7148 org.jetbrains.jps.cmdline.Launcher

jcmd PID GC.class_stats 从jdk1.7出现的新命令,用来连接到运行的JVM并输出详尽的类元数据的柱状图。

jcmd -l

C:\Users\HAN>jcmd -l
1328 sun.tools.jcmd.JCmd -l
1384
13548 JVMtest.DeadThread
7148 org.jetbrains.jps.cmdline.Launcher
....

jcmd pid VM.flags :查看虚拟机启动参数

C:\Users\HAN>jcmd 13548 VM.flags
13548:
-XX:CICompilerCount=4 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4246732800 -XX:MaxNewSize=1415577600 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC

jcmd pid help 列出当前的java进程可以进行的操作

jcmd pid help JFR.dump :查看具体命令的选项.

C:\Users\HAN>jcmd 13548 help
13548:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
...

For more information about a specific command use 'help <command>'.

jcmd pid ProfCounter.print : 查看当前进程性能相关的参数

jcmd pid VM.uptime : 查看jvm的启动时长

jcmd pid GC.class_histogram :查看系统中类的统计信息

jcmd pid Thread.print : 查看线程堆栈信息

jcmd pid GC.heap_dump C:\Users\Administrator\Desktop 导出heap dump文件,可以使用jvisualvm查看

jcmd pid VM.system_properties : 查看JVM的属性信息

jcmd pid VM.version : 查看目标JVM进程的版本信息

jcmd pid VM.command_line : 查看JVM启动的命令行参数信息

jstack : 查看或者导出java应用程序中线程的堆栈信息

另外一个图形化界面工具 jmc(Java Mission Control)

JMC

JMC中有一个Java飞行记录器(JFR):Java Flight Recorder(其中MBean与飞行记录器很有用)

在这里插入图片描述

在这里插入图片描述

jhat:利用web打开转储文件

  • 11
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值