jvm高手来啊,xms和xmx一样出现问题.

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm高手来啊,xms和xmx一样出现问题.相关的知识,希望对你有一定的参考价值。

我看到这样的一段话:
我把-Xms和-Xmx设置成一样时,那就意味着,只有当java使用完所有内存时才会回收垃圾,这样一来内存当然不停的涨。
我就不理解内存不停的涨,我是这样想的,-Xms和-Xmx设置成一样时,xmx已有最大值啊,为什么还说不停的涨啊。

这句话里“内存不停的涨”的意思是在Xmx的范围内内存的占用会不停的涨,但是一旦涨到达到Xmx值的时候就会进行垃圾回收了,内存分配不会超过这个值的,如果进行垃圾回收后仍然不够用,就会报内存溢出的错误。

附:
JVM申请一块内存的过程:
A. JVM会试图为相关Java对象在Eden中初始化一块内存区域
B. 当Eden空间足够时,内存申请结束。否则到下一步
C. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收);释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区/OLD区
D. Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区
E. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)
F. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”

另:
xms/xmx:定义YOUNG+OLD段的总尺寸,ms为JVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。在用户生产环境上一般将这两个值设为相同,以减少运行期间系统在内存申请上所花的开销。追问

我的意思是ms和mx设置一样时,把ms看成mx,那么启动后,内存的大小是最大的,但是这个最大是规定了,为什么规定了还不停的涨。

追答

YOUNG+OLD段的总尺寸不会超过你设置的mx值的,如果超过的话会报“out of memory错误”;那个所谓的”不停的涨“的意思是在mx的范围内不停的涨,直到达到这个值。

参考技术A 只有当java使用完所有内存时才会回收垃圾,这样一来内存当然不停的涨。这说的不对!
jvm gc是分代进行的,大致分为年轻代、老年代和持久代,-Xms和-Xmx是针对heep设置的,也就是年轻代和老年代,一定次数的年轻代gc还存活的对象或年轻代大小不够放的对象才会进老年代,而老年代的空间不够用时才会进行一次full gc。
所以说如果不存在内存泄漏的情况下内存应该不会不停的涨,当然超过了就内存溢出了
可以了解下jvm gc
参考技术B 只有当java使用完所有内存时才会回收垃圾,这样一来内存当然不停的涨。这说的不对

JVM故障问题排查心得「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

为什么我设置的大小关系没有错,还会OOMKilled?

这种问题常发生在JDK8u131或者JDK9版本之后所出现在容器中运行JVM的问题:在大多数情况下,JVM将一般默认会采用宿主机Node节点的内存为Native VM空间(其中包含了堆空间、直接内存空间以及栈空间),而并非是是容器的空间为标准。

堆内存和VM实际分配内存不一致

-XshowSettings:vm

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_java

Jps -lVvm

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_堆内存_02

我们在运行的时候将JVM堆内存内存设置为3000MB,而-XshowSettings:vm打印出的JVM将最大堆大小为1.09G,如果按照这个内存进行分配内存的话很可能会导致实际内存和预分配内存所造成的不一致问题。

如何解决此问题

JVM 感知 cgroup 限制

解决JVM内存超限的问题,这种方法可以让JVM自动感知Docker容器的cgroup限制,从而动态的调整堆内存大小。

JDK8u131在JDK9中有一个很好的特性,即JVM能够检测在Docker容器中运行时有多少内存可用。为了使jvm保留根据容器规范的内存,必须设置标志​​-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap​​。

注意:如果将这两个标志与Xms和Xmx标志一起设置,那么jvm的行为将是什么?-Xmx标志将覆盖-XX:+ UseCGroupMemoryLimitForHeap标志

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_堆内存_03

参数分析

  • ​-XX:+ UseCGroupMemoryLimitForHeap​​标志使JVM可以检测容器中的最大堆大小。
  • ​-Xmx​​标志将最大堆大小设置为固定大小。

除了JVM的堆空间,还会对于非堆Noheap和JVM的东西,还会有一些额外的内存使用情况。

使用JDK9的容器感知机制尝试

设置了容器有4GB内存分配,而JVM使用1GM作为最大堆,因为容器中除了JVM之外没有其他进程在运行,所以我们还可以进一步扩大一下对于Heap堆的分配?

-XX:MaxRAMFraction

在较低的版本的时候可以使用-XX:MaxRAMFraction参数,它告诉JVM使用可用内存/MaxRAMFract作为最大堆。使用-XX:MaxRAMFractinotallow=1,我们将几乎所有可用内存用作最大堆。

问题分析

最大堆占用总内存是否仍然会导致你的进程因为内存的其他部分(如“元空间”)而被杀死?

答案:MaxRAMFractinotallow=1仍将为其他非堆内存留出一些空间

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_堆内存_04

注意:如果容器使用堆外内存,这可能会有风险,因为几乎所有的容器内存都分配给了堆。您必须将-XX:MaxRAMFractinotallow=2设置为堆只使用50%的容器内存,或者使用Xmx。

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_JVM_05

容器内部感知CGroup资源限制

Docker1.7开始将容器cgroup信息挂载到容器中,所以应用可以从 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件获取内存、 CPU等设置,在容器的应用启动命令中根据Cgroup配置正确的资源设置 ​​-Xmx, -XX:ParallelGCThreads​​ 等参数

Java10中,改进了容器集成

Java10+废除了-XX:MaxRAM参数,因为JVM将正确检测该值。在Java10中,改进了容器集成,无需添加额外的标志,JVM将使用1/4的容器内存用于堆。

java10+确实正确地识别了内存的docker限制,但您可以使用新的标志MaxRAMPercentage(例如:-XX:MaxRAMPercentage=75)而不是旧的MaxRAMFraction,以便更精确地调整堆的大小。

java10+上的UseContainerSupport选项,而且是默认启用的,不用设置。同时 UseCGroupMemoryLimitForHeap 这个就弃用了,不建议继续使用,同时还可以通过​​ -XX:InitialRAMPercentage、-XX:MaxRAMPercentage、-XX:MinRAMPercentage​​ 这些参数更加细腻的控制 JVM 使用的内存比率。

-XX:MaxRAMFraction

Java 程序在运行时会调用外部进程、申请 Native Memory 等,所以即使是在容器中运行 Java 程序,也得预留一些内存给系统的。所以 -XX:MaxRAMPercentage 不能配置得太大。当然仍然可以使用-XX:MaxRAMFractinotallow=1选项来压缩容器中的所有内存。

上面我们知道了如何进行设置和控制对应的堆内存和容器内存的之间的关系,所以防止JVM的堆内存超过了容器内存,导致容器出现OOMKilled的情况。但是在整个JVM进程体系而言,不仅仅只包含了Heap堆内存,其实还有其他相关的内存存储空间是需要我们考虑的,一边防止这些内存空间会造成我们的容器内存溢出的场景。

Off Heap Space

接下来了我们需要进行分析出heap之外的一部分就是对外内存就是Off Heap Space,也就是Direct buffer memory堆外内存。主要通过的方式就是采用Unsafe方式进行申请内存,大多数场景也会通过Direct ByteBuffer方式进行获取。好废话不多说进入正题。

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_java_06

JVM参数MaxDirectMemorySize

研究一下jvm的-XX:MaxDirectMemorySize,该参数指定了DirectByteBuffer能分配的空间的限额,如果没有显示指定这个参数启动jvm,默认值是xmx对应的值(低版本是减去幸存区的大小)。

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_堆内存_07

而Runtime.maxMemory()在HotSpot VM里的实现是:

-Xmx减去一个survivor space的预留大小

DirectByteBuffer对象是一种典型的”冰山对象”,在堆中存在少量的泄露的对象,但其下面连接用堆外内存,这种情况容易造成内存的大量使用而得不到释放

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?_JVM_08

-XX:MaxDirectMemorySize=size 用于设置 New I/O (java.nio) direct-buffer allocations 的最大大小,size 的单位可以使用 k/K、m/M、g/G;如果没有设置该参数则默认值为 0,意味着JVM自己自动给NIO direct-buffer allocations选择最大大小。

-XX:MaxDirectMemorySize的默认值是什么?
  • 在sun.misc.VM中,它是​​Runtime.getRuntime.maxMemory()​​,这就是使用-Xmx配置的内容。而对应的JVM参数如何传递给JVM底层的呢?主要通过hotspot/share/prims/jvm.cpp。
  • jvm.cpp里头有一段代码用于把 ​​-XX:MaxDirectMemorySize​​​ 命令参数转换为key为 sun.nio.MaxDirectMemorySize的属性。我们可以看出来他转换为了该属性之后,进行设置和初始化直接内存的配置。针对于直接内存的核心类就在, 在​​-XX:MaxDirectMemorySize​​ 是用来配置NIO direct memory上限用的VM参数。但如果不配置它的话,direct memory默认最多能申请多少内存呢?这个参数默认值是-1,显然不是一个“有效值”。

sun.nio.MaxDirectMemorySize 属性,如果为 null 或者是空或者是 - 1,那么则设置为 Runtime.getRuntime ().maxMemory ();因为当MaxDirectMemorySize参数没被显式设置时它的值就是-1,在Java类库初始化时maxDirectMemory()被java.lang.System的静态构造器调用。

这个max_capacity()实际返回的是 -Xmx减去一个survivor space的预留大小

结论分析说明

MaxDirectMemorySize没显式配置的时候,NIO direct memory可申请的空间的上限就是-Xmx减去一个survivor space的预留大小。例如如果您不配置-XX:MaxDirectMemorySize并配置-Xmx5g,则"默认" MaxDirectMemorySize也将是5GB-survivor space区,并且应用程序的总堆+直接内存使用量可能会增长到5 + 5 = 10 Gb 。

其他获取 maxDirectMemory 的值的API方法

BufferPoolMXBean 及 JavaNioAccess.BufferPool (通过SharedSecrets获取) 的 getMemoryUsed 可以获取 direct memory 的大小;其中 java9 模块化之后,SharedSecrets 从原来的 sun.misc.SharedSecrets 变更到 java.base 模块下的 jdk.internal.access.SharedSecrets;要使用 --add-exports java.base/jdk.internal.access=ALL-UNNAMED 将其导出到 UNNAMED,这样才可以运行

内存分析问题

-XX:+DisableExplicitGC 与 NIO的direct memory

用了-XX:+DisableExplicitGC参数后,System.gc()的调用就会变成一个空调用,完全不会触发任何GC(但是“函数调用”本身的开销还是存在的哦~)。

做ygc的时候会将新生代里的不可达的DirectByteBuffer对象及其堆外内存回收了,但是无法对old里的DirectByteBuffer对象及其堆外内存进行回收,这也是我们通常碰到的最大的问题,如果有大量的DirectByteBuffer对象移到了old,但是又一直没有做cms gc或者full gc,而只进行ygc,那么我们的物理内存可能被慢慢耗光,但是我们还不知道发生了什么,因为heap明明剩余的内存还很多(前提是我们禁用了System.gc)。

以上是关于jvm高手来啊,xms和xmx一样出现问题.的主要内容,如果未能解决你的问题,请参考以下文章

JVM故障问题排查心得「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

JVM 三大性能调优参数-Xms -Xmx -Xss

JVM故障问题排查心得「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

关于ubuntu 10.10中的initramfs问题,高手来啊--

jvm常用参数

JVM XMX设置多大比较好,Docke容器里该怎么设置JVM呢@无界编程