JVM异常现象解析
1、Java进程内存不回落
异常现象:针对 Java 应用进程进行压力测试时,在压测过程中进程的占用内存逐渐升高,但在压测结束后,进程占用内存仍然一直很高不回落,奇怪的是此时堆内存占用其实很低。即 Java应用一直占用高内存并且在空闲时也并未将内存归还给操作系统,这与通常的认知不同。
很多人的认知是:“Java程序在短时间内处理大量任务时会申请使用大量的内存空间,而等任务处理完毕(或者是请求的任务变少时),会有大量的内存被虚拟机回收回来,回收回来的内存由于短期内不再被使用,所以虚拟机会将内存归还给操作系统。”。但其实是错的,Java 在进行垃圾回收后,会将不再被使用的对象占用的内存回收,但这些内存如果短期内没有被接着使用,并不一定会归还给操作系统,具体会不会归还是受JVM版本(直接和GC版本有关)、JVM的参数配置影响的。
“JVM 的垃圾回收,只是一个逻辑上的回收,回收的只是 JVM 申请的那一块逻辑堆区域,将数据标记为空闲之类的操作,并不是调用 free 将内存归还给操作系统”
1.1、操作系统与JVM的内存分配
JVM 的自动内存管理,其实只是先向操作系统申请了一大块内存,然后自己在这块已申请的内存区域中进行“自动内存管理”。JAVA 中的对象在创建前,会先从申请的这一大块内存中划分出一部分来给这个对象使用,在 GC 时也只是这个对象所处的内存区域数据清空,标记为空闲而已。JVM 归还内存给操作系统的代价比较大,所以不会轻易进行,即 JVM 不会在每次 GC 后都进行内存的归还。
虽然代价高,但 JVM 还是提供了这个归还内存的功能。JVM 提供了-XX:MinHeapFreeRatio和-XX:MaxHeapFreeRatio 两个参数,用于配置这个归还策略。
- MinHeapFreeRatio:示例 -XX:MinHeapFreeRatio=20。表示空闲内存最少保留的比例值,当使用内存越来越大,空闲区域小于比值时,会进行扩容,直到达到最大堆内存 Xmx。如果是在收缩过程,剩余内存达到这个阈值后,就会停止收缩。
- MaxHeapFreeRatio:示例 -XX:MaxHeapFreeRatio=70。代表当空闲区域达到该值时,会进行“缩容”,缩容的下限为Xms
不过虽然有这个归还的功能,不过因为这个代价比较昂贵,所以 JVM 在归还的时候,是线性递增归还的,并不是一次全部归还。而且经过实测,这个归还内存的机制,在不同的垃圾回收器,甚至不同的 JDK 版本中还不一样!
一般为了避免 JVM 频繁的扩容缩容,我们会将 Xms 和 Xmx 配置为相等的大小,避免这个扩容的操作。
那是不是只要把 Xms 和 Xmx 配置成一样的大小,这个 JAVA 进程一启动就会占用这个大小的内存呢?并不是的,不会的,哪怕你 Xms6G,启动也只会占用实际写入的内存。这是因为进程在申请内存时,操作系统并不是直接分配物理内存给进程的,而是分配一块虚拟空间,到真正往这块虚拟空间写入数据时才会通过缺页异常(Page Fault)处理机制分配物理内存。可以简单地认为操作系统的内存分配是“惰性”的,分配并不会发生实际的占用,有数据写入时才会发生内存占用。所以哪怕配置了Xms6G,启动后也不会直接占用 6G 内存,实际占用的内存取决于你有没有往这 6G 内存区域中写数据的。
(猜测:当Xms和Xmx设置一样时,堆内存一开始不会占满,当占满时应该不会再锁绒,因为缩容的下限是 Xms)
正常来说,我们也不需要太关心 JVM 是否有归还了内存给操作系统,但是如果一台服务器内有运行着多个 Java 进程,如果都不归还内存的话就可能导致服务器内存不足,各个进程能使用的内存也不足,这种情况下可能就需要通过配置参数来让 JVM 能在内存空闲时自动归还内存。
参考:https://www.cnblogs.com/hd92/p/16934280.html、https://blog.csdn.net/ancoa/article/details/125354061