这个真的很烦。
先说下JVM调优,原则上不要过早调优,啥意思呢?就是JVM要是没有频繁的FGC就别调了没啥意思。
那多频繁FGC算频繁,要调了,业内有自己的标准,我就不哔哔了。笔者呢很幸运,从来没有碰到线上系统频繁的FGC。所以从来没调过。那这篇文章的意思是?说白了就是面试官喜欢问这个,然后网上有很多案例,说实话没法看,原因就是:他们的现场我们复现不了,他文章里面查的可乐呵了,我们是一脸懵。所以我这篇文章的过程就是:
自己写一个制造FGC的代码 -> 然后傻傻的装作自己不知道FGC发生的原因,一脸懵逼的去查。
至于笔者为什么没碰到过线上的FGC,就是笔者之前所在的技术团队实力很强。你网上能找到的FGC的CASE,团队都有经验,都没掉进去过。
{BANNED}中国第一步制造FGC
以下是制造FGC的代码
Hello类
-
package sty.zchi;
-
-
public class Hello {
-
private String name;
-
private byte[] aaa;
-
-
public Hello(String name) {
-
this.name = name;
-
this.aaa = new byte[1024 * 1024 * 400];
-
}
-
}
主类
-
package sty.zchi;
-
-
/**
-
* 模拟fgc问题
-
*/
-
public class JvStudy {
-
public static void main(String[] args) {
-
//创建一个内存快照DUMP的守护线程
-
JvThread jvThread = new JvThread();
-
Thread t = new Thread(jvThread);
-
t.start();
-
while(true) {
-
//申请一个巨大无比的局部变量
-
Hello h = new Hello("zchi");
-
//感觉有点多余,没必要写
-
byte[] MAXOBJ2 = new byte[1024 * 1024 * 70];
-
//一定要写,告诉JVM MAXOBJ废了没用了,如果没有那么下面一个巨大的MAXOBJ3进来后,就不是FGC了,就是OUTOFMEMORY了
-
h = null;
-
//又来一个巨大的局部变量,这个时候JVM发现年轻装不下了,就往年老的地方放,但年老也放满了,那就是开始FGC回收老年区吧
-
Hello h1 = new Hello("yuna");
-
System.out.println("hello world");
-
try {
-
Thread.sleep(1000);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
}
带上JVM参数。
-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=11M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -Xms512m -Xmx600m -Xmn100m
说几个和本次FGC有关的参数意思,其它的网上查下就知道了
-Xms 堆内存初始化大小
-Xmx 对内存{BANNED}最佳大的大小
-Xmn 年轻态大小
第二步发现FGC
好,我们开始装模做样的表示自己一无所知。现在开始要干活了。(其实我们自己早就知道FGC的原因就是巨大的局部变量)。
这个时候一开始系统一般是表现LOAD变高,RT变长等等。一开始你其实还步知道这是FGC导致的,但笔者很幸运,之前线上的系统都有很完备的监控。所以这个时候你的FGC报警也出现了。面试官如果问你怎么知道FGC导致RT变长的,你就直接回答我们对JVM的FGC有监控。那么怎么做到监控FGC的呢,简单。刚{BANNED}中国第一步里面不是有个-Xloggc:gc.log么,其实里面就记录了GC信息,监控这个文件就行了。我们看下gc.log内容
-
Java HotSpot(TM) 64-Bit Server VM (25.261-b12) for windows-amd64 JRE (1.8.0_261-b12), built on Jun 18 2020 06:56:32 by "" with unknown MS VC++:1916
-
Memory: 4k page, physical 16449800k(3684668k free), swap 32899336k(6189288k free)
-
CommandLine flags: -XX:CompressedClassSpaceSize=3145728 -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=629145600 -XX:MaxMetaspaceSize=11534336 -XX:MaxNewSize=104857600 -XX:MetaspaceSize=10485760 -XX:NewSize=104857600 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
-
2023-08-24T14:41:10.560+0800: 0.342: [GC (Allocation Failure) [PSYoungGen: 6161K->808K(89600K)] 487441K->482096K(601600K), 0.0022702 secs] [Times: user=0.09 sys=0.00, real=0.01 secs]
-
2023-08-24T14:41:10.569+0800: 0.344: [Full GC (Ergonomics) [PSYoungGen: 808K->0K(89600K)] [ParOldGen: 481288K->72361K(421888K)] 482096K->72361K(511488K), [Metaspace: 3331K->3331K(1056768K)], 0.0213829 secs] [Times: user=0.00 sys=0.03, real=0.01 secs]
-
2023-08-24T14:41:11.641+0800: 1.417: [GC (Allocation Failure) [PSYoungGen: 4608K->480K(89600K)] 486569K->482449K(601600K), 0.0045677 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
2023-08-24T14:41:11.642+0800: 1.421: [Full GC (Ergonomics) [PSYoungGen: 480K->0K(89600K)] [ParOldGen: 481969K->1002K(421888K)] 482449K->1002K(511488K), [Metaspace: 3824K->3824K(1056768K)], 0.0090994 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
2023-08-24T14:41:11.693+0800: 1.471: [GC (Allocation Failure) [PSYoungGen: 74752K->0K(89600K)] 485354K->482282K(601600K), 0.0276682 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
-
2023-08-24T14:41:11.724+0800: 1.499: [Full GC (Ergonomics) [PSYoungGen: 0K->0K(89600K)] [ParOldGen: 482282K->72681K(512000K)] 482282K->72681K(601600K), [Metaspace: 3824K->3824K(1056768K)], 0.0110738 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
2023-08-24T14:41:12.747+0800: 2.522: [GC (Allocation Failure) [PSYoungGen: 0K->0K(89600K)] 482281K->482281K(601600K), 0.0014162 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
2023-08-24T14:41:12.748+0800: 2.524: [Full GC (Ergonomics) [PSYoungGen: 0K->0K(89600K)] [ParOldGen: 482281K->1024K(512000K)] 482281K->1024K(601600K), [Metaspace: 3824K->3824K(1056768K)], 0.0036358 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
2023-08-24T14:41:12.776+0800: 2.553: [GC (Allocation Failure) [PSYoungGen: 73216K->0K(89600K)] 483840K->482304K(601600K), 0.0080524 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
2023-08-24T14:41:12.776+0800: 2.561: [Full GC (Ergonomics) [PSYoungGen: 0K->0K(89600K)] [ParOldGen: 482304K->72704K(512000K)] 482304K->72704K(601600K), [Metaspace: 3824K->3824K(1056768K)], 0.0121366 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
我们就解释下这个吧
2023-08-24T14:41:19.377+0800: 9.153: [GC (Allocation Failure) [PSYoungGen: 0K->0K(101376K)] 482304K->482304K(613376K), 0.0011350 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2023-08-24T14:41:19.378+0800: 9.154: [Full GC (Ergonomics) [PSYoungGen: 0K->0K(101376K)] [ParOldGen: 482304K->1024K(512000K)] 482304K->1024K(613376K), [Metaspace: 3825K->3825K(1056768K)], 0.0050395 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
前面开始是时间戳。
第二个GC和FULL GC就表示GC类型,简单好理解。
(Allocation Failure) (Ergonomics) GC的原因
[PSYoungGen: 808K->0K(89600K)] [ParOldGen: 481288K->72361K(421888K)] 482096K->72361K(511488K), [Metaspace: 3331K->3331K(1056768K)] 后统一解释面的格式就是,[GC发生的地方: 垃圾收集前使用了多少->垃圾收集后使用了多少(一共有多大)]
第三步 开始分析原因
好吧纯粹吃饱了撑了,我们其实早就知道FGC的原因了,我们还是假惺惺的表示不知道,开始找原因。
1.先用jstat看下GC情况
先用jps看下当前正在运行的进程。找打进程号以后,执行jstat -gc 进程号 2000 40 ,命令的意思是没隔2000ms收集一次进程的GC情况,一共收集40次。结果如下
字段意思自己查下,我们重点看FGC这个,65表示从开始到现在一共执行了65次,从上面信息看到大概每2秒执行了4次FGC。好吧,其实没什么作用,但面试时候还是提下,表示你会。
2.去找hprof文件
hprof你可以叫它内存快照文件,这个文件就是一个很重要的工具了。
怎么得到这个文件。你可以这样用命令jmap -dump:format=b,file=E:\users\ZhangC82\Documents\26868.hprof 进程号,这样你就能DUMP hprof文件。
但其实线上环境比较复杂,比如我只有每周三半夜1到4点之间会发生持续5分钟的FGC,那你是不是要半夜去值守,等它发生的时候赶紧去dump文件。当然其实每这么麻烦,办法很多。
比如你写一个cronjob里面跑一个shell脚本就是jmap命令,然后每隔个几分钟跑一次。
或者你可以用代码来导出,然后用SPRING 的定时器,或者各种你熟悉的定时器均可。JAVA代码dump内存如下
-
package sty.zchi;
-
-
import com.sun.management.HotSpotDiagnosticMXBean;
-
-
import javax.management.MBeanServer;
-
import java.lang.management.ManagementFactory;
-
-
public class JvThread implements Runnable{
-
@Override
-
public void run() {
-
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
-
//每隔1分钟做一次内存快照DUMP
-
int i = 1;
-
while(true) {
-
try {
-
String filePath = "E:\\users\\ZhangC82\\Documents\\dumpfile\\" + i + ".hprof";
-
Thread.sleep(60000);
-
HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
-
server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
-
mxBean.dumpHeap(filePath, true);
-
i++;
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
-
}
-
}
-
}
3.开始分析内存快照文件
分析工具很多,这里只说下jvisualvm,好了拿到一个文件,我们用jvisualvm打开,截图如下
我们发现仅一个sty.zchi.Hello就占用了我们400M的内存。然后回头就去找代码,看看Hello这个对象的代码,然后顺藤摸瓜找到初始化的代码就知道原因了。
这只是一个很简单的示例,实际上场景肯定远比这要复杂。而且很多时候,要结合代码加业务才能知道原因。当然这里只是告诉你一个调优的思路罢了。毕竟能碰到要调优JVM的情况不多了。