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和其他工具对比
| 功能 | JHSDB | JCMD | 类似工具 |
|---|---|---|---|
| 展示JAVA进程 | 无 | jcmd | jps -lm |
| 堆DUmp | jhsdb jmap --binaryheap | jcmd pid GC.heap_dump | jmap -dump pid |
| 堆使用直方图 | jhsdb jmap --histo | jcmd pid GC.class_histogram | jmap -histo pid |
| 线程Dump | jhsdb jstack --locks (subset of locked thread frames) | jcmd pid Thread.print | jstack pid |
| 展示系统属性 | jhsdb jinfo --sysprops | jcmd pid VM.system_properties | jinfo -sysprops pid |
| 列出JVM标记 | jhsdb jinfo --flags | jcmd pid VM.flags | jinfo -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:0top -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文件去进行分析
点击Leak Suspects
通过分析可以看到Leak Suspects
com.gosuncn.generator.HeapOOMTest.main这个方法可能存在问题
查看支配树中的对象信息内存占用最多对象是个Object数组
点击查看对象被谁引用
- outgoing references 是自己调用别人
- incoming references 是别人调用自己
通过详情查询到 oomList调用了该数组
通过点击See stacktrace 也可以查看调用栈信息
原因
- 内存泄漏 借助工具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中无法分配,只能直接分配到线程共享的堆中