Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4037
  • 博文数量: 8
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 90
  • 用 户 组: 普通用户
  • 注册时间: 2023-07-18 09:31
文章分类

全部博文(8)

文章存档

2023年(8)

我的朋友
最近访客

分类: Java

2023-08-25 10:35:38

这个真的很烦。
先说下JVM调优,原则上不要过早调优,啥意思呢?就是JVM要是没有频繁的FGC就别调了没啥意思。
那多频繁FGC算频繁,要调了,业内有自己的标准,我就不哔哔了。笔者呢很幸运,从来没有碰到线上系统频繁的FGC。所以从来没调过。那这篇文章的意思是?说白了就是面试官喜欢问这个,然后网上有很多案例,说实话没法看,原因就是:他们的现场我们复现不了,他文章里面查的可乐呵了,我们是一脸懵。所以我这篇文章的过程就是:
自己写一个制造FGC的代码 -> 然后傻傻的装作自己不知道FGC发生的原因,一脸懵逼的去查。
至于笔者为什么没碰到过线上的FGC,就是笔者之前所在的技术团队实力很强。你网上能找到的FGC的CASE,团队都有经验,都没掉进去过。

{BANNED}中国第一步制造FGC

以下是制造FGC的代码
Hello类

点击(此处)折叠或打开

  1. package sty.zchi;

  2. public class Hello {
  3.     private String name;
  4.     private byte[] aaa;

  5.     public Hello(String name) {
  6.         this.name = name;
  7.         this.aaa = new byte[1024 * 1024 * 400];
  8.     }
  9. }
主类

点击(此处)折叠或打开

  1. package sty.zchi;

  2. /**
  3.  * 模拟fgc问题
  4.  */
  5. public class JvStudy {
  6.     public static void main(String[] args) {
  7.         //创建一个内存快照DUMP的守护线程
  8.         JvThread jvThread = new JvThread();
  9.         Thread t = new Thread(jvThread);
  10.         t.start();
  11.         while(true) {
  12.             //申请一个巨大无比的局部变量
  13.             Hello h = new Hello("zchi");
  14.             //感觉有点多余,没必要写
  15.             byte[] MAXOBJ2 = new byte[1024 * 1024 * 70];
  16.             //一定要写,告诉JVM MAXOBJ废了没用了,如果没有那么下面一个巨大的MAXOBJ3进来后,就不是FGC了,就是OUTOFMEMORY了
  17.             h = null;
  18.             //又来一个巨大的局部变量,这个时候JVM发现年轻装不下了,就往年老的地方放,但年老也放满了,那就是开始FGC回收老年区吧
  19.             Hello h1 = new Hello("yuna");
  20.             System.out.println("hello world");
  21.             try {
  22.                 Thread.sleep(1000);
  23.             } catch (Exception e) {
  24.                 e.printStackTrace();
  25.             }
  26.         }
  27.     }
  28. }





带上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内容

点击(此处)折叠或打开

  1. 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
  2. Memory: 4k page, physical 16449800k(3684668k free), swap 32899336k(6189288k free)
  3. 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
  4. 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]
  5. 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]
  6. 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]
  7. 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]
  8. 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]
  9. 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]
  10. 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]
  11. 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]
  12. 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]
  13. 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内存如下

点击(此处)折叠或打开

  1. package sty.zchi;

  2. import com.sun.management.HotSpotDiagnosticMXBean;

  3. import javax.management.MBeanServer;
  4. import java.lang.management.ManagementFactory;

  5. public class JvThread implements Runnable{
  6.     @Override
  7.     public void run() {
  8.         MBeanServer server = ManagementFactory.getPlatformMBeanServer();
  9.         //每隔1分钟做一次内存快照DUMP
  10.         int i = 1;
  11.         while(true) {
  12.             try {
  13.                 String filePath = "E:\\users\\ZhangC82\\Documents\\dumpfile\\" + i + ".hprof";
  14.                 Thread.sleep(60000);
  15.                 HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
  16.                         server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
  17.                 mxBean.dumpHeap(filePath, true);
  18.                 i++;
  19.             } catch (Exception e) {
  20.                 e.printStackTrace();
  21.             }

  22.         }
  23.     }
  24. }

3.开始分析内存快照文件

分析工具很多,这里只说下jvisualvm,好了拿到一个文件,我们用jvisualvm打开,截图如下

我们发现仅一个sty.zchi.Hello就占用了我们400M的内存。然后回头就去找代码,看看Hello这个对象的代码,然后顺藤摸瓜找到初始化的代码就知道原因了。
这只是一个很简单的示例,实际上场景肯定远比这要复杂。而且很多时候,要结合代码加业务才能知道原因。当然这里只是告诉你一个调优的思路罢了。毕竟能碰到要调优JVM的情况不多了。
阅读(71) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~