JVM工具篇

266 阅读16分钟

JVM 工具类

监控工具

JPS

参数如下:

usage: jps [-help]
       jps [-q] [-mlvV] [<hostid>]

Definitions:
    <hostid>:      <hostname>[:<port>]
    
-q 只显示进程号
-m 显示传递给main方法的参数
-l 显示应用main class的完整包名应用的jar文件完整路径名
-v 显示传递给JVM的参数
-V 禁止输出类名、JAR文件名和传递给main方法的参数,仅显示本地JVM标识符的列表

JSTAT

[root@localhost ~]# jstat
invalid argument count
Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

option:		 	 支持监控的类型 通过jstat -options查看
-t:				显示每次采样花费的时间
-h<lines>:		每采样次<lines>次后输出,默认是0,显示数据第一行的列标题
vmid:			进程唯一标示,远程进程格式为<lvmid>[@<hostname>[:<port>]]
interval:		指定间隔多久监控一次
count:			指定监控多少次退出

[root@localhost ~]# jstat -options
-class					显示类加载器统计的信息
-compiler				显示有关Java HotSpot VM即时编译器行为的统计信息
-gc						显示有关垃圾收集堆行为的统计信息
-gccapacity				统计各个分代(新生代、老年代、持久代)的容量情况
-gccause				显示引起垃圾收集事件的原因
-gcnew					显示新生代相关的行为统计信息
-gcnewcapacity			显示新生代的容量
-gcold					显示老年代、元空间的行为统计信息
-gcoldcapacity			显示老年代的容量
-gcmetacapacity			显示元空间的容量
-gcutil					显示有关垃圾收集系统信息的摘要
-printcompilation		显示Java HotSpot VM编译方法统计信息

示例:

# 监控进程为3702的类信息 显示每次采样花费时间 每采样3次后输出列标题 每秒采集一次 共采集5次
[root@localhost ~]# jstat -class -t -h3 3702 1000 5
Timestamp       Loaded  Bytes  Unloaded  Bytes     Time   
          930.5  12214 22816.3        0     0.0      20.07
          931.6  12214 22816.3        0     0.0      20.07
          932.6  12214 22816.3        0     0.0      20.07
Timestamp       Loaded  Bytes  Unloaded  Bytes     Time   
          933.6  12214 22816.3        0     0.0      20.07
          934.6  12214 22816.3        0     0.0      20.07

故障排查工具

jinfo

Usage:
    jinfo [option] <pid>  #连接运行的进程
        (to connect to running process)
    jinfo [option] <executable <core> #连接核心文件
        (to connect to a core file) # 连接远端服务
    jinfo [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    -flag <name>         打印VM参数的值 				to print the value of the named VM flag
    -flag [+|-]<name>    动态启用和禁用VM参数  		  to enable or disable the named VM flag
    -flag <name>=<value> 动态设置VM参数的数值          to set the named VM flag to the given value
    -flags               打印VM参数信息	            to print VM flags
    -sysprops            打印进程的系统参数信息 		 to print Java system properties
    <no option>          没有参数打印上面所有信息    	to print both of the above
    -h | -help           打印jinfo帮助信息			to print this help message

[option] 可选参数

<pid> 必选参数

使用下面命令显示出来的参数支持jinfo动态修改

[root@localhost ~]# java -XX:+PrintFlagsInitial | grep manageable
     intx CMSAbortablePrecleanWaitMillis            = 100                                 {manageable}
     intx CMSTriggerInterval                        = -1                                  {manageable}
     intx CMSWaitDuration                           = 2000                                {manageable}
     bool HeapDumpAfterFullGC                       = false                               {manageable}
     bool HeapDumpBeforeFullGC                      = false                               {manageable}
     bool HeapDumpOnOutOfMemoryError                = false                               {manageable}
     ccstr HeapDumpPath                             =                                     {manageable}
     uintx MaxHeapFreeRatio                         = 70                                  {manageable}
     uintx MinHeapFreeRatio                         = 40                                  {manageable}
     bool PrintClassHistogram                       = false                               {manageable}
     bool PrintClassHistogramAfterFullGC            = false                               {manageable}
     bool PrintClassHistogramBeforeFullGC           = false                               {manageable}
     bool PrintConcurrentLocks                      = false                               {manageable}
     bool PrintGC                                   = false                               {manageable}
     bool PrintGCDateStamps                         = false                               {manageable}
     bool PrintGCDetails                            = false                               {manageable}
     bool PrintGCID                                 = false                               {manageable}
     bool PrintGCTimeStamps                         = false                               {manageable}

下面是修改使用示例

jinfo -flag -HeapDumpAfterFullGC 3702  # 关闭
jinfo -flag +HeapDumpAfterFullGC 3702  # 打开
jinfo -flag HeapDumpAfterFullGC 3702   # 查看信息 确认是否修改成功

jinfo -flag MinHeapFreeRatio=33 3702   # 赋予指定值
jinfo -flag MinHeapFreeRatio=33 3702   # 赋予指定值
jinfo -flag MinHeapFreeRatio 3702   # 查看信息 确认是否修改成功

jmap

Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    <none>               #打印和Solaris pmap相同的信息  
    -heap                #打印Java堆的摘要信息			
    -histo[:live]        #打印java堆中对象,如果追加:live 表示只打印存活对象 
    -clstats             #打印类加载器统计信息
    -finalizerinfo       #打印等待回收的对象信息 
    -dump:<dump-options> #讲堆信息存储为 hprof格式的二进制文件
                         #存储参数	dump-options:
                           live         #指定存储存活对象,如果没有指定,存储所有对象
                           format=b     #指定binary格式化
                           file=<file>  #指定存储的文件 dump heap to <file>
                         #示例: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   force. Use with -dump:<dump-options> <pid> or -histo
                         to force a heap dump or histogram when <pid> does not
                         respond. The "live" suboption is not supported
                         in this mode.
    -h | -help           to print this help message
    -J<flag>             to pass <flag> directly to the runtime system For
			  example, -J-mx512m to use a maximum heap size of 512MB
    
jmap -dump:live,format=b,file=heap.bin

拓展知识

除了jmap以外,还有以下方式获取堆Dump

  • 使用-XX:+HeapDumpOnOutOfMemoryError,让虚拟机在OOM异常出现后自动生成堆Dump文件
  • 使用-XX:+HeapDumpOnCtrlBreak,可使用[Ctrl]+[Break],让虚拟机生成堆Dump文件
  • 在Linux操作系统下,发送kill -3 pid命令
  • 对于SpringBoot应用,可以使用Spring Boot Actuator 提供的/actuator/heapdump实现堆Dump

jstack

jstack,全称 Stack Trace for Java ,用于打印当前虚拟机的线程快照,jdk8的版本比jdk11的参数还要多

jdk8

[root@localhost ~]# jstack
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  # 强制线程转储,如果jstack <pid> 没响应 程序会被挂起
    -m  # 打印本地方法栈和java方法(mixed mode)
    -l  # 显示有关锁的信息
    -h or -help to print this help message

jdk11

[root@localhost java]# jstack
Usage:
    jstack [-l][-e] <pid>
        (to connect to running process)

Options:
    -l  # 显示有关锁的信息
    -e  # 扩展清单 显示线程的相关信息
    -? -h --help -help to print this help message

"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=12.45ms elapsed=713.18s tid=0x00007f87040fe800 nid=0x12a9 waiting on condition  [0x00007f8708386000]
   java.lang.Thread.State: RUNNABLE
	at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.1/Native Method)
	at java.lang.ref.Reference.processPendingReferences(java.base@11.0.1/Reference.java:241)
	at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.1/Reference.java:213)
# -l会额外输出下面信息 包括持有的锁信息,锁类型 ,锁地址
   Locked ownable synchronizers:
	- None

-e参数会额外打印 allocated=0B defined_classes=0 包括分配的内存和定义的类数量

"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=12.45ms elapsed=720.72s allocated=0B defined_classes=0 tid=0x00007f87040fe800 nid=0x12a9 waiting on condition  [0x00007f8708386000]
   java.lang.Thread.State: RUNNABLE
	at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.1/Native Method)
	at java.lang.ref.Reference.processPendingReferences(java.base@11.0.1/Reference.java:241)
	at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.1/Reference.java:213)

   Locked ownable synchronizers:
	- None

如果线程长时间等待的情况下可以考虑使用jstack去分析,比如线程死锁,死循环,远程请求长时间得不到返回,都会出现线程长时间等待,使用jstack可以看到每个线程的调用信息,这样能知道没有响应的线程在干什么信息,或者在等待什么资源,可以通过工具去分析jstack结果

jhat

jhat(JVM Heap Analysis Tool)用来分析jmap生成的堆Dump。

jhat功能不是很强,VisualVM,Eclipse Menory Analyzer都比jhat强大

jhat在jdk11中已被废弃

Usage:  jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

	-J<flag>          Pass <flag> directly to the runtime system. For
			  example, -J-mx512m to use a maximum heap size of 512MB
	-stack false:     Turn off tracking object allocation call stack.
	-refs false:      Turn off tracking of references to objects
	-port <port>:     Set the port for the HTTP server.  Defaults to 7000
	-exclude <file>:  Specify a file that lists data members that should
			  be excluded from the reachableFrom query.
	-baseline <file>: Specify a baseline object dump.  Objects in
			  both heap dumps with the same ID and same class will
			  be marked as not being "new".
	-debug <int>:     Set debug level.
			    0:  No debug output
			    1:  Debug hprof file parsing
			    2:  Debug hprof file parsing, no server
	-version          Report version number
	-h|-help          Print this help and exit
	<file>            The file to read

For a dump file that contains multiple heap dumps,
you may specify which dump in the file
by appending "#<number>" to the file name, i.e. "foo.hprof#3".

All boolean options default to "true"

使用示例

#分析t1.hprof 并开启对象分配调用栈的分析
jhat -stack true t1.hprof
#分析t1.hprof 并开发对象分配调用栈的分析,关闭对象引用的分析
jhat -stack true -refs false t1.hprof

jcmd

[root@localhost ~]# jcmd -h
Usage: jcmd <pid | main class> <command ...|PerfCounter.print|-f file>
   or: jcmd -l                                                    
   or: jcmd -h                                                    
                                                                  
  command must be a valid jcmd command for the selected jvm.      
  Use the command "help" to see which commands are available.   
  If the pid is 0, commands will be sent to all Java processes.   
  The main class argument will be used to match (either partially or fully) the class used to start Java.                         
  If no options are given, lists Java processes (same as -l).     
                                                                  
  PerfCounter.print #显示此进程公开的计数器数量 display the counters exposed by this process  
  -f  #通过文件读取并执行命令 read and execute commands from the file                     
  -l  #查看本地JVM全部进程 list JVM processes on the local machine                     
  -? -h --help print this help message       

command中的参数

Compiler.CodeHeap_Analytics
Compiler.codecache
Compiler.codelist
Compiler.directives_add
Compiler.directives_clear
Compiler.directives_print
Compiler.directives_remove
Compiler.queue
GC.class_histogram
GC.class_stats
GC.finalizer_info
GC.heap_dump
GC.heap_info
GC.run
GC.run_finalization
JFR.check
JFR.configure
JFR.dump
JFR.start
JFR.stop
JVMTI.agent_load
JVMTI.data_dump
ManagementAgent.start
ManagementAgent.start_local
ManagementAgent.status
ManagementAgent.stop
Thread.print
VM.check_commercial_features
VM.class_hierarchy
VM.classloader_stats
VM.classloaders
VM.command_line
VM.dynlibs
VM.flags
VM.info
VM.log
VM.metaspace
VM.native_memory
VM.print_touched_methods
VM.set_flag
VM.stringtable
VM.symboltable
VM.system_properties
VM.systemdictionary
VM.unlock_commercial_features
VM.uptime
VM.version
help

jhsdb

JDK8默认没有jhsdb命令 要通过以下命令进入

java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB #使用命令行方式调试器
java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB #使用GUI方式调试器

JDK11下命令使用方法

jhsdb clhsdb [--pid pid | --exe executable  --core coredump] # 启动交互式命令行调试工具
jhsdb debugdb [options] (pid | executable coredump) [server-id] # 启动远程调试服务器
jhsdb hsdb [--pid pid | --exe executable  --core coredump] # 启动交互式GUI调试工具
jhsdb jstack [--pid pid | --exe executable  --core coredump] [options] # 打印堆栈并锁定信息
jhsdb jmap [--pid pid | --exe executable  --core coredump] [options] # 打印堆信息
jhsdb jinfo [--pid pid | --exe executable  --core coredump] [options] # 打印基本的JVM信息
jhsdb jsnap [options] [--pid pid | --exe executable  --core coredump] # 打印性能计数器信息

其中

  • PID 为进程号:

  • server-id:当多个调试服务器在同一远程主机上运行时使用的可选唯一ID

  • executable:从中生成核心转储的java可执行文件

  • coredump:jhsdb工具连接到的Dump文件

    coredump介绍与开启方式:www.cnblogs.com/Anker/p/607…

  • option:命令行选项,和子命令有关

    --pid ,--exe 参数二选一必填

jhsdb clhsdb --pid 4774 # 进入命令行调试工具

输入flags 能查看所有能配置的 -XX开头的JVM参数

通过 java -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsInitial 也能查看-XX开头的JVM参数

输入help,查看相关命令:

Available commands:
  assert true | false
  attach pid | exec core
  buildreplayjars [ all | app | boot ]  | [ prefix ]
  detach
  dis address [length]
  disassemble address
  dumpcfg { -a | id }
  dumpcodecache
  dumpideal { -a | id }
  dumpilt { -a | id }
  dumpreplaydata { <address > | -a | <thread_id> }
  echo [ true | false ]
  examine [ address/count ] | [ address,address]
  field [ type [ name fieldtype isStatic offset address ] ]
  findpc address
  flags [ flag | -nd ]
  g1regiondetails
  help [ command ]
  history
  inspect expression
  intConstant [ name [ value ] ]
  jdis address
  jhisto
  jstack [-v]
  livenmethods
  longConstant [ name [ value ] ]
  pmap
  print expression
  printall
  printas type expression
  printmdo [ -a | expression ]
  printstatics [ type ]
  pstack [-v]
  quit
  reattach
  revptrs address
  scanoops start end [ type ]
  search [ heap | perm | rawheap | codecache | threads ] value
  source filename
  symbol address
  symboldump
  symboltable name
  thread { -a | id }
  threads
  tokenize ...
  type [ type [ name super isOop isInteger isUnsigned size ] ]
  universe
  verbose true | false
  versioncheck [ true | false ]
  vmstructsdump
  where { -a | id }
  
  #直接输入上述命令查看输出信息 具体参数做什么 后续查资料不全

jhsdb和其他工具对比

功能JHSDBJCMD类似工具
展示JAVA进程jcmdjps -lm
堆DUmpjhsdb jmap --binaryheapjcmd pid GC.heap_dumpjmap -dump pid
堆使用直方图jhsdb jmap --histojcmd pid GC.class_histogramjmap -histo pid
线程Dumpjhsdb jstack --locks (subset of locked thread frames)jcmd pid Thread.printjstack pid
展示系统属性jhsdb jinfo --syspropsjcmd pid VM.system_propertiesjinfo -sysprops pid
列出JVM标记jhsdb jinfo --flagsjcmd pid VM.flagsjinfo -flags pid

可视化工具

jhsdb

jconsole

VisualVM

Java Mission Control (JMC)

第三方工具

MAT(Memory Analyzer Tool)

JITWatch

实战篇

JVM日志

常用JDK8运行时参数

TraceExceptions、TraceClassLoading、 TraceClassLoadingPreorder.
TraceClassUnloading、VerkoseVerification、 TraceClassPaths、
TraceClassResolution、TraceClassInitialization、 TraceLoaderConstraints、
TraceClassLoaderData、TraceSafepointCleanupTime、 TraceSafepoint、
ThateMonitorInflation、TraceBiasedL ocking、TraceRedefineClasses

JDK8打印日志

-Xms50m -Xmx50m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCCause -Xloggc:H:\gclog.log

跟踪类加载情况以及偏向锁情况

-XX:+TraceClassLoading -XX:+TraceBiasedLocking

Xlog

通过对比上述打印日志信息发现,两边的日志格式差异非常大,在JDK9开始使用了 Xlog 做统一日志管理

可以通过-Xlog选项,启用统一日志管理。

Xlog选项支持的参数如下:

  • -Xlog: 使用info级别启用JVM日志

  • -Xlog:help: 打印Xlog帮助文档

  • -Xlog:disable: 关闭所有日志记录并清除日志记录框架的所有配置,包括警告和错误的默认配置

  • -Xlog[:option]: 按照命令行上出现的顺序应用多个参数。同一输出的多个参数按其给定顺序覆盖。option的格式为:

    [:[what][:[output][:[decorators][:output-options[,...]]]]]
    

其中:

  • what:指定level和tag的组合,格式: tag1[+tag2...][*][=leve1][,...]。除非用*指定了通配符,否则只有匹配了指定tag的日志消息才会被匹配。

  • output: 设置输出类型。默认为stdout。

  • decorators: 使用一系列自定义的装饰器去配置output。 缺省的装饰器为uptime、level和tags。

  • output-options: 设置Xlog的日志输出选项,格式:

    filecount=file-count filesize=file size with optional K, M or G suffix
    

问题定位

CPU过高

原因

  • 存在死循环

    • 尽量避免死循环
    • 在循环代码块中适当休眠
  • 频繁GC

    • 分配内存对象太小导致
  • 频繁创建新对象

    • 单例模式结局频繁创建对象问题
  • 序列化和反序列化

    • 序列化工具使用不当导致
  • 正则表达式

  • 频繁的线程上下文切换

    • 降低切换频率

解决方案

  • top+jstack

    通过top查看进程资源占用情况

    [root@localhost java]# top
    top - 17:53:23 up 1 day,  2:51,  4 users,  load average: 0.00, 0.01, 0.05
    Tasks: 108 total,   1 running, 107 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  1882300 total,   369116 free,   566436 used,   946748 buff/cache
    KiB Swap:  2097148 total,  2097148 free,        0 used.  1095200 avail Mem 
      PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                
     4774 root      20   0 2861944 414752  15116 S  0.3 22.0   2:39.82 java                                  
    12024 root      20   0       0      0      0 S  0.3  0.0   0:00.25 kworker/0:3                                               
        1 root      20   0  128024   6532   4136 S  0.0  0.3   0:01.41 systemd                                                   
        2 root      20   0       0      0      0 S  0.0  0.0   0:00.01 kthreadd                                                  
        3 root      20   0       0      0      0 S  0.0  0.0   0:03.35 ksoftirqd/0                                               
        5 root       0 -20       0      0      0 S  0.0  0.0   0:00.00 kworker/0:0H                                              
        6 root      20   0       0      0      0 S  0.0  0.0   0:00.00 kworker/u2:0       
    

    top -Hp <pid> 查看进程中的线程运行信息

    [root@localhost ~]# top -Hp 4774
    top - 17:54:08 up 1 day,  2:52,  4 users,  load average: 0.00, 0.01, 0.05
    Threads: 103 total,   0 running, 103 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.3 us,  0.0 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  1882300 total,   369856 free,   565716 used,   946728 buff/cache
    KiB Swap:  2097148 total,  2097148 free,        0 used.  1095940 avail Mem 
    
     PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
     4835 root      20   0 2861944 414752  15116 S  0.3 22.0   0:00.99 task-scheduler-                                           
     4774 root      20   0 2861944 414752  15116 S  0.0 22.0   0:00.00 java                                                      
     4775 root      20   0 2861944 414752  15116 S  0.0 22.0   0:11.96 java                                                      
     4776 root      20   0 2861944 414752  15116 S  0.0 22.0   0:10.04 VM Thread                                                 
     4777 root      20   0 2861944 414752  15116 S  0.0 22.0   0:00.01 Reference Handl                                           
     4778 root      20   0 2861944 414752  15116 S  0.0 22.0   0:00.00 Finalizer                                                 
     4779 root      20   0 2861944 414752  15116 S  0.0 22.0   0:00.00 Signal Dispatch                                           
     4780 root      20   0 2861944 414752  15116 S  0.0 22.0   0:37.53 C2 CompilerThre                                           
     4781 root      20   0 2861944 414752  15116 S  0.0 22.0   0:06.14 C1 CompilerThre                                           
     4782 root      20   0 2861944 414752  15116 S  0.0 22.0   0:05.71 Sweeper thread           
    

    转换线程ID 转为16进制 因为线程id在dump文件中是以16进制方式显示的

    # 用科学计算器 或者linux命令行转化 
    [root@localhost java]# printf %x 4776
    12a8
    

    使用jstack dump进程的信息

    jstack 4774 > 1.txt
    

    通过命令查看dump信息内的线程信息id去确定代码块

    cat 1.txt | grep -A 30 12ab # 查看相关代码信息
    
    "http-nio-8080-exec-1" #109 daemon prio=5 os_prio=0 tid=0x000000002cc29000 nid=0x12a8 runnable [0x00000000450eb000]
       java.lang.Thread.State: RUNNABLE
    	at java.io.FileOutputStream.writeBytes(Native Method)
    	at java.io.FileOutputStream.write(FileOutputStream.java:326)
    	at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
    	at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
    	- locked <0x00000005c22ac540> (a java.io.BufferedOutputStream)
    	at java.io.PrintStream.write(PrintStream.java:482)
    	- locked <0x00000005c22ac520> (a java.io.PrintStream)
    	at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
    	at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
    	at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
    	- locked <0x00000005c22ac668> (a java.io.OutputStreamWriter)
    	at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
    	at java.io.PrintStream.write(PrintStream.java:527)
    	- locked <0x00000005c22ac520> (a java.io.PrintStream)
    	at java.io.PrintStream.print(PrintStream.java:669)
    	at java.io.PrintStream.println(PrintStream.java:806)
    	- locked <0x00000005c22ac520> (a java.io.PrintStream)
    	at com.gosuncn.controller.test.TestDolistController.test(TestDolistController.java:30)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    

内存溢出

模拟代码

/**
 * - Xms20m -Xmx20m - -XX: +HeapDumpOnOutO fMemoryError  # 配置的vm参数便于产生内存溢出
 * 当发生溢出情况后 会在项目根目录下自动产生堆Dump文件 格式为 java_pid<pid>.hprof  例如:java_pid149824.hprof
 * 
 */ 
public class HeapOOMTest {
    private List<String> oomList = new ArrayList<>();

    public static void main(String[] args) {
        HeapOOMTest oomTest = new HeapOOMTest();
        while (true) {
            oomTest.oomList.add(UUID.randomUUID().toString());
        }
    }
}

使用MAT工具打开堆dump文件去进行分析

image.png 点击Leak Suspects

image.png 通过分析可以看到Leak Suspects com.gosuncn.generator.HeapOOMTest.main这个方法可能存在问题

查看支配树中的对象信息内存占用最多对象是个Object数组 image.png 点击查看对象被谁引用

  • outgoing references 是自己调用别人
  • incoming references 是别人调用自己

image.png 通过详情查询到 oomList调用了该数组

image.png 通过点击See stacktrace 也可以查看调用栈信息

image.png

image.png

原因

  • 内存泄漏 借助工具MAT或者jVisualVM来排查对象到对象的引用链,方便快速排查泄漏的问题代码
  • 非内存泄漏 内存中的对象都是必须存活的,这种情况下要根据内存的大小去配置Xms Xms的大小,为应用分配更多的堆内存,可以监测代码,是否对象的生命周期太长,和存储结构不合理,有的时候换不同的存储结构去存储对象,能节约很多内存

栈内存溢出

Java虚拟机规范

  • 如果线程请求的栈深度大于虚拟机锁允许的最大深度,将抛出StackOverflowError
  • 如果虚拟机栈内存允许动态扩展,当无法申请到足够内存时,将抛出OutOfMemoryError HotSpot 虚拟机
  • 占内存不可扩展
  • 统一用Xss设置栈的大小(不区分虚拟机栈和本地方法栈)
    • 有的虚拟机可以用Xss设置虚拟机栈,Xoss设置本地方法栈

直接内存溢出

什么是直接内存?

  • 直接内存是一块由操作系统直接管理的内存,也叫堆外内存,IO效率高 为什么要有直接内存?
  • 性能优势:直接内存读取速度比堆内存要快很多
  • 堆内存和直接内存对比:developer.aliyun.com/article/326… 什么场景使用直接内存?
  • 有很大的数据需要存储,并且声明周期很长
  • 频繁的IO操作,比如并发网络通讯就使用的直接内存 如何使用直接内存?
  • 可以使用Unsafe或者ByteBuffer分配直接内存
    • Unsafe.allocateMemory(size);
    • ByteBuffer.allocateDirect(size);
  • 可用-XX:MaxDirectMemorySize控制,默认是0,表示不限制
    • 该配置对Unsafe类不起作用
  • www.jb51.net/article/140…
  • blog.csdn.net/weixin_3413…
  • www.jianshu.com/p/dd2be4d3b…

代码缓存区溢出

代码缓存区默认大小是240m。当代码缓存区满了他会导致及时编译器(JIT)停止工作,已经编译的代码会继续以编译方式执行,但是没有编译过的代码还是以解析方式执行,所以会导致项目性能下降。因为编译执行比解释执行快。

解释执行: 由解释器根据输入的数据当场执行而不生成任何的目标程序. 解释执行,它解释一句就执行一句,不形成目标程序,输入一条命令语句,解释程序就立即将此语句解释成一条或几条指令并提交硬件立即执行且将执行结果反映到终端,就能立即得到计算结果。但解释程序执行速度很慢,例如源程序中出现循环,则解释程序也重复地解释并提交执行这一组语句,这就造成很大浪费时间。
编译执行: 先将源代码编译成目标语言(如:机器语言)之后通过连接程序连接到生成的目标程序进行执行 编译程序工作时,先分析,后综合,从而得到目标程序。它会将所有的源代码进行编译,优化等,然后一次性执行。 编译语言需要编译一次,运行直接执行、不需要翻译,所以编译型语言的程序执行效率高。而解释语言则不同,解释型语言的程序不需要编译,省了道工序,解释性语言在运行程序的时候才翻译。这样解释性语言每执行一次就要翻译一次,效率比较低。 代码缓存区参数配置

属性作用默认值
-XX:initialCodeCacheSize设置代码缓存区的初始大小,以java -XX: +PrintFlagsFinal | grep InitialCodeCacheSize结果为准不同操作系统、不同编译器的值不同
-XX:ReservedCodeCacheSize设置代码缓存区的最大大小,以java -XX: +PrintFlagsFinal | grep ReservedCodeCacheSize结果为准不同版本不同,jJDK 864位、jDK 11 64位都是240M
-XX:-PrintCodeCache在JVM停止时打印代码缓存的使用情况关闭
-XX:-PrintCodeCacheOnCompilation每当方法被编译后,就打印一下代码缓存区的使用情况关闭
-XX:+UseCodeCacheFlushing代码缓存区即将耗尽时,尝试回收一些早期编译、很久未被调用的方法打开
X:SegmentedCodeCache是否使用分段的代码缓存区,默认关闭,表示使用整体的代码缓存区关闭

TLAB

TLAB是什么?

  • TLAB全称(Thread Local ALlocation Buffer)即线程私有分配缓存区。
  • 是一块线程专用的内存分配区域,JVM会为每个线程分配一块TLAB区域,可以避免线程分配冲突,并提升对象分配效率,占用Eden区的空间。
  • TLAB空间小,如果对象过大,在TLAB中无法分配,只能直接分配到线程共享的堆中

image.png

推荐工具

opts.console.heapdump.cn/