1. JVM
调优的参数可以在哪里设置参数值?
1.1 tomcat
的设置vm
参数
修改TOMCAT_HOME/bin/catalina.sh
文件,如下图:
JAVA_OPTS="-Xms512m -Xmx1024m"
1.2 springboot
项目jar
文件启动
通常在linux
系统下直接加参数启动springboot
项目。
nohup java -Xms512m -Xmx1024m -jar xxxx.jar --spring.profiles.active=prod &
nohup
: 用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行。参数 & :让命令在后台执行,终端退出后命令仍旧执行。
2. 用的JVM
调优的参数都有哪些?
对于JVM
调优,主要就是调整年轻代、年老大、元空间的内存空间大小及使用的垃圾回收器类型。
https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html
1)设置堆的初始大小和最大大小,为了防止垃圾收集器在初始大小、最大大小之间收缩堆而产生额外的时间,通常把最大、初始大小设置为相同的值。
-Xms:设置堆的初始化大小
-Xmx:设置堆的最大大小
2) 设置年轻代中Eden
区和两个Survivor
区的大小比例。该值如果不设置,则默认比例为8:1:1。Java
官方通过增大Eden
区的大小,来减少YGC
发生的次数,但有时我们发现,虽然次数减少了,但Eden
区满。
的时候,由于占用的空间较大,导致释放缓慢,此时STW
的时间较长,因此需要按照程序情况去调优。
-XXSurvivorRatio=3,表示年轻代中的分配比率:survivor:eden = 2:3
3)年轻代和老年代默认比例为1:2。可以通过调整二者空间大小比率来设置两者的大小。
-XX:newSize 设置年轻代的初始大小
-XX:MaxNewSize 设置年轻代的最大大小, 初始大小和最大大小两个值通常相同
4)线程堆栈的设置:每个线程默认会开启1M
的堆栈,用于存放栈帧、调用参数、局部变量等,但一般256K
就够用。通常减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。
-Xss 对每个线程stack大小的调整,-Xss128k
5)一般来说,当survivor
区不够大或者占用量达到50%,就会把一些对象放到老年区。通过设置合理的eden
区,survivor
区及使用率,可以将年轻对象保存在年轻代,从而避免full GC
,使用-Xmn
设置年轻代的大小。
6)系统CPU
持续飙高的话,首先先排查代码问题,如果代码没问题,则咨询运维或者云服务器供应商,通常服务器重启或者服务器迁移即可解决。
7)对于占用内存比较多的大对象,一般会选择在老年代分配内存。如果在年轻代给大对象分配内存,年轻代内存不够了,就要在eden
区移动大量对象到老年代,然后这些移动的对象可能很快消亡,因此导致full GC
。通过设置参数:-XX:PetenureSizeThreshold=1000000
,单位为B
,标明对象大小超过1M
时,在老年代(tenured
)分配内存空间。
8)一般情况下,年轻对象放在eden
区,当第一次GC
后,如果对象还存活,放到survivor
区,此后,每GC
一次,年龄增加1,当对象的年龄达到阈值,就被放到tenured
老年区。这个阈值可以同构-XX:MaxTenuringThreshold
设置。如果想让对象留在年轻代,可以设置比较大的阈值。
(1)-XX:+UseParallelGC:年轻代使用并行垃圾回收收集器。这是一个关注吞吐量的收集器,可以尽可能的减少垃圾回收时间。
(2)-XX:+UseParallelOldGC:设置老年代使用并行垃圾回收收集器。
9)尝试使用大的内存分页:使用大的内存分页增加CPU
的内存寻址能力,从而系统的性能。
-XX:+LargePageSizeInBytes 设置内存页的大小
10)使用非占用的垃圾收集器。
-XX:+UseConcMarkSweepGC老年代使用CMS收集器降低停顿。
3. 说一下JVM
调优的工具?
3.1 命令工具
3.1.1 jps
(Java Process Status
)
输出JVM
中运行的进程状态信息(现在一般使用jconsole
)。
3.1.2 jstack
查看java
进程内线程的堆栈信息。
jstack [option] <pid>
java
案例
package com.dcxuexi.jvm;
public class Application {
public static void main(String[] args) throws InterruptedException {
while (true){
Thread.sleep(1000);
System.out.println("哈哈哈");
}
}
}
使用jstack
查看进行堆栈运行信息。
3.1.3 jmap
用于生成堆转存快照。
jmap [options] pid
内存映像信息
jmap -heap pid
显示Java
堆的信息
jmap -dump:format=b,file=heap.hprof pid
format=b
表示以hprof
二进制格式转储Java
堆的内存
file=<filename>
用于指定快照dump
文件的文件名。
例:显示了某一个java
运行的堆信息。
PS D:\dcbut.coding> jmap -heap 34420
Attaching to process ID 34420, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12
using thread-local object allocation.
Parallel GC with 8 thread(s) //并行的垃圾回收器
Heap Configuration: //堆配置
MinHeapFreeRatio = 0 //空闲堆空间的最小百分比
MaxHeapFreeRatio = 100 //空闲堆空间的最大百分比
MaxHeapSize = 4265607168 (4068.0MB) //堆空间允许的最大值
NewSize = 89128960 (85.0MB) //新生代堆空间的默认值
MaxNewSize = 1421869056 (1356.0MB) //新生代堆空间允许的最大值
OldSize = 179306496 (171.0MB)//老年代堆空间的默认值
NewRatio = 2 //新生代与老年代的堆空间比值,表示新生代:老年代=1:2
SurvivorRatio = 8 //两个Survivor区和Eden区的堆空间比值为8,表示S0:S1:Eden=1:1:8
MetaspaceSize = 21807104 (20.796875MB) //元空间的默认值
CompressedClassSpaceSize = 1073741824 (1024.0MB) //压缩类使用空间大小
MaxMetaspaceSize = 17592186044415 MB //元空间允许的最大值
G1HeapRegionSize = 0 (0.0MB)//在使用 G1 垃圾回收算法时,JVM 会将 Heap 空间分隔为若干个 Region,该参数用来指定每个 Region 空间的大小。
Heap Usage:
PS Young Generation
Eden Space: //Eden使用情况
capacity = 67108864 (64.0MB)
used = 36235792 (34.55714416503906MB)
free = 30873072 (29.442855834960938MB)
53.995537757873535% used
From Space: //Survivor-From 使用情况
capacity = 11010048 (10.5MB)
used = 8700008 (8.296974182128906MB)
free = 2310040 (2.2030258178710938MB)
79.01880173456101% used
To Space: //Survivor-To 使用情况
capacity = 11010048 (10.5MB)
used = 0 (0.0MB)
free = 11010048 (10.5MB)
0.0% used
PS Old Generation //老年代 使用情况
capacity = 179306496 (171.0MB)
used = 16384 (0.015625MB)
free = 179290112 (170.984375MB)
0.009137426900584795% used
8724 interned Strings occupying 751096 bytes.
PS D:\dcbut.coding>
3.1.4 jhat
用于分析jmap
生成的堆转存快照(一般不推荐使用,而是使用Ecplise Memory Analyzer
)。
3.1.5 jstat
是JVM
统计监测工具。可以用来显示垃圾回收信息、类加载信息、新生代统计信息等。
常见参数:
①总结垃圾回收统计
jstat -gcutil pid
字段 | 含义 |
---|---|
S0 | 幸存1区当前使用比例 |
S1 | 幸存2区当前使用比例 |
E | 伊甸园区使用比例 |
O | 老年代使用比例 |
M | 元数据区使用比例 |
CCS | 压缩使用比例 |
YGC | 年轻代垃圾回收次数 |
YGCT | 年轻代垃圾回收消耗时间 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收消耗时间 |
GCT | 垃圾回收消耗总时间 |
②垃圾回收统计
jstat -gc pid
3.2 可视化工具
3.2.1 jconsole
Jconsole
是JDK
自带的监控工具,在JDK/bin
目录下可以找到。它用于连接正在运行的本地或者远程的JVM
,对正在运行。
用于对jvm
的内存,线程,类 的监控,是一个基于jmx
的GUI
性能监控工具。
打开方式:
-
通过
cmd
命令行(直接输入jconsole
)打开 -
JDK
安装目录bin
目录下 直接启动jconsole.exe
就行。
可以内存、线程、类等信息。
3.2.2 VisualVM
:故障处理工具
能够监控线程,内存情况,查看方法的CPU
时间和内存中的对 象,已被GC
的对象,反向查看分配的堆栈。
打开方式:JDK
安装目录bin
目录下 直接启动jvisualvm.exe
就行。
监控程序运行情况。
查看运行中的dump
。
查看堆中的信息。
4. java
内存泄露的排查思路?
原因:
如果线程请求分配的栈容量超过java
虚拟机栈允许的最大容量的时候,java
虚拟机将抛出一个StackOverFlowError
异常。
如果java
虚拟机栈可以动态拓展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成拓展,或者在建立新线程的时候没有足够的内存去创建对应的虚拟机栈,那java
虚拟机将会抛出一个OutOfMemoryError
异常。
如果一次加载的类太多,元空间内存不足,则会报OutOfMemoryError: Metaspace
。
1、通过jmap
指定打印他的内存快照dump
。
有的情况是内存溢出之后程序则会直接中断,而
jmap
只能打印在运行中的程序,所以建议通过参数的方式的生成dump
文件,配置如下:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/app/dumps/
指定生成后文件的保存目录
2、通过工具, VisualVM
(Ecplise MAT
)去分析dump
文件。
VisualVM
可以加载离线的dump
文件,如下图:
文件–>装入—>选择dump
文件即可查看堆快照信息。
如果是
linux
系统中的程序,则需要把dump
文件下载到本地(windows
环境)下,打开VisualVM
工具分析。VisualVM
目前只支持在windows
环境下运行可视化
3、通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题。
4、找到对应的代码,通过阅读上下文的情况,进行修复即可。
5. CPU
飙高排查方案与思路?
1.使用top
命令查看占用cpu
的情况。
2.通过top
命令查看后,可以查看是哪一个进程占用cpu
较高,上图所示的进程为:30978。
3.查看当前线程中的进程信息。
ps H -eo pid,tid,%cpu | grep 30978
pid
进行id
tid
进程中的线程id
%
cpu
使用率
4.通过上图分析,在进程30978中的线程30979占用cpu
较高。
注意:上述的线程
id
是一个十进制,我们需要把这个线程id
转换为16进制才行,因为通常在日志中展示的都是16进制的线程id
名称
转换方式:
在linux
中执行命令:
printf "%x\n" 30979
5.可以根据线程id
找到有问题的线程,进一步定位到问题代码的源码行号。
执行命令:
jstack 30978 此处是进程id
6. 面试现场
6.1 JVM
调优的参数可以在哪里设置参数值?
我们当时的项目是springboot
项目,可以在项目启动的时候,java -jar
中加入参数就行了。
6.2 用的JVM
调优的参数都有哪些?
嗯,这些参数是比较多的。
我记得当时我们设置过堆的大小,像-Xms
和-Xmx
。
还有就是可以设置年轻代中Eden
区和两个Survivor
区的大小比例。
还有就是可以设置使用哪种垃圾回收器等等。具体的指令还真记不太清楚。
6.3 你们平时调试JVM
都用了哪些工具呢?
嗯,我们一般都是使用jdk
自带的一些工具,比如:
jps
输出JVM
中运行的进程状态信息。
jstack
查看java
进程内线程的堆栈信息。
jmap
用于生成堆转存快照。
jstat
用于JVM
统计监测工具。
还有一些可视化工具,像jconsole
和VisualVM
等。
6.4 假如项目中产生了java
内存泄露,你说一下你的排查思路?
嗯,这个我在之前项目排查过。
第一,可以通过jmap
指定打印他的内存快照dump
文件,不过有的情况打印不了,我们会设置vm
参数让程序自动生成dump
文件。
第二,可以通过工具去分析dump
文件,jdk
自带的VisualVM
就可以分析。
第三,通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题。
第四,找到对应的代码,通过阅读上下文的情况,进行修复即可。
6.5 那现在再来说一种情况,就是说服务器CPU
持续飙高,你的排查方案与思路?
可以这么做~~
第一可以使用使用top
命令查看占用cpu
的情况。
第二通过top
命令查看后,可以查看是哪一个进程占用cpu
较高,记录这个进程id
。
第三可以通过ps
查看当前进程中的线程信息,看看哪个线程的cpu
占用较高。
第四可以jstack
命令打印进行的id
,找到这个线程,就可以进一步定位问题代码的行号。