天天看点

jvm优化-a第一节

第一节

1 为什么要对jvm做优化

本地开发环境中很少会对jvm优化:
运行应用“卡住了”,日志不输出,程序没有反应
服务器CPU负载突然变高
在多线程应用下如何分配线程数量
...
           

2 jvm的运行参数

jvm中有很多参数可以进行设置,绝大多参数保持默认设置即可。
           

2.1 三种参数类型

jvm的参数分为三种类型,分别是
· 标准参数
	-help
	-version
· -X参数(非标准参数)
	-Xint
	-Xcomp
· -XX参数(使用频率较高)
	-XX:newSize
	-XX:+UserSerialGC
           

2.2 标准参数

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

    [[email protected] ~]# java -help

    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.

    -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.1 实战

实战1 查看jvm版本

[[email protected] ~]# java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

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

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

public class ATestJVM {
	
	    public static void main(String[] args) {
	        String str = System.getProperty("str");
	        if(str == null){
	            System.out.println("lifeng");
	        }else{
	            System.out.println(str);
	        }
	
	        System.gc();
	    }
	}
           

进行编译、测试

[[email protected] jvm]# javac ATestJVM.java
[[email protected] jvm]# 
[[email protected] jvm]# java ATestJVM
lifeng
[[email protected] jvm]# java -Dstr=12345 ATestJVM
12345
           

2.2.2 -server与-client参数

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

  • 他们的区别是Server VM初始堆空间会大一些,默认使用的是并行垃圾回收器,启动慢运行快。
  • Client VM相对来讲会保守一些,初始堆空间会小一些,使用穿行的垃圾回收器,他的目标是为了让JVM的启动速度更快,但运行速度会比Server VM模式慢些。
  • JVM在启动的时候会根据硬件系统自动选择使用Server还是Client类型的VM
  • 32位操作系统
    • 如果是Windows系统,不论硬件配置如何而,都默认使用Client类型的JVM
    • 如果是其他操作系统,机器配置有2GB以上的内存,同时有两个以上CPU的话默认使用server模式,否则使用client模式
  • 64 位操作系统
    • 只有server类型,不支持client类型

测试:

[[email protected] jvm]# java -client -showversion ATestJVM

java version “1.8.0_60”

Java™ SE Runtime Environment (build 1.8.0_60-b27)

Java HotSpot™ 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng
[[email protected] jvm]# 
[[email protected] jvm]# java -server -showversion ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng

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

2.3 -X参数

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

[[email protected] jvm]# java -X
    -Xmixed           mixed mode execution (default)
    -Xint             interpreted mode execution only  (仅解释模式执行)
    -Xbootclasspath:<directories and zip/jar files separated by :>
                      set search path for bootstrap classes and resources
    -Xbootclasspath/a:<directories and zip/jar files separated by :>
                      append to end of bootstrap class path
    -Xbootclasspath/p:<directories and zip/jar files separated by :>
                      prepend in front of bootstrap class path
    -Xdiag            show additional diagnostic messages
    -Xnoclassgc       disable class garbage collection
    -Xincgc           enable incremental garbage collection
    -Xloggc:<file>    log GC status to a file with time stamps
    -Xbatch           disable background compilation
    -Xms<size>        set initial Java heap size
    -Xmx<size>        set maximum Java heap size
    -Xss<size>        set java thread stack size
    -Xprof            output cpu profiling data
    -Xfuture          enable strictest checks, anticipating future default
    -Xrs              reduce use of OS signals by Java/VM (see documentation)
    -Xcheck:jni       perform additional checks for JNI functions
    -Xshare:off       do not attempt to use shared class data
    -Xshare:auto      use shared class data if possible (default)
    -Xshare:on        require using shared class data, otherwise fail.
    -XshowSettings    show all settings and continue
    -XshowSettings:all
                      show all settings and continue
    -XshowSettings:vm show all vm related settings and continue
    -XshowSettings:properties
                      show all property settings and continue
    -XshowSettings:locale
                      show all locale related settings and continue

The -X options are non-standard and subject to change without notice.
           

2.3.1 -Xint、-Xcomp、-Xmixed

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

示例:强制设置为解释模式

[[email protected] jvm]# java -showversion -Xint ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, interpreted mode)

lifeng
           

示例:强制设置为编译模式

[[email protected] jvm]# java -showversion -Xcomp ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, compiled mode)

lifeng

#在编译模式下,第一次执行会比解释模式慢一些。
           

示例:默认的混合模式

[[email protected] jvm]# java -showversion ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng
           

2.4 -XX参数

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

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

  • boolean类型
    • 格式:-XX:[±] 表示启用或禁用属性
    • 如: -XX:+DisableExplicitGC表示禁用手动调用gc操作,也就是说调用System.gc()无效
  • 非bollean类型
    • 格式:-XX:=表示的属性值为
    • 如:-XX:NewRatio=1 表示新生代和老年代的比值

用法:

[[email protected] jvm]# java -showversion -XX:+DisableExplicitGC ATestJVM 
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

lifeng
           

2.5 -Xms与-Xmx参数

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小
-Xmx2048: 等价于-XX:MAXHeapSize,设置JVM最大堆内存为2048M
-Xms512:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M
适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快。
           

示例:

[[email protected] jvm]# java -Xms512 -Xmx1024 ATestJVM 
Error occurred during initialization of VM
Too small initial heap
[[email protected] jvm]# java -Xms512m -Xmx1024m ATestJVM   
lifeng
[[email protected] jvm]# 
           

2.6 查看jvm的运行参数

有些时候需要查看jvm的运行参数,
第一,运行java命令时候打印出运行参数
第二,查看正在运行的java进程的参数。
           

2.6.1 运行java命令时打印参数

添加 -XX:+PrintFlagsFinal参数即可。
	java -XX:+PrintFlagsFinal -version
# 可以看出参数有boolean类型和数字类型,值的操作符是=或者:=分别表示默认值和被修改的值。
           

2.6.2 查看正在运行的jvm参数

需要借助于jinfo命令查看
启动一个tomcat,测试,用来观察jvm参数:
启动tomcat,访问8080端口。

# 查看所有的参数
jinfo -flags <进程id>
jinfo -flags 28142

[[email protected] apache-tomcat-8.5.34]# jinfo -flags 28142
Attaching to process ID 28142, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.60-b23
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=29360128 -XX:MaxHeapSize=459276288 -XX:MaxNewSize=153092096 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=9764864 -XX:OldSize=19595264 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
Command line:  -Djava.util.logging.config.file=/export/servers/apache-tomcat-8.5.34/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -Dcatalina.base=/export/servers/apache-tomcat-8.5.34 -Dcatalina.home=/export/servers/apache-tomcat-8.5.34 -Djava.io.tmpdir=/export/servers/apache-tomcat-8.5.34/temp

# 查看某一参数的用法
jinfo -flag <参数名> <进程id>
jinfo -flag MaxHeapSize 28142

[[email protected] apache-tomcat-8.5.34]# jinfo -flag MaxHeapSize 28142
-XX:MaxHeapSize=459276288
           

3 jvm的内存模型

3.1 jdk1.7堆内存模型

jvm优化-a第一节
  • 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中,这种情况下,一般重新启动应用服务器就可以已解决问题。

3.2 jdk1.8的堆内存模型

jvm优化-a第一节
  • 由上图可以看出,jdk1.8的堆内存模型是由2部分组成的,年轻代 + 年老代
  • 年轻代 Eden + 2*Survivor
  • 年老代 OldGen
  • 在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间) 进行了替换。
  • 需要特别说明:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间,这也是与1.7的永久代最大的区别所在。
jvm优化-a第一节

3.3 为什么要废弃1.7中的永久区

官网:http://openjdk.java.net/jeps/122

Motivation
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加载统计
[[email protected] webapps]# jps
28582 Jps
28142 Bootstrap
[[email protected] webapps]# jstat -class 28142
Loaded  Bytes  Unloaded  Bytes     Time   
  3668  7838.3        0     0.0       1.91
           

说明:

  • Loaded:加载class的数量
  • Bytes:所占用空间大小
  • Unloaded:未加载数量
  • Bytes:未加载占用空间
  • Time时间

3.4.2 查看编译统计

[[email protected] webapps]# jstat -compiler 28142
Compiled Failed Invalid   Time   FailedType FailedMethod
    2505      0       0     4.15          0  
           

说明:

  • Compiled: 编译数量
  • Failed:失败数量
  • Invalid:不可用数量
  • Time:时间
  • FailedType:失败类型
  • FailedMethod:失败的方法

3.4.3 垃圾回收统计

[[email protected] webapps]# jstat -gc 28142
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1344.0 1344.0 301.9   0.0   11200.0   8050.4   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098

# 也可以指定打印的间隔和次数,每一秒打印一次,共打印5次
[[email protected] webapps]# jstat -gc 28142 1000 5
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
1344.0 1344.0 301.9   0.0   11200.0   8236.2   27708.0    20235.3   25600.0 25027.5 2816.0 2615.2     18    0.072   1      0.025    0.098
           

说明:

  • 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可以获取到更加详细的内容,如:内存使用情况的汇总、对内存溢出的定位与分析
           

4.1 查看内存的使用情况

[[email protected] webapps]# jmap -heap 28142
Attaching to process ID 28142, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.60-b23

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 459276288 (438.0MB)
   NewSize                  = 9764864 (9.3125MB)
   MaxNewSize               = 153092096 (146.0MB)
   OldSize                  = 19595264 (18.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 = 12845056 (12.25MB)
   used     = 10679976 (10.185218811035156MB)
   free     = 2165080 (2.0647811889648438MB)
   83.14464335538904% used
Eden Space:
   capacity = 11468800 (10.9375MB)
   used     = 10370848 (9.890411376953125MB)
   free     = 1097952 (1.047088623046875MB)
   90.42661830357143% used
From Space:
   capacity = 1376256 (1.3125MB)
   used     = 309128 (0.29480743408203125MB)
   free     = 1067128 (1.0176925659179688MB)
   22.46151878720238% used
To Space:
   capacity = 1376256 (1.3125MB)
   used     = 0 (0.0MB)
   free     = 1376256 (1.3125MB)
   0.0% used
tenured generation:
   capacity = 28372992 (27.05859375MB)
   used     = 20720952 (19.76103973388672MB)
   free     = 7652040 (7.297554016113281MB)
   73.03054961563447% used
           

​ 14484 interned Strings occupying 1933240 bytes.

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

查看所有的对象,包括活跃及非活跃的

jmap -histo <pid> | more
           

查看活跃对象

jmap -histo:live <pid> | more

[[email protected] webapps]# jmap -histo:live 28142 | more

 num     #instances         #bytes  class name
----------------------------------------------
   1:         33674        6204920  [C
   2:          3059        1298280  [I
   3:           837         797888  [B
   4:         32047         769128  java.lang.String
   5:          4042         466176  java.lang.Class
   6:         11140         356480  java.util.HashMap$Node
   7:          3741         245952  [Ljava.lang.Object;
   8:          6805         217760  java.util.concurrent.ConcurrentHashMap$Node
   9:           863         138000  [Ljava.util.HashMap$Node;
  10:            67         101840  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  11:          6038          96608  java.lang.Object
  12:           879          73208  [Ljava.lang.String;
  13:          1294          62112  java.util.HashMap
  14:          1797          57504  java.util.Hashtable$Entry
  15:            88          49344  [Ljava.util.WeakHashMap$Entry;
  16:          1027          41080  java.util.TreeMap$Entry
  17:           842          40416  org.apache.tomcat.util.modeler.AttributeInfo
  18:           446          39248  java.lang.reflect.Method
  19:           976          39040  java.util.LinkedHashMap$Entry
  20:           516          37152  org.apache.jasper.compiler.Node$TemplateText
  21:           893          35720  java.lang.ref.SoftReference
  22:            48          34944  [S
  23:           125          34416  [[C
  24:           428          34240  java.lang.reflect.Constructor
  25:           401          28872  java.util.logging.Logger
  26:           572          22880  org.apache.jasper.compiler.Mark
  27:           438          21024  java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync
  28:           645          20640  javax.management.MBeanAttributeInfo
  29:            99          20464  [Ljava.util.Hashtable$Entry;
  30:           633          20256  java.util.concurrent.locks.ReentrantLock$NonfairSync
  31:           620          19840  java.lang.ref.WeakReference
  32:           407          19536  java.util.logging.LogManager$LoggerWeakRef
--More--
           

对象说明:

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

示例:

jmap -dump:format=b,file=/tmp/dump.dat 28142

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

将jvm的内存dump文件中,文件是一个二进制的文件,不方便查看,这时我们借助于jhat工具进行查看。
jhat -port <port> <file>

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

[[email protected] ~]# jhat -port 9999 /tmp/dump.dat
Reading from /tmp/dump.dat...
Dump file created Mon Apr 13 13:09:10 CST 2020
Snapshot read, resolving...
Resolving 148587 objects...
Chasing references, expect 29 dots.............................
Eliminating duplicate references.............................
Snapshot resolved.
Started HTTP server on port 9999
Server is ready.
           
jvm优化-a第一节

在后面有OQL查询功能。

jvm优化-a第一节
select s from java.lang.String s where s.value.length >= 1000
           
jvm优化-a第一节

4.5 通过MAT工具对dump文件进行分析

4.5.1 MAT工具介绍

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

# 官网地址	
	https://www.eclipse.org/mat
           
jvm优化-a第一节

4.5.2 下载安装

下载地址:https://www.eclipse.org/mat/downloads.php
           
jvm优化-a第一节

将下载得到的MemoryAnalyzer-1.8 进行解压。

jvm优化-a第一节

4.5.3 使用

  • 打开文件
    jvm优化-a第一节
jvm优化-a第一节
  • 列出内存中的对象
jvm优化-a第一节
  • 正则匹配
jvm优化-a第一节
  • 查看对象以及它的依赖:
jvm优化-a第一节
  • 查看肯能存在的内存泄漏分析
jvm优化-a第一节

5 实战:内存溢出的定位和分析

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

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

首先,要学会如何定位问题,然后再进行分析,如何定位问题,需要借助于jmat和Mat工具进行定位分析。
           

5.1 模拟内存溢出

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

package cn.zephyr.jvm;

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

public class BTestJvmOutOfMemory {

    // 实现,向集合中添加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();
            }
            list.add(str);
        }
        System.out.println("ok");
    }

}

# 设置的jvm参数: 
	-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
           
jvm优化-a第一节

5.2 运行测试

结果如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:421)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at cn.zephyr.jvm.BTestJvmOutOfMemory.main(BTestJvmOutOfMemory.java:15)
Disconnected from the target VM, address: '127.0.0.1:57216', transport: 'socket'
           

​ Process finished with exit code 1

可以看到当内存溢出时,会dump文件到java_pid25620.hprof文件中。

5.3 导入到MAT工具中进行分析

jvm优化-a第一节

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

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

查看详情:

jvm优化-a第一节

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

6 jstack的使用

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

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

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

# 用法 jstack <pid>

[[email protected] ~]# jstack 28142 
2020-04-13 23:02:07
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode):

"http-nio-8080-exec-20" #64 daemon prio=5 os_prio=0 tid=0x00007fc5a8124800 nid=0x6f9f waiting on condition [0x00007fc599d87000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-20" #63 daemon prio=5 os_prio=0 tid=0x00007fc5c847c800 nid=0x6f9e waiting on condition [0x00007fc599e88000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-19" #62 daemon prio=5 os_prio=0 tid=0x00007fc5b40e2000 nid=0x6f9d waiting on condition [0x00007fc59a892000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-19" #61 daemon prio=5 os_prio=0 tid=0x00007fc5ac155800 nid=0x6f9c waiting on condition [0x00007fc599a84000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-18" #60 daemon prio=5 os_prio=0 tid=0x00007fc59c0db800 nid=0x6f9a waiting on condition [0x00007fc59a690000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-18" #59 daemon prio=5 os_prio=0 tid=0x00007fc5c84a0000 nid=0x6f99 waiting on condition [0x00007fc59a08a000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-17" #58 daemon prio=5 os_prio=0 tid=0x00007fc5b000c000 nid=0x6f98 waiting on condition [0x00007fc59af99000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-17" #57 daemon prio=5 os_prio=0 tid=0x00007fc5c84ac000 nid=0x6f97 waiting on condition [0x00007fc599b85000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-16" #56 daemon prio=5 os_prio=0 tid=0x00007fc5a0037800 nid=0x6f96 waiting on condition [0x00007fc59a993000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-16" #55 daemon prio=5 os_prio=0 tid=0x00007fc5b0004800 nid=0x6f95 waiting on condition [0x00007fc599c86000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-15" #54 daemon prio=5 os_prio=0 tid=0x000000000218a000 nid=0x6f94 waiting on condition [0x00007fc59ad97000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-15" #53 daemon prio=5 os_prio=0 tid=0x00007fc5a0037000 nid=0x6f93 waiting on condition [0x00007fc59a28c000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"ajp-nio-8009-exec-14" #52 daemon prio=5 os_prio=0 tid=0x00007fc5a8124000 nid=0x6f91 waiting on condition [0x00007fc59a791000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523960> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

"http-nio-8080-exec-14" #51 daemon prio=5 os_prio=0 tid=0x00007fc5b403f000 nid=0x6f90 waiting on condition [0x00007fc59a18b000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ee523fc8> (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)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
...
           

6.1 线程的状态

jvm优化-a第一节

在java中线程的状态一共被分为6种:

  • 初始态(NEW)
    • 创建一个Thread对象,但还未调用start()启动线程时,线程处于初态
  • 运行态(RUNNABLE)在java中,运行态包括就绪态和运行态
    • 就绪态
      • 改状态下的线程已经获得执行所需要的资源,只要CPU分配执行权就能运行。
      • 所有就绪态的线程存放在就绪队列中
    • 运行态
      • 获得CPU执行权,正在执行的线程
      • 由于一个CPU同一个时刻只能执行一条线程,一次每个CPU每个时刻只有一条运行态的线程
  • 阻塞态(BLOCKED)
    • 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态
    • 而在java中,阻塞态专指请求锁失败时进入的状态
    • 由一个阻塞队列存放所有阻塞态的线程
    • 处于阻塞态的线程会不断的请求资源,一旦请求成功,就会进入就绪队列,等待执行
  • 等待态
    • 当线程中调用wait、join、park函数时,当前线程就会进入等待态
    • 也有一个等待队列存放所有等待态的线程。
    • 线程处于等待态表示等待其他线程的执行才能继续运行
    • 进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
  • 超时等待态(TIMED_WAITING)
    • 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态
    • 它和等待态一样,并不是因为请求不到资源,二十主动进入,并且进入后需要其他线程唤醒;
    • 进入该状态后释放CPU执行权和占有的资源
    • 与等待态的区别:到了超时的时间后自动进入阻塞队列,开始竞争锁。
  • 终止态
    • 线程执行结束后的状态。

6.2 死锁问题

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

6.2.1 构造死锁

编写代码,启动2个线程,Thread1拿到了obj1锁,准备去拿obj2锁时,obj2已经被Thread2锁定,发生了死锁。
           
public class CTestDeadLock {

    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 在linxu上运行

[[email protected] jvm]# javac CTestDeadLock.java 
[[email protected] jvm]# ll
total 20
-rw-r--r-- 1 root root  187 Apr 14 12:24 'CTestDeadLock$1.class'
-rw-r--r-- 1 root root 1083 Apr 14 12:24 'CTestDeadLock$Thread1.class'
-rw-r--r-- 1 root root 1083 Apr 14 12:24 'CTestDeadLock$Thread2.class'
-rw-r--r-- 1 root root  849 Apr 14 12:24  CTestDeadLock.class
-rw-r--r-- 1 root root 1570 Apr 14 12:24  CTestDeadLock.java
[[email protected] jvm]# ll | wc -l 
6
           
[[email protected] jvm]# java CTestDeadLock
Thread1 拿到了 obj1 的锁!
Thread2 拿到了 obj2 的锁!
           

6.2.3 使用jstack进行分析

[[email protected] ~]# jstack 30439
2020-04-14 12:28:01
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f9fb8001000 nid=0x771c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007f9fdc009800 nid=0x76e8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #9 prio=5 os_prio=0 tid=0x00007f9fdc0cd000 nid=0x76f2 waiting for monitor entry [0x00007f9fccefd000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at CTestDeadLock$Thread2.run(CTestDeadLock.java:47)
        - waiting to lock <0x00000000e4a81228> (a java.lang.Object)
        - locked <0x00000000e4a81238> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"Thread-0" #8 prio=5 os_prio=0 tid=0x00007f9fdc0cb000 nid=0x76f1 waiting for monitor entry [0x00007f9fccffe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at CTestDeadLock$Thread1.run(CTestDeadLock.java:27)
        - waiting to lock <0x00000000e4a81238> (a java.lang.Object)
        - locked <0x00000000e4a81228> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f9fdc0b7800 nid=0x76ef runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f9fdc0b2800 nid=0x76ee waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f9fdc0b0800 nid=0x76ed waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

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

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f9fdc075000 nid=0x76eb in Object.wait() [0x00007f9fe0966000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000e4a070b8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000e4a070b8> (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=0 tid=0x00007f9fdc073000 nid=0x76ea in Object.wait() [0x00007f9fe0a67000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000e4a06af8> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)
        - locked <0x00000000e4a06af8> (a java.lang.ref.Reference$Lock)

"VM Thread" os_prio=0 tid=0x00007f9fdc06e000 nid=0x76e9 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f9fdc0ba800 nid=0x76f0 waiting on condition 

JNI global references: 6


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

"Thread-1":
  waiting to lock monitor 0x00007f9fc0006528 (object 0x00000000e4a81228, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f9fc0005088 (object 0x00000000e4a81238, a java.lang.Object),
  which is held by "Thread-1"

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

"Thread-1":
        at CTestDeadLock$Thread2.run(CTestDeadLock.java:47)
        - waiting to lock <0x00000000e4a81228> (a java.lang.Object)
        - locked <0x00000000e4a81238> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)
"Thread-0":
        at CTestDeadLock$Thread1.run(CTestDeadLock.java:27)
        - waiting to lock <0x00000000e4a81238> (a java.lang.Object)
        - locked <0x00000000e4a81228> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.
           

在输出的信息中,已经看到,发现了一个死锁,关键看这个:

"Thread-1":
        at CTestDeadLock$Thread2.run(CTestDeadLock.java:47)
        - waiting to lock <0x00000000e4a81228> (a java.lang.Object)
        - locked <0x00000000e4a81238> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)
"Thread-0":
        at CTestDeadLock$Thread1.run(CTestDeadLock.java:27)
        - waiting to lock <0x00000000e4a81238> (a java.lang.Object)
        - locked <0x00000000e4a81228> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.
           

可以清晰的看到:

  • Thread2获取了 <0x00000000e4a81238>,等待获取<0x00000000e4a81228> 这个锁
  • Thread1获取了 <0x00000000e4a81228>,等待获取 <0x00000000e4a81238>这个锁7

7 VisualVM工具的使用

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

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

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

7.1 启动

在jdk的安装目录bin下,找到 jvisualvm.exe,双击打开即可。

jvm优化-a第一节

7.2 查看本地进程

jvm优化-a第一节

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

jvm优化-a第一节

7.4 查看线程详情

jvm优化-a第一节

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

jvm优化-a第一节

显示的内容是一样的。

7.5 抽样器

抽样器可以对CPU,内存在一段时间内进行好抽样,以供分析

jvm优化-a第一节

7.6 监控远程的JVM

VisualJVM可以监控本地进程,还可以监控远程jvm进程,借助于JMX技术实现。

7.6.1 什么是JMX

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

7.6.2 远程监控tomcat

要想监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,在tomcat的bin目录下,修改catalina.sh,添加如下的参数:
           

不需要鉴权的配置

先修改Tomcat的启动脚本,windows下为bin/catalina.bat(linux下为catalina.sh),添加以下内容
           
  1. windows配置:
    set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false  
    set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG% 
               
  2. linux配置
    JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.authenticate=false - Dcom.sun.management.jmxremote.ssl=false"
               
  3. 配置参数的意思
  • -Dcom.sun.management.jmxremote 允许使用JMX远程管理
  • -Dcom.sun.management.jmxremote.port=8999 JMX远程连接端口
  • -Dcom.sun.management.jmxremote.authenticate=false 不进行身份认证,任何用户都可以连接
  • -Dcom.sun.management.jmxremote.ssl=false 不使用ssl

需要鉴权的配置

  1. 配置
    set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access  
    set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%
               
  2. 复制并修改授权文件
    • JAVA_HOME/jre/lib/management下有jmxremote.access和jmxremote.password.template的模板文件,将两个文件复制到CATALINA_BASE/conf目录下

      mv jmxremote.password.template jmxremote.password

    • vi CATALINA_BASE/conf/jmxremote.access 添加内容:

      monitorRole readonly

      controlRole readwrite

    • vi CATALINA_BASE/conf/jmxremote.password 添加内容:

      monitorRole liuke #可自定义

      controlRole liuke #可自定义

7.6.3 使用 VisualJVM连接远程tomcat

添加远程主机:

jvm优化-a第一节

在一个远程主机上可能有多个jvm需要监控,接下来在该主机上添加需要监控的jvm

jvm优化-a第一节

连接成功

jvm优化-a第一节

JMX远程连接端口

  • -Dcom.sun.management.jmxremote.authenticate=false 不进行身份认证,任何用户都可以连接
  • -Dcom.sun.management.jmxremote.ssl=false 不使用ssl

需要鉴权的配置

  1. 配置
    set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access  
    set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%
               
  2. 复制并修改授权文件
    • JAVA_HOME/jre/lib/management下有jmxremote.access和jmxremote.password.template的模板文件,将两个文件复制到CATALINA_BASE/conf目录下

      mv jmxremote.password.template jmxremote.password

    • vi CATALINA_BASE/conf/jmxremote.access 添加内容:

      monitorRole readonly

      controlRole readwrite

    • vi CATALINA_BASE/conf/jmxremote.password 添加内容:

      monitorRole liuke #可自定义

      controlRole liuke #可自定义

7.6.3 使用 VisualJVM连接远程tomcat

添加远程主机:

jvm优化-a第一节

在一个远程主机上可能有多个jvm需要监控,接下来在该主机上添加需要监控的jvm

jvm优化-a第一节

连接成功

jvm优化-a第一节

使用方法和本地一样,就可以像监控本地jvm一样监控远程tomcat进程