JVM内存性能问题定位

JVM内存性能定位

有点乱的一篇记录,想到哪敲到哪了。。。

堆和栈

首先,栈(stack)和堆(heap)都是Java用来在RAM中存放数据的地方。根据 JVM 规范,JVM 内存共分为java栈(虚拟机栈VM Stack)、堆(Heap)、方法区(Method Area)、程序计数器(Program Counter Register)、本地方法栈(Native Method Stack)五个部分。

:用来存储程序中的一些对象,比如你用new关键字创建的对象,它就会被存储在堆内存中,但是这个对象在堆内存中的首地址会存储在栈中

:在jvm中栈用来存储一些对象的引用、局部变量以及计算过程的中间数据,在方法退出后那么这些变量也会被销毁。它的存储比堆快得多,只比CPU里的寄存器慢

栈内存在JVM中默认是1M,可以通过参数进行设置:-Xss
最小堆内存在JVM中默认物理内存的64分之1,最大堆内存在JVM中默认物理内存4分之一,且建议最大堆内存不大于4G,并且设置-Xms=-Xmx避免每次GC后,调整堆的大小,减少系统内存分配开销:-Xms-Xmx.

JVM堆内存与GC

在JVM里的内存空间,从大的层面划分,主要有新生代空间(Young)和老年代空间(Old),其中Young空间,又有1个Egen区,和2个Survivor区

  1. Eden区域是用来存放使用new或者newInstance等方式创建的对象,默认都是存放在Eden区,除非这个对象太大,或者超过了设定的阈值-XX:PretenureSizeThresold,这样的对象会被直接分配到Old区域。

  2. 2个Survivor(幸存)区,一般称S0,S1,理论上他们是一样大的,解释一下,他们是如何工作的:
    在不断创建对象的过程中,Eden区会满,这时候会开始做Young G也叫Minor GC,而Young空间的第一次GC就是找出Eden区中,幸存活着的对象,然后将这些对象,放到S0,或S1区中的其中一个, 假设第一次选择了S0,它会逐步将活着的对象拷贝到S0区域,但是如果S0区域满了,剩下活着的对象只能放old区域了,接下来要做的是,将Eden区域 清空,此时时候S1区域也是空的。

当第二次Eden区域满的时候,就将Eden区域中活着的对象+S0区域中活着的对象,迁移到S1中,如果S1放不下,就会将剩下的部门,放到Old区域中,只是这次对象来源区域增加了S0,最后会将Eden区+S0区域,清空。

第三次和第四次依次类推,始终保证S0和S1有一个是空的,用来存储临时对象,用于交换空间的目的,反反复复多次没有被淘汰的对象,将会放入old区域中,默认是15次。具体的交换过程就和上图中的信息相似。

S0和S1一般多大,靠什么参数来控制,有什么变化?

一般来说很小,我们大概知道它与Young差不多相差一倍的比例,设置的的参数主要有两个:
-XX:SurvivorRatio=8
-XX:InitialSurvivorRatio=8

第一个参数是Eden和Survivor区域比重,注意是一个Survivor的的大小,如果将其设置为8,则说明Eden区是一个Survivor区的8倍,换句话说S0或S1空间是整个Young空间的1/10,剩余的80%由Eden区域来使用。

第二个参数是Young/S0的比值,当其设置为8时,表示s0或s1占整个Young空间的12.5%。

一个对象每次Minor Gc时,活着的对象都会在s0和s1区域转移,经过经过Minor GC多少次后,会进入Old区域呢?

默认是15次,参数设置-XX:MaxTenuringThreshold=15,计数器会在对象的头部记录它交换的次数

FULL GC

发生FULL GC的时候,意味着JVM会安全的暂停所有正在执行的线程(Stop The World),来回收内存空间,在这个时间段内,所有除了回收垃圾的线程外,其他有关JAVA的程序,代码都会静止,反映到系统上,就会出现系统响应大幅度变慢,卡机等状态。

内存分析

线程阻塞

关于线程阻塞,在之前定位linux最耗CPU时已经有提过:Jstack定位线程阻塞

这边提供一个分析工具,当使用jstack pid > 1.log生成堆栈日志之后,使用tda工具进行分析

tda度娘盘地址(提取码:fut9)

jstat查看gc

jstat -gccause pid 5000,每隔5s刷新一次,查看GC信息,

1
2
3
4
[root@iZuf61um4k0dww403xbmgpZ ~]# jstat -gccause 1270 5000
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
0.00 0.00 28.12 99.96 87.59 80.39 610 21.693 31 61.932 83.625 G1 Evacuation Pause No GC
0.00 0.00 28.12 99.96 87.59 80.39 610 21.693 31 61.932 83.625 G1 Evacuation Pause No GC

其中FGC代表程序启动至今的FULL GC次数

jmap分析GC

jmap -heap PID可以直接看到当前堆内存信息,包含各区域使用率

jmap -histo:live PID | head -10打印当前java堆活跃的各个对象的数量、大小

jmap -dump:live,format=b,file=dump.hprof PID生成dump文件(堆栈)
生成的dump文件(dump.hprof),可以用MATMemoryAnalyzer)工具进行堆栈分析

MAT度娘盘地址(提取码:jidc)

打印GC日志

最后,可以通过启动应用时加上GC打印的设置,直接将GC打印到文件中进行分析:

1
-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/root/log/gclogs

分析用工具:gcviewer-1.36-SNAPSHOT

gcviewer度娘盘地址(提取码:cvhs)
工具启动方式:java -jar gcviewer-1.36-SNAPSHOT.jar(文件目录下执行)

参考文章

JDK8 JVM性能优化

工具使用教程

利用mat定位内存泄漏原因
java GC日志查看gcviewer
TDA进行java线程dump分析
java线程Dump分析工具–jstack
Java GC日志查看

文章目录
  1. 堆和栈
  2. JVM堆内存与GC
    1. S0和S1一般多大,靠什么参数来控制,有什么变化?
    2. 一个对象每次Minor Gc时,活着的对象都会在s0和s1区域转移,经过经过Minor GC多少次后,会进入Old区域呢?
    3. FULL GC
  3. 内存分析
    1. 线程阻塞
    2. jstat查看gc
    3. jmap分析GC
    4. 打印GC日志
  4. 参考文章
    1. 工具使用教程
|