备战面试日记(2.6) - (JVM.JVM调优)

本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试。

记录日期:2022.1.3

大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习。

文章目录

  • JVM - JVM调优
    • JVM参数
      • JVM参数设置方法
    • JVM调优工具
      • JPS、JMAP、JSTACK
      • jconsole
      • jvisualvm
        • 使用方式
    • JVM调优方案
      • 调优原则
      • 调优目的
        • 从应用层面来说
        • 从虚拟机层面来说
      • 调优方案
      • 调优步骤
        • 1.监控运行状态(GC等)
        • 2.分析结果,判断是否需要优化
        • 3.调整GC类型和内存分配
          • 从JVM层面调整
          • 从代码层面调整
        • 4.不断的分析和调整
        • 5.全面应用参数
      • 调优实战
        • 如果你的系统CPU/内存占用100%了你怎么办?
          • CPU100%
          • 内存100%
        • 如果服务OOM,如何排查?
        • 如果系统非常卡顿,如何排查?
        • 如果你的系统忽然不能响应了你怎么排查?
        • 如果你的系统压测数据上不去你除了加负载还有没有其他的好办法?

JVM - JVM调优

jvm调优内容建议手操以下,记忆深刻一点。

JVM参数

现在这里汇总一下可能会经常用到的JVM参数:

  1. -Xms20M:表示设置JVM启动内存的最小值为20M,必须以M为单位
  2. -Xmx20M:表示设置JVM启动内存的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免JVM内存自动扩展。大的项目-Xmx和-Xms一般都要设置到10G、20G甚至还要高
  3. -verbose:gc:表示输出虚拟机中GC的详细情况
  4. -Xss128k:表示可以设置虚拟机栈的大小为128k
  5. -Xoss128k:表示设置本地方法栈的大小为128k。不过HotSpot并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说这个参数是无效的
  6. -XX:PermSize=10M:表示JVM初始分配的永久代的容量,必须以M为单位
  7. -XX:MaxPermSize=10M:表示JVM允许分配的永久代的最大容量,必须以M为单位,大部分情况下这个参数默认为64M
  8. -Xnoclassgc:表示关闭JVM对类的垃圾回收
  9. -XX:+TraceClassLoading:表示查看类的加载信息
  10. -XX:+TraceClassUnLoading:表示查看类的卸载信息
  11. -XX:NewRatio=4:表示设置年轻代:老年代的大小比值为1:4,这意味着年轻代占整个堆的1/5
  12. -XX:SurvivorRatio=8:表示设置2个Survivor区:1个Eden区的大小比值为2:8,这意味着Survivor区占整个年轻代的1/5,这个参数默认为8
  13. -Xmn20M:表示设置年轻代的大小为20M
  14. -XX:+HeapDumpOnOutOfMemoryError:表示可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照
  15. -XX:+UseG1GC:表示让JVM使用G1垃圾收集器
  16. -XX:+PrintGCDetails:表示在控制台上打印出GC具体细节
  17. -XX:+PrintGC:表示在控制台上打印出GC信息
  18. -XX:PretenureSizeThreshold=3145728:表示对象大于3145728(3M)时直接进入老年代分配,这里只能以字节作为单位
  19. -XX:MaxTenuringThreshold=1:表示对象年龄大于1,自动进入老年代
  20. -XX:CompileThreshold=1000:表示一个方法被调用1000次之后,会被认为是热点代码,并触发即时编译
  21. -XX:+PrintHeapAtGC:表示可以看到每次GC前后堆内存布局
  22. -XX:+PrintTLAB:表示可以看到TLAB的使用情况
  23. -XX:+UseSpining:开启自旋锁
  24. -XX:PreBlockSpin:更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁

JVM参数设置方法

这里以IDEA为例:

备战面试日记(2.6) - (JVM.JVM调优)_第1张图片

备战面试日记(2.6) - (JVM.JVM调优)_第2张图片

JVM调优工具

调优工具不是要讲的重点,这些理应都应该知道,就不详细说了。

博客参考链接:Java系列笔记(4) - JVM监控与调优

jps、jstat、jhat、jinfo、jstack、jmap、jconsole、jvisualvm。这里主要清楚下jstack 和 jvisualvm,这两个比较常用。

  • jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题
  • jvisualvm是SUN/Oracle JDK自带的JVM运行状态监测工具,能够获取JVM运行状态的各种信息,包括Thread Dump和Heap Dump,在可以使用的情况下建议使用此工具监测JVM运行状态

JPS、JMAP、JSTACK

博客参考链接:Java常用命令(一) jps、jstack、jmap

jstack:java命令–jstack 工具

jconsole

博客参考链接:JConsole可视化工具介绍

jvisualvm

博客参考链接:java jvisualvm简要说明

jvisualvm可以监控内存泄露,跟踪垃圾回收,执行时内存、cpu分析,线程分析…

jvisualvm已经被集成在jdk1.6以上的版本中(不是jre)。自身运行需要最低jdk1.6版本,但是可以监控运行在jdk1.4以上版本的java程序。

使用方式

cmd中打开直接输入”jvisualvm“。

备战面试日记(2.6) - (JVM.JVM调优)_第3张图片

打开jvisualvm界面。

备战面试日记(2.6) - (JVM.JVM调优)_第4张图片

通过可视化监控jvm信息。

备战面试日记(2.6) - (JVM.JVM调优)_第5张图片

JVM调优方案

调优原则

  1. 多数的Java应用不需要在服务器上进行GC优化。
  2. 多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题。
  3. 在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合)。
  4. 减少创建对象的数量。
  5. 减少使用全局变量和大对象。
  6. GC优化是到最后不得已才采用的手段。
  7. 在实际使用中,分析GC情况优化代码比优化GC参数要多得多。

调优目的

从应用层面来说

使用较小的内存占用来获得较高的吞吐量或者较低的延迟。

这里就要关注几个指标:

  • 内存占用:程序正常运行需要的内存大小。
  • 延迟:由于垃圾收集而引起的程序停顿时间。
  • 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。
从虚拟机层面来说
  • 将转移到老年代的对象数量降低到最小。
  • 减少full GC的执行时间。

调优方案

  • 减少使用全局变量和大对象。
  • 调整新生代的大小到最合适。
  • 设置老年代的大小为最合适。
  • 选择合适的GC收集器。

调优步骤

1.监控运行状态(GC等)

使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化。

调优可以依赖、参考的数据有系统运行日志、堆栈错误信息、gc日志、线程快照、堆转储快照等。

  1. 系统运行日志:系统运行日志就是在程序代码中打印出的日志,描述了代码级别的系统运行轨迹(执行的方法、入参、返回值等),一般系统出现问题,系统运行日志是首先要查看的日志。
  2. 堆栈错误信息:当系统出现异常后,可以根据堆栈信息初步定位问题所在,比如根据“java.lang.OutOfMemoryError: Java heap space”可以判断是堆内存溢出;根据“java.lang.StackOverflowError”可以判断是栈溢出;根据“java.lang.OutOfMemoryError: PermGen space”可以判断是方法区溢出等。
  3. GC日志:程序启动时用-XX:+PrintGCDetails-Xloggc:/data/jvm/gc.log 可以在程序运行时把gc的详细过程记录下来,或者直接配置“-verbose:gc”参数把gc日志打印到控制台,通过记录的gc日志可以分析每块内存区域gc的频率、时间等,从而发现问题,进行有针对性的优化。
  4. 线程快照:顾名思义,根据线程快照可以看到线程在某一时刻的状态,当系统中可能存在请求超时、死循环、死锁等情况是,可以根据线程快照来进一步确定问题。通过执行虚拟机自带的“jstack pid”命令,可以dump出当前进程中线程的快照信息,更详细的使用和分析网上有很多例,这篇文章写到这里已经很长了就不过多叙述了。
  5. 堆转储快照:程序启动时可以使用 “-XX:+HeapDumpOnOutOfMemory”“-XX:HeapDumpPath=/data/jvm/dumpfile.hprof”,当程序发生内存溢出时,把当时的内存快照以文件形式进行转储(也可以直接用jmap命令转储程序运行时任意时刻的内存快照),事后对当时的内存使用情况进行分析。
2.分析结果,判断是否需要优化

如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化;

如果GC时间超过1-3秒,或者频繁GC,则必须优化。

注:如果满足下面的指标,则一般不需要进行GC优化:

  • Minor GC执行时间不到50ms;
  • Minor GC执行不频繁,约10秒一次;
  • Full GC执行时间不到1s;
  • Full GC执行频率不算频繁,不低于10分钟1次;
3.调整GC类型和内存分配

如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找1台或几台机器进行beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择。

针对不同的问题在不同层面提供优化方案:

从JVM层面调整

JVM配置方面,一般情况可以先用默认配置(基本的一些初始参数可以保证一般的应用跑的比较稳定了),在测试中根据系统运行状况(会话并发情况、会话时间等),结合gc日志、内存监控、使用的垃圾收集器等进行合理的调整,当老年代内存过小时可能引起频繁Full GC,当内存过大时Full GC时间会特别长。

那么JVM的配置比如新生代、老年代应该配置多大最合适呢?答案是不一定,调优就是找答案的过程,物理内存一定的情况下,新生代设置越大,老年代就越小,Full GC频率就越高,但Full GC时间越短;相反新生代设置越小,老年代就越大,Full GC频率就越低,但每次Full GC消耗的时间越大。建议如下:

  • -Xms和-Xmx的值设置成相等,堆大小默认为-Xms指定的大小,默认空闲堆内存小于40%时,JVM会扩大堆到-Xmx指定的大小;空闲堆内存大于70%时,JVM会减小堆到-Xms指定的大小。如果在Full GC后满足不了内存需求会动态调整,这个阶段比较耗费资源。
  • 新生代尽量设置大一些,让对象在新生代多存活一段时间,每次Minor GC 都要尽可能多的收集垃圾对象,防止或延迟对象进入老年代的机会,以减少应用程序发生Full GC的频率。
  • 老年代如果使用CMS收集器,新生代可以不用太大,因为CMS的并行收集速度也很快,收集过程比较耗时的并发标记和并发清除阶段都可以与用户线程并发执行。
  • 方法区大小的设置,1.6之前的需要考虑系统运行时动态增加的常量、静态变量等,1.7只要差不多能装下启动时和后期动态加载的类信息就行。
从代码层面调整

代码实现方面,性能出现问题比如程序等待、内存泄漏除了JVM配置可能存在问题,代码实现上也有很大关系:

  • 避免创建过大的对象及数组:过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代,如果是短命的大对象,会提前出发Full GC。
  • 避免同时加载大量数据,如一次从数据库中取出大量数据,或者一次从Excel中读取大量记录,可以分批读取,用完尽快清空引用。
  • 当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代。
  • 可以在合适的场景(如实现缓存)采用软引用、弱引用,比如用软引用来为ObjectA分配实例:SoftReference objectA=new SoftReference(); 在发生内存溢出前,会将objectA列入回收范围进行二次回收,如果这次回收还没有足够内存,才会抛出内存溢出的异常。
    避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满。
  • 尽量避免长时间等待外部资源(数据库、网络、设备资源等)的情况,缩小对象的生命周期,避免进入老年代,如果不能及时返回结果可以适当采用异步处理的方式等。
4.不断的分析和调整

通过不断的试验和试错,分析并找到最合适的参数。

5.全面应用参数

如果找到了最合适的参数,则将这些参数应用到所有服务器,并进行后续跟踪。

调优实战

如果你的系统CPU/内存占用100%了你怎么办?

如果CPU和内存同时上涨,一般推测是程序的某个线程进入死循环,不停的分配内存导致

CPU100%

参考博客链接:阿里程序员不小心把服务器CPU打到100%,淡定展示教科书排查过程

⼀般CPU100%疯狂GC,都是死循环或者产生大对象的问题。

先进入对应的服务器,⽤top -c命令找出当前进程的运⾏列表。

按⼀下P 可以按照CPU使⽤率进⾏排序。

假如说,显示Java进程 PID1234 的java进程消耗最⾼。

然后我们需要根据PID 查出CPU⾥⾯消耗最⾼的进程。

使⽤命令 top -Hp 1234 找出这个进程下⾯的线程,继续按P排序。

假如说,该进程下的 2854 线程 CPU消耗最⾼。

我们要导出该进程的快照,看看该线程做了什么工作。

jstack -l 1234 > ./1234.stack

再⽤grep查看⼀下线程在⽂件⾥做了什么,b262854线程的16进制。

cat 1234.stack |grep 'b26' -C 8

这样就可以通过快照具体定位问题。

内存100%

一应用在性能测试过程中,发现内存占用率很高,Full GC频繁,使用sudo -u admin -H jmap -dump:format=b,file=文件名.hprof pid 来dump内存,生成dump文件,并使用Eclipse下的mat差距进行分析,发现:

备战面试日记(2.6) - (JVM.JVM调优)_第6张图片
从图中可以看出,这个线程存在问题,队列LinkedBlockingQueue所引用的大量对象并未释放,导致整个线程占用内存高达378m,此时通知开发人员进行代码优化,将相关对象释放掉即可。

这里参考了一篇文章,在前面有表明引用。

如果服务OOM,如何排查?

一般如果出现java.lang.OutOfMemoryError: GC overhead limit exceeded

一般都是代表为了释放很小的空间却耗费了太多的时间,其原因一般有两个:

  1. 堆太小
  2. 有死循环或大对象

如果是第一点,使用ps -ef |grep "java"查看,发现该应用使用堆过小,则只需要改变堆中各区域的大小设置即可。

img

如果是第二点,则可通过进程快照,根据日志找出问题代码进行修改。

如果系统非常卡顿,如何排查?

一个服务系统,经常出现卡顿,分析原因,可能是Full GC时间太长。

jstat -gcutil:
S0   S1  E   O    P    YGC YGCT FGC FGCT GCT
12.16 0.00 5.18 63.78 20.32 54  2.047 5   6.946 8.993

分析上面的数据,发现Young GC执行了54次,耗时2.047秒,每次Young GC耗时37ms,在正常范围。

而Full GC执行了5次,耗时6.946秒,每次平均1.389s,数据显示出来的问题是:Full GC耗时较长。

分析该系统的是指发现,NewRatio=9,也就是说,新生代和老生代大小之比为1:9,这就是问题的原因:

  1. 新生代太小,导致对象提前进入老年代,触发老年代发生Full GC。
  2. 老年代较大,进行Full GC时耗时较大。

优化的方法是调整NewRatio的值,调整到4,发现Full GC没有再发生,只有Young GC在执行,这就是把对象控制在新生代就清理掉,没有进入老年代。

这种做法对一些应用是很有用的,但并不是对所有应用都要这么做。

如果你的系统忽然不能响应了你怎么排查?

待补充。

如果你的系统压测数据上不去你除了加负载还有没有其他的好办法?

待补充。

你可能感兴趣的:(面试准备,JVM虚拟机相关,面试,java,职场和发展)