深入理解JVM

带着问题去学习,jvm分析

[root@localhost ~]# jstat -gcutil 3461 2000
S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
0.00   0.00  23.72   6.21  53.61   9     1.302     3    5.263    6.564
0.00   0.00  23.72   6.21  53.61   9     1.302     3    5.263    6.564
0.00   0.00  23.72   6.21  53.61   9     1.302     3    5.263    6.564
0.00   0.00  23.72   6.21  53.61   9     1.302     3    5.263    6.564
0.00   0.00  23.72   6.21  53.61   9     1.302     3    5.263    6.564

上面是一段通过JVM内建的指令jstat对一个Java应用程序的资源和性能进行实时监控的记录,“3461”是Java应用程序的进程ID,“2000”是指每隔2秒钟采集一次监控数据。

各个数据的含义:

S0:Survivor0 使用的百分比

S1:Survivor1 使用的百分比

E:Eden 使用的百分比

O:老年代 使用的百分比

P:永久代或者持久代 使用的百分比

YGC:Yong GC(Minor GC)次数

YGCT:Yong GC(Minor GC)耗费的时间(单位:秒)

FGC:Full GC(Full GC)次数

FGCT:Full GC(Full GC)耗费的时间(单位:秒)

如果你对上面的数据很熟悉和了解,也知道什么意思,那么下面的内容或许可以跳过,如果不熟悉,下面的内容将帮助你更好的理解。

JVM 体系结构

PC寄存器

JVM会为每一个创建的线程分配一个PC寄存器
大小为一个字节
内容是下一条将执行指令的地址

java方法栈

线程启动时,JVM会为其分配一个Java栈
JVM对java方法栈只有“压栈”,“出栈”的操作,操作的单位是栈帧
栈帧由三部分组成“局部变量区”,“操作数栈“,“栈帧数据区”

方法区

一个JVM只有一个方法区,是所有线程共享的
存放Class的线性二进制流
类信息,该类型的常量池,字段信息,方法的字节码,操作数栈和该方法的栈帧中的局部变量区的大小,异常表,到类ClassLoader的引用,到Class类的应用
方法区大小不固定,可以动态调整
方法区也可以被GC

一个JVM只有一个堆,所有线程共享
存放所有类实例和数组

JVM 的内存构成

上图对应文章开头列出的jvm分析数据中的各个代号意思。

上图是对象数据在jvm内各代的流转,上面各代的内存使用情况是我们优化jvm的关键,如果有任何一个的内存不够用,那么将抛出 OutOfMemorryError,最终导致java进程退出。

GC流程:

1、大部分情况新生对象都存在Eden,当Eden满了,触发YGC;

2、S0 存放 YGC 后存活的对象及 S1 中存活的对象;

3、YGC 完成几次后,如果 S0 空间满了,则存放到 O 中,如果 YGC 几次之后新生代内存还是不足,则抛出:
java.lang.OutOfMemorryError:java heap space;

4、O 中对象如果满了,则触发 FGC,如果 FGC 后,年老代内存还是不足,则抛出:
java.lang.OutOfMemorryError:java heap space;

5、P 中存放一些class信息等,当系统重要加载的类,反射的类和调用的方法较多时,P 可能会被占满, P 如果满了则触发 FGC,如果 FGC 后仍然满着,则抛出:java.lang.OutOfMemoeryError:PermGen space;

通过jstat工具我们可以看到各代实时的占用情况,下面我们通过jmap来了解jvm当前的配置和使用情况,如下显示:

[root@localhost ~]# jmap -heap 7583
Attaching to process ID 7583, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.1-b02

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 6442450944 (6144.0MB)
   NewSize          = 2147483648 (2048.0MB)
   MaxNewSize       = 2147483648 (2048.0MB)
   OldSize          = 5439488 (5.1875MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 268435456 (256.0MB)
   MaxPermSize      = 536870912 (512.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 1382416384 (1318.375MB)
   used     = 562811192 (536.7385787963867MB)
   free     = 819605192 (781.6364212036133MB)
   40.71213264787232% used
From Space:
   capacity = 370540544 (353.375MB)
   used     = 253155912 (241.42829132080078MB)
   free     = 117384632 (111.94670867919922MB)
   68.32070500765498% used
To Space:
   capacity = 382533632 (364.8125MB)
   used     = 0 (0.0MB)
   free     = 382533632 (364.8125MB)
   0.0% used
PS Old Generation
   capacity = 4294967296 (4096.0MB)
   used     = 107942928 (102.94239807128906MB)
   free     = 4187024368 (3993.057601928711MB)
   2.5132421404123306% used
PS Perm Generation
   capacity = 268435456 (256.0MB)
   used     = 128682560 (122.72125244140625MB)
   free     = 139752896 (133.27874755859375MB)
   47.937989234924316% used

上面我们看到了Eden(Eden Space),S0(From Space),S1(To Space),O(PS Old Generation),P(PS Perm Generation)各个内存使用情况,那么接下来我们来看看如何进行上述参数的配置。

JVM 优化的参数设置

首先看一下各代的分配比例及组成:

由上图可以看到:整个堆大小 = 年轻代大小(Eden+S0+S1) + 年老代大小(O)。

各代的内存大小设置:

1、堆的大小可以通过 -Xms 和 -Xmx 来设置,一般将他们设置为相同的大小,目的是避免在每次垃圾回收后重新调整堆的大小,比如 -Xms=2g -Xmx=2g 或者 -Xms=512m -Xmx=512m

2、年轻代大小可以通过 -Xmn 来设置,比如-Xmn=2g 或者 -Xmn512m,此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8

3、年老代大小 = 堆大小 – 年轻代大小

4、持久代或者永久代大小可以通过 -XX:PermSize 和 -XX:MaxPermSize 来控制

5、-XX:SurvivorRatio 控制 Eden和Survivor的内存占用比例,默认为8

以上涉及到的图片来自网上,来源不详!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据