1.JVM 组成
1.参考 腾讯课堂 jvm 教程,该课程详解了 jvm 组成和垃圾收集机制,调优工具 jvisualvm 和 arthas 基本使用,以及 jvm 调优的相关场景(最后一节可以不看,大部分时长都是在推销课程)
2. JVM 内部组成关系图
3. JVM 常用参数
1.堆栈相关配置
-Xmx512m 整堆最大为512MB,超出报OOM
-Xmn256m 新生代大小,新生代太小会频繁full GC
-Xms256m 初始堆大小
-Xss256k 线程栈最大值,栈太小容易发生栈溢出
-XX:NewRatio = 2 默认新生代占整堆1/3,老年代占2/3,老年代:新生代 = 2:1
-XX:SurvivorRatio = 8 默认Eden区占整个新生代4/5,Eden区:s0:s1 = 8:1:1
2.配置垃圾收集器
-XX:UseSerialGC 新生代使用serial,老年代使用serial old
-XX:UseParallelGC 新生代使用Parallel Scanvenge,老年代使用Parallel Old
-XX:UseParNewGC:新生代使用 ParNew,老年代使用CMS
-XX:UseConcMarkSweepGC:老年代使用CMS
-XX:UseG1GC:使用 G1 收集器
3.条件触发配置
-XX:+HeapDumpOnOutOfMemoryError 当发生内存溢出时导出堆信息到指定文件
-XX:-HeapDumpOnOutOfMemoryError 取消上面的配置
-XX:+HeapDumpBeforeFullGC Full GC 前导出堆信息到指定文件
-XX:+HeapDumpAfterFullGC FUll GC 后...
-XX: HeapDumpPath 堆信息导出路径,与上面两个命令联合使用
- 设置JVM参数的方式
-
IDEA 里面设置
-
java -jar …jar 命令启动时设置
java -jar -Xmx512m -XX:NewRatio=1 app.jar
2.准备工作
1.编写一个会产生死锁的类 LockTest.java
和 一个会报内存溢出的类 GCTest.java
,运行
- LockTest
import java.util.Date;
public class LockTest {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
LockA la = new LockA();
new Thread(la).start();
LockB lb = new LockB();
new Thread(lb).start();
}
}
class LockA implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockA 开始执行");
while(true){
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockA 锁住 obj1");
Thread.sleep(3000); // 此处等待是给B能锁住机会
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockA 锁住 obj2");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockB implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockB 开始执行");
while(true){
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockB 锁住 obj2");
Thread.sleep(3000); // 此处等待是给A能锁住机会
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockB 锁住 obj1");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- GCTest
import java.util.ArrayList;
public class GCtest {
byte[] arr = new byte[1024*100];
public static void main(String[] args) throws InterruptedException {
ArrayList<GCtest> gctests = new ArrayList<>();
while(true){
gctests.add(new GCtest());
Thread.sleep(10);
}
}
}
3.jdk 内置工具 jps、jmap、jstat、jstack
这些命令可以在 cmd 命令行直接使用,如果报jxx 不是内部命令或外部命令...
错误,可参考这篇文章解决
- jps(JVM Process Status Tool):用于输出当前正在运行的 JVM 进程信息
jps -l
输出主类的全名,如果进程执行的是Jar包,输出Jar路径
- jmap(Memory Map for Java):用于生成堆转储快照(一般称为heapdump或dump文件)
- 查看运行的JAVA进程堆内存使用情况:
jmap -heap 102144
- 手动导出dump:
jmap -dump:format=b,file=heap.hprof 102144
- jstat:用来监视VM内存内的各种堆和非堆的大小及其内存使用量,以及加载类的数量
jstat -class 102144 1000 10
(每隔1秒监控一次,一共做10次):统计class loader行为信息
jstat -gc 102144 2000 20
(每隔2秒监控一次,共20次):统计jdk gc时heap信息
- jstack:可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码
jstack pid
4.jvisualvm
上面的命令比较繁琐,且命令行界面不够简洁明了,JVisualVM是JDK自带的性能检测工具,路径在%JAVA_HOME%/bin下面。双击打开,可以看到当前在本地正在运行的jvm进程,点击上面写的那个死锁类,可以看到该进程的cpu、内存、类加载、线程信息
线程监视:点击线程即可当前进程所有的线程,右下角根据不同的颜色可以区分线程的不同状态,可以看到jvisualvm监测到死锁,点击右边的 线程Dump,可以定位到死锁代码
jvisualvm 还可以解析jmap命令产生的dump文件,找到dump文件,改为*.hprof
后缀,点击左上角的 文件–>装入
文件类型改为Dump 堆,找到生成的dump文件,确定,即可解析dump文件
5.arthas
上面的工具都是jdk自带的,在没有其他工具可用的情况下使用,一般情况下,在现在排查线上问题用arthas 比较多
arthas 安装,下载 arthas的 jar 包后,java -jar arthas-boot.jar
命令运行即可运行arthas,选择要监测的进程,出现如下页面表示启动成功
arthas 常用命令
-
dashboard
展示当前进程的信息
-
thread 1
会打印线程ID 1的栈,通常是main函数的线程
-
jad
反编译:jad jvm.GCTest
,可用来检验自己写的代码是否被运维正确发布
其他arthas进阶使用,可参考 arthas 文档
6.常见线上调优场景
-
线上几分钟就进行一次 Full GC,由于 Stop The World 造成系统频繁卡顿
可能的原因:新生代大小不够,导致大对象直接进入进入老年代,很快占满老年代导致 Full GC
解决办法:调大新生代大小 -
发生 OutOfMemory Error
可能的原因:有部分对象过大,比如从数据库查出几十万条数据,将结果放在 ArrayList 里面,导致list过大
解决办法:使用 jmap 手动输出 dump 文件,使用 mat 或者 jvisualvm 解析dump 文件,查看堆内存使用情况,定位大对象 -
监测到进程内有大量线程处于阻塞状态
可能的原因:多线程环境下,锁的粒度太大,锁住了不该锁住的资源,导致大量线程阻塞
解决办法:定位到问题代码所在位置,只锁该锁的资源,减小锁粒度 -
线程死锁
jvisualvm 可以发现死锁并定位到死锁位置