一个线上服务内存占用带来的问题
在Oracle官网给出的JVM调优文档中关于堆的调整技巧有以下三个方面:
-
1.应将堆大小设置为不超过最大可用物理RAM量。如果超过此值,操作系统将开始分页,性能会显着下降。VM总是使用比堆大小更多的内存。除了堆大小设置之外,还分配内部VM功能所需的内存,VM外部的本地类库和永久区(仅适用于Sun虚拟机:存储类和方法所需的内存)。
-
2.使用分代垃圾收集方案时,年轻代大小不应超过Java堆总大小的一半。通常,堆大小的25%到40%就足够了。
-
3.在生产环境中,将最小堆大小和最大堆大小设置为相同的值,以防止浪费用于
不断增长和收缩堆的VM资源。
第三点将初始堆大小设置和最大堆大小相同,可以减少JVM重新分配内存,伸缩J堆大小时候的GC压力,在线上实际运行的时候给-Xms和-Xmx参数均设置成2048m,可是使用htop命令查看进程消耗的内存时候,发现只使用了1098m。Xms是设置初始堆和最小堆的大小,难道出问题了么?使用jmap命令输出堆的实际情况,如下:
[root@pc ~]# jmap -heap 15189
Attaching to process ID 15189, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 715653120 (682.5MB)
MaxNewSize = 715653120 (682.5MB)
OldSize = 1431830528 (1365.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 677380096 (646.0MB)
used = 359083872 (342.4490661621094MB)
free = 318296224 (303.5509338378906MB)
53.01069135636368% used
From Space:
capacity = 18874368 (18.0MB)
used = 8472112 (8.079635620117188MB)
free = 10402256 (9.920364379882812MB)
44.886864556206596% used
To Space:
capacity = 18350080 (17.5MB)
used = 0 (0.0MB)
free = 18350080 (17.5MB)
0.0% used
PS Old Generation
capacity = 1431830528 (1365.5MB)
used = 45051784 (42.96472930908203MB)
free = 1386778744 (1322.535270690918MB)
3.14644667221399% used
31785 interned Strings occupying 3781800 bytes.
查看以上信息发现,堆分配大小没有问题的呀!这是为什么呢。。查了一会资料,JVM启动的时候,将虚拟地址与本机物理内存地址进行映射,实际运行的时候并没有使用到2048m内存,在堆内存不够用的时候回向操作系统申请物理内存,而操作系统htop命令查看的内存是RES即实际占用的物理内存,刚启动的时候比实际Xms来的小。
这里会带来两个问题:
- 第1次YGC之前Eden区分配对象的速度较慢;
- YGC的时候,Young区的对象要晋升到Old区的时候,这个时候需要操作系统真正分配内存,这样就会加大YGC的停顿时间;
我们可以给JVM添加-XX:+AlwaysPreTouch这个参数优化这个问题,不过这个参数在JDK8下有一个副作用会大大提高启动时间。
在没有配置-XX:+AlwaysPreTouch参数即默认情况下,JVM参数-Xms申明的堆只是在虚拟内存中分配,而不是在物理内存中分配:它被以一种内部数据结构的形式记录,从而避免被其他进程使用这些内存。这些内存页直到被访问时,才会在物理内存中分配。当JVM需要内存的时候,操作系统将根据需要分配内存页。
配置-XX:+AlwaysPreTouch参数后,JVM将-Xms指定的堆内存中每个字节都写入’0’,这样的话,除了在虚拟内存中以内部数据结构保留之外,还会在物理内存中分配。并且由于touch这个行为是单线程的,因此它将会让JVM进程启动变慢。所以,要么选择减少接下来对每个缓存页的第一次访问时间,要么选择减少JVM进程启动时间
不过在JDK9中可以使用并行操作,Parallelize Memory Pretouch。
这里又有疑问了,当JVM内存申请使用完毕后会返还给物理内存吗?
- 不会,一旦虚拟机申请分配了物理内存,不会返还。
设置了Xmx参数,JVM一定不会超过这个大小么?超过一定会OOM或者SOF吗?
- 还有堆外内存。。。
JVM内存占用情况
JVM进程主要占用内存的一些地方,其中JDK8之前JMM模型中共享的永久区取消变为元空间(metaspace)依旧存放于堆外内存之中。
由以上可以看出,JVM实际占用的内存实际上包含堆内和堆外的,而虚拟机启动参数Xms和Xmx限制的是堆的大小,实际虚拟机内存可能大得多。
总结
JVM在默认情况下启动的时候,
-
JVM参数-Xms申明的堆只是在虚拟内存中分配,而不是在物理内存中分配;
-
-XX:+AlwaysPreTouch参数可以申请提前占用内存,减少GC消耗,但会提高启动时间;
-
JVM申请的物理内存,一旦申请不会返还,除非重启或者关闭;
-
JVM实际占用的内存包括堆内和堆外内存,也就是会大于Xmx设置的值。
网友评论