JVM 调优-给你的java应用看看病

目录java应用1cpu负载过高1.1分析问题1.2解决方案2内存占用过多2.1从内存回收方面2.2从代码层面java应用1cpu负载过高1.1分析问题首先我们通过top命令进行分析,找出消耗最多cpu的java进程id。找出对应的进程id后,我们可以通过top-Hp进程id命令来找出该进程中占用cpu最多的前几个线程id。我们使用jstack-l进程pid>/tmp/java_pid.lo...

JVM 调优-给你的java应用看看病

目录

  • java 应用
    • 1 cpu 负载过高
      • 1.1 分析问题
      • 1.2 解决方案
    • 2 内存占用过多
      • 2.1 从内存回收方面
      • 2.2 从代码层面

java 应用

1 cpu 负载过高

1.1 分析问题

  1. 首先我们通过top 命令进行分析,找出消耗最多cpu的java 进程id 。

  2. 找出对应的进程id 后,我们可以通过 top -Hp 进程id 命令来找出该进程中占用cpu最多的前几个线程id。

  3. 我们使用 jstack -l 进程pid > /tmp/java_pid.log 输出java的堆栈日志到文件 /tmp/java_pid.log。

  4. 我们将刚刚查询到的java进程中占用cpu最多的前几个线程id。进行转化为16进制。

    printf “%X“ 线程id 
  5. 我们在java堆栈日志文件中找到上面转化为16进制的线程的pid对应的 日志。

    实际操作步骤流程图: image

    补充:有时可能是我们代码创建线程过多导致的问题:

    # 查看该进程有多少线程ps p 9534 -L -o pcpu,pmem,pid,tid,time,tname,cmd|wc -l

1.2 解决方案

我们把对应的线程id的日志拿给我们的开发,进行定位错误,这里容易定位出的错误是:
  1. 线程处于WAITING(等待状态)
  2. 线程BLOCKED(阻塞)

img

我可以把定位到代码位置,告诉开发,让开发查看对应的代码是否有问题。

2 内存占用过多

​ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。

这些组成部分一些是线程私有的,其他的则是线程共享的。

线程私有的:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享的:

  • 方法区
  • 直接内存

2.1 从内存回收方面

​ Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。

img

在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。关于metaspace的详细讲解看:JVM源码分析之Metaspace解密

​ java 实际的内存使用是这样的,大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC(新生代GC).将eden 区的一些存活对象移动到Survivor 区,当Survivor区的大小,不够储存eden 区的存活对象时,那么就会将它移动到老年区(Old Generation ),当老年区满了时候将触发一次 Full GC .

​ 在实际工作中,我们可以使用 jmap -heap pid 来查看当前的进程的 java 堆的分布情况。

[root@iz23nb5ujp69 ~]# jmap -heap  11764Attaching to process ID 11764, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.73-b02using thread-local object allocation.Parallel GC with 2 thread(s)Heap Configuration:MinHeapFreeRatio= 0 #GC后,如果发现空闲堆内存占到整个预估堆内存的40%,则放大堆内存的预估最大值,但不超过固定最大值。默认该值是40MaxHeapFreeRatio= 100  #GC后,如果发现空闲堆内存占到整个预估堆内存的100%,则收缩堆内存预估最大值。默认的是70MaxHeapSize  = 2147483648 (2048.0MB)  # 最大的堆内存NewSize= 715653120 (682.5MB) # 新生代初始大小MaxNewSize= 715653120 (682.5MB) # 新生代最大大小OldSize= 1431830528 (1365.5MB)  #老年代NewRatio  = 2  #  新生代和老年代的 内存比例: 1:2  默认值SurvivorRatio= 8  #  Eden区与Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10,Eden区和Survivor区 的实际比例值是会变动的MetaspaceSize= 21807104 (20.796875MB) # 元空间大小CompressedClassSpaceSize = 1073741824 (1024.0MB) # 压缩时可用的最大的内存MaxMetaspaceSize= 17592186044415 MB#元空间可用最大大小G1HeapRegionSize= 0 (0.0MB)  #G1 收集器的内存大小Heap Usage:PS Young Generation # 新生代Eden Space:# Edencapacity = 372768768 (355.5MB)used  = 185979712 (177.36407470703125MB)free  = 186789056 (178.13592529296875MB)49.89144154909459% used#  eden 可用区使用率,该值满了将触发 young gcFrom Space:# Survivor1capacity = 175112192 (167.0MB)used  = 47983120 (45.76026916503906MB)free  = 127129072 (121.23973083496094MB)27.401358781460516% usedTo Space:  # Survivor2capacity = 167772160 (160.0MB)used  = 0 (0.0MB)free  = 167772160 (160.0MB)0.0% usedPS Old Generation# 老年代capacity = 1431830528 (1365.5MB)used  = 257274632 (245.35620880126953MB)free  = 1174555896 (1120.1437911987305MB)17.9682320616089% used  #  老年代 可用区使用率,该值满了将触发 full gc

适当的young gc 可以让清理一些不存活的对象,但是短时间大量的 young GC 是会导致 Full GC 的,那么Full gc 是尽量不要产生的,当一个应用,产生大量的full GC是不正常的, 过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。

​ young gc:

1552463473610

Metadata GC

image

​ full gc :

image

GC 日志解析

对了,如何在日志中打印GC日志,我们在后面的配置中会讲到。

[GC (Allocation Failure) [DefNew: 279616K->19156K(314560K), 0.0595827 secs] 279616K->19256K(1013632K), 0.0601044 secs] [Times: user=0.03 sys=0.02, real=0.06 secs] GC:表明进行了一次垃圾回收,前面没有Full修饰,表明这是一次Minor GC。Allocation Failure:表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。279616K->19156K(314560K)  260460三个参数分别为:GC前该内存区域(这里是年轻代)使用容量,GC后该内存区域使用容量,该内存区域总容量。0.0595827 secs表示GC耗时279616K->19256K(1013632K)堆区垃圾回收前的大小,堆区垃圾回收后的大小,堆区总大小。  0.0071945 secsTimes: user=0.01 sys=0.00, real=0.01 secs分别表示用户态耗时,内核态耗时和总耗时 新生代清理的内存:279616 - 19156 =  260460k 堆区减少的内存:279616 - 19256 = 260360k   新生代存到老年代的 数据为260460k - 260360k    

那么如何查询一个应用发生young Gc 和Full GC 的次数和耗时时间。我们可以使用jstat 。

jstat 查询GC 次数和Full Gc 次数

jstat -gcutil  pid  2000  10  (每隔2秒输出一次结果,输出10次)[root@iz23nb5ujp69 ~]# jstat -gcutil  3626  2000 10S0  S1  EOM  CCS YGC  YGCT FGC FGCT  GCT56.010.008.21  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.008.30  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.008.41  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.008.56  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.009.00  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.009.27  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.009.34  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.009.46  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.009.57  88.19  98.44  96.92  27350  353.229 4132.416  385.64556.010.009.70  88.19  98.44  96.92  27350  353.229 4132.416  385.645S0 — Heap上的 Survivor space 0 区已使用空间的百分比  S1 — Heap上的 Survivor space 1 区已使用空间的百分比  E— Heap上的 Eden space 区已使用空间的百分比  O— Heap上的 Old space 区已使用空间的百分比M- 表示的是Klass Metaspace以及NoKlass Metaspace两者总共的使用率CSS  -表示的是NoKlass Metaspace的使用率YGC — 从应用程序启动到采样时发生 Young GC 的次数 YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)  FGC — 从应用程序启动到采样时发生 Full GC 的次数 FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)  GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒) FGC Scavenge GC要慢,因此应该尽可能减少Full GC。

导致young Gc 和 full GC 的原因有哪些:

  1. young 可用区 设置的太小 ,young gc 设置的太小就会导致 ,多次young gc,多次young gc 也就导致 oldGeneration 不断增大,最终导致full gc
  2. Old Generation 设置的太小, 当 Old Generation 太小的话就会导致 经常占满,然后会进行full GC 。
  3. System.gc()被显示调用 , 垃圾回收不要手动触发,尽量依靠JVM自身的机制。
  4. Meta(元数据)区可用内存设置的太少。

源文地址:https://www.guoxiongfei.cn/cntech/12750.html