目录
1. JDK工具包
1.1 jps
1.2 jstat
1.3 jinfo
1.4 jmap
1.5 jhat
1.6 jstack
1.7 VisualVM
2. 第三方工具
2.1 GCEasy
2.2 MAT
2.3 Arthas
3. JVM参数
3.1 标准参数
3.2 非标准参数
3.3 不稳定参数
4. 调优
4.1 什么时候调优
4.2 调优调什么
4.3 调优原则
4.4 调优步骤
4.5 调优参数设置
4.6 Young GC 日志含义
4.7 FullGC 日志含义
4.8 堆内存跟元空间优化
4.9 线程堆栈优化
4.10 垃圾回收器优化
4.11 案例
1. JDK工具包
1.1 jps
查看Java进程 ,相当于Linux下的ps命令,只不过它只列出Java进程。
jps :列出Java程序进程ID和Main函数名称
jps -q :只输出进程ID
jps -m :输出传递给Java进程(主函数)的参数
jps -l :输出主函数的完整路径
jps -v :显示传递给Java虚拟的参数
1.2 jstat
jstat可以查看Java程序运行时相关信息,可以通过它查看运行时堆信息的相关情况。
jstat -<options> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
options:由以下值构成
-class:显示ClassLoader的相关信息
-compiler:显示JIT编译的相关信息
-gc:显示与GC相关信息
-gccapacity:显示各个代的容量和使用情况
-gccause:显示垃圾收集相关信息(同-gcutil),同时显示最后一次或当前正在发生的垃圾收集的诱发原因
-gcnew:显示新生代信息
-gcnewcapacity:显示新生代大小和使用情况
-gcold:显示老年代信息
-gcoldcapacity:显示老年代大小
-gcpermcapacity:显示永久代大小
-gcutil:显示垃圾收集信息
jstat -gc 18551 250 4
# 进程ID 34784 ,采样间隔250ms,采样数4
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:metaspace的容量 (单位kb)
MU:metaspace目前已使用空间 (单位kb)
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC :从应用程序启动到采样时年轻代中gc次数
YGCT :从应用程序启动到采样时年轻代中gc所用时间(s)
FGC :从应用程序启动到采样时old代(全gc)gc次数
FGCT :从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
jstat -gcutil
下面输出的是进程内存区域百分百 及 GC详细信息
jstat -gcutil 30108 1s 5
# 进程ID 30108,采样间隔1s,采样数5
S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E 年轻代中Eden(伊甸园)已使用的占当前容量百分比
O old代已使用的占当前容量百分比
M metaspace已使用的占当前容量百分比
CCS 压缩使用比例
YGC 从应用程序启动到采样时年轻代中gc次数
YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)
FGC 从应用程序启动到采样时old代(全gc)gc次数
FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT 从应用程序启动到采样时gc用的总时间(s)
1.3 jinfo
jinfo可以用来查看正在运行的Java程序的扩展参数,甚至支持修改运行过程中的部分参数
jinfo [option] <pid>
-flags 打印虚拟机 VM 参数
-flag <name> 打印指定虚拟机 VM 参数
-flag [+|-]<name> 打开或关闭虚拟机参数
-flag <name>=<value> 设置指定虚拟机参数的值
jinfo -flags 18551
1.4 jmap
jmap用来查看堆内存使用状况,一般结合jhat使用。
jmap pid
描述:查看进程的内存映像信息。使用不带选项参数的jmap打印共享对象映射,将会打印目标虚拟机中加载的每个共享对象的 起始地址、映射大小以及共享对象文件的路径全称。
jmap -heap pid
显示Java堆详细信息:打印堆的摘要信息,包括使用的GC算法、堆配置信息和各内存区域内存使用信息
jmap -histo:live pid
描述:显示堆中对象的统计信息:其中包括每个Java类、对象数量、内存大小(单位:字节)、完全限定的类名。打印的虚拟机内 部的类名称将会带有一个’*’前缀。如果ja指定了live子选项,则只计算活动的对象
jmap -clstats pid
描述:打印类加载器信息:打印Java堆内存的方法区的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、 地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。
jmap -finalizerinfo pid
描述:打印等待终结的对象信息
jmap -dump:format=b,file=heapdump.hprof pid
描述:生成堆转储快照dump文件:以二进制格式转储Java堆到指定文件中。如果指定了live子选项,堆中只有活动的对象会被转 储。浏览heap dump 可以使用jhat 读取生成的文件,也可以使用MAT等堆内存分析工具。
1.5 jhat
jhat 命令会解析Java堆转储文件,并启动一个 web server。然后用浏览器来查看/浏览 dump 出来的 heap二进制文件。
jhat 命令支持预先设计的查询,比如:显示某个类的所有实例。还支持 对象查询语言(OQL)。 OQL有点类似SQL,专门用来 查询堆转储。
Java生成堆转储的方式有多种:
- 使用 jmap -dump 选项可以在JVM运行时获取 dump.
- 使用 jconsole 选项通过 HotSpotDiagnosticMXBean 从运行时获得堆转储。
- 在虚拟机启动时如果指定了 -XX:+HeapDumpOnOutOfMemoryError 选项,则抛出 OutOfMemoryError 时,会自动执行堆转 储
jhat [ options ] heap-dump-file
1.6 jstack
stack是Java虚拟机自带的一种堆栈跟踪工具,用于生成java虚拟机当前时刻的线程快照。
线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因, 如线程间死锁、死循环、请求外部资源导致的长时间等待、等等。
线程快照里留意下面几种状态
死锁,Deadlock(重点关注)
等待资源,Waiting on condition(重点关注)
等待获取管程,Waiting on monitor entry(重点关注)
阻塞,Blocked(重点关注)
执行中,Runnable
暂停,Suspended
对象等待中,Object.wait() 或 TIMED_WAITING
停止,Parked
jstack [ option ] pid 查看当前时间点,指定进程的dump堆栈信息。
jstack [ option ] pid > 文件 将当前时间点的指定进程的dump堆栈信息,写入到指定文件中。
# 注:若该文件不存在,则会自动生成; 若该文件存在,则会覆盖源文件。
jstack [ option ] executable core 查看当前时间点,core文件的dump堆栈信息。
jstack [ option ] [server_id@]<remote server IP or hostname> 查看当前时间点,远程机器的dump堆栈信息。
-F # 当进程挂起了,此时'jstack [-l] pid'是没有相应的,这时候可使用此参数来强制打印堆栈信息,强制jstack),一般情况不
需要使用。
-m # 打印java和native c/c++框架的所有栈信息。可以打印JVM的堆栈,以及Native的栈帧,一般应用排查不需要使用。
-l # 长列表. 打印关于锁的附加信息。例如属于java.util.concurrent的ownable synchronizers列表,会使得JVM停顿得长久
得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。一般情况不需
要使用。
-h or -hel # 打印帮助信息
jstack pid 打印堆栈
jstack -l 30108 | grep 'java.lang.Thread.State' | wc -l
1.7 VisualVM
VisualVM 是一款免费的性能分析工具。它通过 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多种方式从程序运行时 获得实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高 性能分析的精度。
2. 第三方工具
2.1 GCEasy
业界首先采用机器学习算法解决GC日志分析问题,GCeasy内置机器智能可以自动检测JVM和Android GC日志中的问题,并推荐解决 方案。
https://gceasy.io/
2.2 MAT
MAT是一个强大的可视化内存分析工具,可以快捷、有效地帮助我们找到内存泄露,减少内存消耗分析工具。MAT是Memory Analyzer tool的缩写,是一种快速,功能丰富的Java堆分析工具,能帮助你查找内存泄漏和减少内存消耗。
功能:
找到最大的对象,因为MAT提供显示合理的累积大小(retained size)
探索对象图,包括inbound和outbound引用,即引用此对象的和此对象引出的
查找无法回收的对象,可以计算从垃圾收集器根到相关对象的路径
找到内存浪费,比如冗余的String对象,空集合对象。
MAT安装有两种方式,一种是以eclipse插件方式安装,一种是独立安装。
在MAT的官方文档中有相应的安装文件下载,下载地址为:https://www.eclipse.org/mat/downloads.php
eclipse插件安装,help -> install new soft点击ADD,在弹出框中添加插件地址:http://download.eclipse.org/mat/1.9.0/upda te-site/,也可以直接在下载页面下载离线插件包,以离线方式安装。
独立安装:下载解压包,解压即安装
2.3 Arthas
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下, 对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
# 下载arthas-boot.jar
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
# 启动
java -jar arthas-boot.jar
# 选择应用java进程:
[INFO] arthas-boot version: 3.5.3
[INFO] Found existing java process, please choose one and input the serial number of the
process, eg : 1. Then hit ENTER.
* [1]: 31480 org.apache.catalina.startup.Bootstrap
[2]: 30108 /root/hero_web-1.0-SNAPSHOT-default.jar
# Demo进程是第2个,则输入2,再输入回车/enter。Arthas会attach到目标进程上,并输出日志:
[INFO] Start download arthas from remote server: https://arthas.aliyun.com/download/3.5.4?
mirror=aliyun
[INFO] Download arthas success.
[INFO] arthas home: /root/.arthas/lib/3.5.4/arthas
[INFO] Try to attach process 30108
[INFO] Attach process 30108 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.5.4
main_class
pid 30108
time 2021-10-12 11:24:34
[arthas@30108]$
-
Arthas 常见命令
jvm:查看当前 JVM 的信息
thread:查看当前 JVM 的线程堆栈信息,
-b 选项可以一键检测死锁
-n 指定最忙的前N个线程并打印堆栈
trace:方法内部调用路径,并输出方法路径上的每个节点上耗时,服务间调用时间过长时使用
stack:输出当前方法被调用的调用路径
Jad:反编译指定已加载类的源码,反编译便于理解业务
logger:查看和修改 logger,可以动态更新日志级别 -
查看dashboard
输入dashboard,按 回车/enter ,会展示当前进程的信息,按 ctrl+c 可以中断执行
- 查看线程thread
通过thread命令来获取到应用进程的线程信息
thread -1 会打印线程统计信息。
- 反编译已加载类源码
运行期通过jad来反编译项目代码
- 监听运行时方法的返回值watch
通过watch命令来查看 com.hero.web.user.controller#UserController 函数的返回值:
watch com.hero.web.user.controller.UserController findByUsername returnObj
- 退出
如果只是退出当前的连接,可以用 quit 或者 exit 命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时 可以直接连接上。如果想完全退出arthas,可以执行 stop 命令。
3. JVM参数
标准参数、非标准参数、不稳定参数
3.1 标准参数
顾名思义,标准参数中包括功能以及输出的结果都是很稳定的,基本上不会随着JVM版本的变化而变化。标准参数以-开 头,如:java -version、java -jar等,通过java -help可以查询所有的标准参数,我们可以通过 -help 命令来检索出所有标准参数
3.2 非标准参数
非标准参数:以-X开头,是标准参数的扩展。表示在将来的JVM版本中可能会发生改变,但是这类以-X开始的参数变化比较小。 可以通过 Java -X 命令来检索所有-X 参数。
比较常见的非标准参数:
- -Xms堆最小值:默认值是总内存/64(且小于1G),默认情况下,当堆中可用内存小于40%(-XX: MinHeapFreeRatio调整) 时,堆内存会开始增加,一直增加到-Xmx大小。
- -Xmx堆最大值:默认值是总内存/64(且小于1G),如果Xms和Xmx都不设置,则两者大小会相同,默认情况下,当堆中可用 内存大于70%时,堆内存会开始减少,一直减小到-Xms的大小;
- -Xmn新生代内存:默认是整堆的1/3,包括Eden区和两个Survivor区的总和,写法如: -Xmn1024,-Xmn1024k,-Xmn1024m,- Xmn1g 。新生代按整堆的比例分配,所以,此值不建议设置!
- -Xss线程栈内存:默认1M,一般来说是不需要改的。
- 打印GC日志:-Xloggc:file与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中。
3.3 不稳定参数
不稳定参数:这也是非标准化参数,相对来说不稳定,随着JVM版本的变化可能会发生变化,主要用于JVM调优和debug。 不稳定参数以-XX 开头,此类参数的设置很容易引起JVM 性能上的差异,使JVM存在极大的不稳定性。如果此类参数设置合理将大大提 高JVM的性能及稳定性。
不稳定参数分为三类:
性能参数:用于JVM的性能调优和内存分配控制,如内存大小的设置;
行为参数:用于改变JVM的基础行为,如GC的方式和算法的选择;
调试参数:用于监控、打印、输出jvm的信息;
不稳定参数语法规则:
- 布尔类型参数值:
-XX:+
'+'表示启用该选项
-XX:-
'-'表示关闭该选项
示例:-XX:+UseG1GC:表示启用G1垃圾收集器 - 数字类型参数值:
-XX:
=给选项设置一个数字类型值,可跟随单位,例如:'m'或'M'表示兆字节;'k'或'K'千字节;'g'或'G'千兆字节。32K与32768是相同大小的。
示例:-XX:MaxGCPauseMillis=500 :表示设置GC的最大停顿时间是500ms - 字符串类型参数值:
-XX:
=给选项设置一个字符串类型值,通常用于指定一个文件、路径或一系列命令列表。
示例:-XX:HeapDumpPath=./dump.core
常用的不稳定参数:
-XX:+UseSerialGC 配置串行收集器
-XX:+UseParallelGC 配置PS并行收集器
-XX:+UseParallelOldGC 配置PO并行收集器
-XX:+UseParNewGC 配置ParNew并行收集器
-XX:+UseConcMarkSweepGC 配置CMS并行收集器
-XX:+UseG1GC 配置G1并行收集器
-XX:+PrintGCDetails 配置开启GC日志打印
-XX:+PrintGCTimeStamps 配置开启打印GC时间戳
-XX:+PrintGCDateStamps 配置开启打印GC日期
-XX:+PrintHeapAtGC 配置开启在GC时,打印堆内存信息
4. 调优
调优的最终目的都是为了应用程序使用最小的硬件消耗来承载更大的吞吐量。JVM调优主要是针对垃圾收集器的收集性能进行优化令 运行在虚拟机上的应用,能够使用更少的内存(Footprint),及更低的延迟(Latency),获取更大的吞吐量(Throughput)。
调优最终目标:
- 堆内存使用率 <= 70%;
- 老年代内存使用率<= 70%;
- avg pause <= 1秒;
- Full GC 次数0 或 avg pause interval >= 24小时 ;
4.1 什么时候调优
遇到以下情况,就需要考虑进行JVM调优:
- 系统吞吐量下降与响应延迟(P99);
- Heap内存(老年代)持续上涨至出现OOM;
- Full GC 次数频繁;
- GC 停顿过长(超过1秒);
- 应用出现OutOfMemory 等内存异常;
- 应用中有使用本地缓存且占用大量内存空间;
4.2 调优调什么
内存分配 + 垃圾回收!
- 合理使用堆内存
- GC高效回收占用的内存的垃圾对象
- GC高效释放掉内存空间
4.3 调优原则
优先原则:优先架构调优和代码调优,JVM优化是不得已的手段
大多数的Java应用不需要进行JVM优化
观测性原则:发现问题解决问题,没有问题不找问题
4.4 调优步骤
第一步:监控分析GC日志
第二步:判断JVM问题:
如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化 如果GC时间超过1秒,或者频繁GC,则必须优化。
第三步:确定调优目标
第四步:调整参数
调优一般是从满足程序的内存使用需求开始,之后是时间延迟需求,最后才是吞吐量要求,要基于这个步骤来不断优化,每 一个步骤都是进行下一步的基础,不可逆行之。
第五步:对比调优前后差距
第六步:重复:1、2、3、4、5步骤
找到最佳JVM参数设置
第七步:应用JVM参数到应用服务器:
找到最合适的参数,将这些参数灰度发布一部分机器,观察一段时间。
如果,观察结果可以验证方案的价值,则进行全量发布!
4.5 调优参数设置
JVM调优典型参数设置:
-Xms堆内存最小值
-Xmx堆内存最大值
-Xmn新生代内存的最大值
-Xss每个线程的栈内存
# 计算最大线程数的公式:理论上限
Number of threads = (MaxProcess内存 - JVM内存 - ReservedOsMemory) / (ThreadStackSize)
系统最大可创建的线程数量=(机器本身可用内存 - (JVM分配的堆内存+JVM元数据区)) / Xss的值
如果想要确定JVM性能问题瓶颈,需要分析GC日志
- -XX:+PrintGCDetails 开启GC日志创建更详细的GC日志,默认关闭
- -XX:+PrintGCTimeStamps,-XX:+PrintGCDateStamps 开启GC时间提示, 开启时间便于我们更精确地判断几次GC操作之间的时两个参数的区别
时间戳是相对于0(依据JVM启动的时间)的值,而日期戳(date stamp)是实际的日期字符串 由于日期戳需要进行格式化,所以它的效率可能会受轻微的影响,不过这种操作并不频繁,它造成的影响也很难被我们感 知。 - -XX:+PrintHeapAtGC 打印堆的GC日志
- -Xloggc:./logs/gc.log 指定GC日志路径
# 配置GC日志输出
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -
XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-default.log "
4.6 Young GC 日志含义
2021-05-18T14:31:11.340+0800: 2.340: [GC (Allocation Failure) [PSYoungGen: 896512K->41519K(1045504K)]
896512K-41543K(3435008K), 0.0931965 secs] [Times: user=0.14 sys=0.02, real=0.10 secs]
# 第一部分
2021-05-18T14:31:11.340+0800: 2.340: [GC (Allocation Failure)
GC事件开始时间,+0800代表中国所在的东区:2021-05-18T14:31:11.340+0800
GC事件开始时间相对于JVM开始启动的间隔秒数:2.340
区分YoungGC还是FullGC的标志,GC代表YoungGC
触发GC的原因: (Allocation Failure)
# 第二部分
[PSYoungGen: 896512K->41519K(1045504K)] 896512K-41543K(3435008K), 0.0931965 secs]
垃圾收集器的名称:PSYoungGen
在垃圾收集之前 和 之后新时代的使用量:896512K->41519K
新生代总空间大小:(1045504K)
在垃圾收集之前和之后整个堆内存使用量:896512K-41543K
堆总空间大小:(3435008K)
GC持续时间:0.0931965 secs
# 第三部分
[Times: user=0.14 sys=0.02, real=0.10 secs]
GC线程消耗的CPU时间:user=0.14
GC过程中操作系统调用和系统等待事件所消耗的时间:sys=0.02
应用程序暂停的时间:0.10 secs
4.7 FullGC 日志含义
2021-05-19T14:46:07.367+0800: 1.562: [Full GC (Metadata GC Threshold)[PSYoungGen: 18640K-
>0K(1835008K)] [ParOldGen: 16K->18327K(1538048K)] 18656K->18327K(3373056K), [Metaspace: 20401K-
>20398K(1069056K)], 0.0624559 secs] [Times: user=0.19 sys=0.00, real=0.06 secs]
# 第一部分
2021-05-19T14:46:07.367+0800: 1.562: [Full GC (Metadata GC Threshold)
GC事件开始时间,+0800代表中国所在的东区:
GC事件开始时间相对于JVM开始启动的间隔秒数:2.340
区分YoungGC还是FullGC的标志
触发GC的原因: (Allocation Failure)
# 第二部分
[PSYoungGen: 18640K->0K(1835008K)] [ParOldGen: 16K->18327K(1538048K)] 18656K->18327K(3373056K),
垃圾收集器的名称:PSYoungGen
在垃圾收集之前 和 之后新时代的使用量:18640K->0K
新生代总空间大小:(1835008K)
老年代垃圾收集器名称:ParOldGen
在垃圾收集之前和之后老年代的使用量:16K->18327K
老年代总空间大小:(1538048K)
在垃圾收集之前和之后整个堆内存使用量:18656K->18327K
堆总空间大小:(3373056K)
# 第三部分
[Metaspace: 20401K->20398K(1069056K)], 0.0624559 secs] [Times: user=0.19 sys=0.00, real=0.06 secs]
元空间区域垃圾收集器:Metaspace
在垃圾收集之前和之后元空间的使用量:20401K->20398K
元空间大小:(1069056K)
GC事件持续的时间:0.0624559 secs
GC线程消耗的CPU时间:user=0.19
GC过程中,操作系统调用和系统等待事件所消耗的时间:sys=0.00
应用程序暂停时间:real=0.06 secs
4.8 堆内存跟元空间优化
老年代的空间大小为 274MB【那些不容易消亡的老对象】
java heap:参数-Xms和-Xmx,建议扩大至3-4倍FullGC后的老年代空间占用。274 * (3-4) = (822-1096)MB ,设置heap大小为 1096MB,最好是8的整数倍;
元空间:参数-XX:MetaspaceSize=N,设置元空间大小为128MB;
新生代:参数-Xmn,建议扩大至1-1.5倍FullGC之后的老年代空间占用。274M*(1-1.5)=(274 -411)M,设置新生代大小为 411MB,最好是8的整数倍,因此改为408M;
# 调整参数,基于当前系统运行情况这是最佳配置
JAVA_OPT="${JAVA_OPT} -Xms1096m -Xmx1096m -Xmn408m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -
XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-best-heap-metaspace.log"
4.9 线程堆栈优化
对于不同版本的Java虚拟机和不同的操作系统,栈容量最小值可能会有所限制,这主要取决于操作系统内存分页大小。譬如上述方法 中的参数-Xss128k可以正常用于32位Windows系统下的JDK 6,但是如果用于64位Windows系统下的JDK 11,则会提示栈容量最小不 能低于180K,而在Linux下这个值则可能是228K,如果低于这个最小限制,HotSpot虚拟器启动时会给出如下提示:
The Java thread stack size specified is too small. Specify at least 228k
JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256。根据应用的线程所需内存大小进行调整。在相同物理内存下,减 小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成, 如果栈不是很深, 应该是256k够 用了,大的应用建议使用512k。
JAVA_OPT="${JAVA_OPT} -Xms1096m -Xmx1096m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -
XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-best-stack.log"
4.10 垃圾回收器优化
吞吐量优先ps+po
默认使用ps+po 垃圾回收器组合: 并行垃圾回收器组合
JAVA_OPT="${JAVA_OPT} -Xms256m -Xmx256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+UseParallelGC -XX:+UseParallelOldGC "
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -
XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-ps-po.log"
响应时间优先parnew+cms
使用cms垃圾回收器,垃圾回收器组合: parNew+CMS, cms垃圾回收器在垃圾标记,垃圾清除的时候,和业务线程交叉执行,尽量 减少stw时间,因此这垃圾回收器叫做响应时间优先;
JAVA_OPT="${JAVA_OPT} -Xms256m -Xmx256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+UseParNewGC -XX:+UseConcMarkSweepGC "
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -
XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-parnew-cms.log"
G1全功能
配置G1只需要简单三步即可:
- 第一步,开启G1垃圾收集器
- 第二步,设置堆的最大内存
- 第三步,设置最大的停顿时间
JAVA_OPT="${JAVA_OPT} -Xms256m -Xmx256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:MaxGCPauseMillis=100"
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -
XX:+PrintHeapAtGC -Xloggc:${BASE_DIR}/logs/gc-g-one.log"
对于G1垃圾收集器参数设置建议:
设置为100-300之间比较合理,不要设置的太短
堆内存小于2GB,不建议使用G1
4.11 案例
内存溢出的定位与分析
内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会 造成内存溢出。
如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应 该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。
首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap与MAT工具进行定位分析。 接下来,我们模拟内存溢出的场景。
模拟内存溢出
编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。
package com.hero.jvm.memory;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TestJvmOutOfMemory {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
String str = "";
for (int j = 0; j < 1000; j++) {
str += UUID.randomUUID().toString();
}
list.add(str);
}
System.out.println("ok");
}
}
#参数如下:
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
可以看到,当发生内存溢出时,会dump文件到java_pid31092.hprof
导入到MAT工具中进行分析
可以看到,有81.72%的内存由Object[]数组占有,所以比较可疑。
分析:这个可疑是正确的,因为已经有超过80%的内存都被它占有,这是非常有可能出现内存溢出的。
检测死锁
有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何 分析呢?
由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下JVM的内部线程的执行情况,然后再进行 分析查找出原因。
这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进行快照,并且打印出来:
jstack 18487 | grep 'BLOCKED' -A 15 --color
使用Arthas进行分析
thread -b
可以准确知道: 死锁在代码的中的第几行