当前位置: 首页 > 后端技术 > Java

谈JVM xmx, xms等内存相关参数合理性设置

时间:2023-04-01 20:57:39 Java

浅谈JVM的xmx、xms等内存相关参数的合理设置。那么有没有可能在不影响暂停时间的情况下提高吞吐量,甚至缩短暂停时间呢?答案是可能的。可以通过增加内存占用来同时优化两个目标。这篇文章会讲到内存相关的内容。内存占用一般是指应用程序运行所需的全部内存,包括堆内内存和堆外内存1.堆内内存堆内内存是分配给JVM的一部分内存,用于存放所有Java类对象实例和数组,这是JVMGC操作的对象。我们先回顾一下堆内存的模型:图1.堆内存包括新生代(浅绿色)和老年代(浅蓝色)。在JDK7或者更早的版本中,图中右侧还有一个Permanentgeneration(永久代在逻辑上位于JVM的堆区,但也叫非堆内存,用metaspace代替)在JDK8中)。JVM有动态调整内存的策略,通过-Xms、-Xmx指定堆中内存动态调整的上下限。JVM在初始化的时候,实际上只分配了一部分内存。初始堆内存大小可以通过-XX:InitialHeapSize指定。未分配的空间就是图中虚拟的部分。每次GC都会调整新生代和老年代的大小,保证存活对象的百分比在一定的阈值范围内,直到达到Xms指定的下限或Xms指定的上限。(阈值范围由-XX:MinHeapFreeRatio、XX:MaxHeapFreeRatio指定,默认值分别为40、70)。GC调优中另一个重要的参数是老年代与年轻代的比例,由-XX:NewRatio设置,与此相关的是-XX:MaxNewSize和-XX:NewSize,分别设置了年轻代的大小。上下限,-Xmn直接指定了新生代的大小。1.1参数默认值?-Xmx:Xmx的默认值比较复杂。有时在官方文档中写成1GB,但实际值取决于JRE版本,JVM模式(客户端,服务器)和系统(平台类型,32位,64位)等都是相关的。经过查阅源码和实验,确定在生产环境(服务器模式,64位Centos,JRE8)下,Xmx的默认值可以按照以下规则计算:容器内存小于等于2G:默认值为容器内存的1/2,最小16MB,最大512MB。?容器内存大于2G:默认为容器内存的1/4,最大可达32G。?-Xms:默认为容器内存的1/64,最小为8MB。如果Xmx被显式指定并且小于容器内存的1/64,则Xms的默认值是Xmx指定的值。?-NewRatio:默认为2,即新生代与老年代的比例为1:2,新生代的大小为堆内存的1/3。注意:在JRE1.8.0_131版本之前,JVM无法感知Docker的资源限制。当Xmx和Xms没有明确指定时,主机的内存将被用来计算默认值。1.2Bestpractice每次Eden区域满时触发YGC,每次YGC完成时,当提升到老年代的对象大小超过老年代剩余空间时触发FGC。所以基本上,GC频率和堆内存的大小成反比,也就是说堆内存越大,吞吐量越大。如果Xmx设置太小,不仅会浪费容器资源,而且在大流量下会频繁的GC,导致吞吐量降低、响应变长、CPU占用率高、java.lang.OutOfMemoryError异常等一系列问题。当然Xmx不建议设置太大,否则会导致进程挂掉或者使用容器swap。因此,合理设置Xmx非常重要,尤其是1.8.0_131之前的版本,必须明确指定Xmx。建议设置为容器内存的50%,不能超过容器内存的80%。JVM的动态内存策略不适合服务,因为每次GC都需要计算Heap是否需要伸缩,内存抖动需要向系统申请或释放内存,尤其是在服务重启的warm-up阶段,内存抖动会更频繁。另外,如果容器中还有其他进程还在消耗内存,在JVM内存抖动的时候,JVM内存可能会申请内存失败,导致OOM。因此,建议在服务模式下将Xms设置为与Xmx相同的值。NewRatio建议在2到3之间,最佳选择取决于对象的生命周期分布。一般先确定老年代的空间(足够存放所有活数据,适当增加10%~20%),剩下的就是年轻代,一定要小于老年代。另外,以上建议是基于每个容器部署一个JVM实例的用法。如果有个别需求,需要在一个容器中启用多个JVM,或者包含其他语言,研发需要根据业务需要在推荐值范围内分配JVM的Xmx。2.堆外内存和堆上内存对应堆外内存。堆外内存包括CodeCache、MemoryPool、StackMemory、DirectByteBuffers、Metaspace等很多部分,其中,我们需要重点关注DirectByteBuffers和Metaspace。2.1DirectByteBuffersDirectByteBuffers是系统的本机内存,不位于JVM中。狭义的堆外内存是指DirectByteBuffers。为什么要使用系统的本机内存?为了更高效地进行SocketI/O或文件读写等内核态资源操作,会使用到JNI(JavaNativeInterface)。这时候操作的记忆就需要是连续的、确定的。但是Heap中的内存并不能保证是连续的,GC也可能随时导致对象移动。因此,在进行Output操作时,不是直接使用Heap上的数据,而是需要先从Heap复制到nativememory中,而Input操作则相反。因此,为了避免冗余拷贝,提高I/O效率,很多第三方包和框架都使用DirectByteBuffers,这比Netty要好。DirectByteBuffers虽然具有以上优点,但使用起来也存在一定的风险。使用直接字节缓冲区的常见方法是使用java.nio.DirectByteBuffer的unsafe.allocateMemory方法创建它们。DirectByteBuffer对象只保存了系统分配的本机内存的大小和起始位置。这些nativememory的释放需要等到DirectByteBuffer对象被回收。.在某些特殊情况下(如JVM没有FGC,设置-XX:+DisableExplicitGC禁用System.gc),这部分对象会不断增加,直到堆外内存达到-XX:MaxDirectMemorySize指定的大小或耗尽所有系统内存。当没有明确指定MaxDirectMemorySize时,默认值为0,实际上就是代码中的Runtime.getRuntime().maxMemory(),略小于-Xmx指定的值(堆内存的最大值减去幸存者区域的大小)。这个默认值有点太大,如果MaxDirectMemorySize没有设置或者设置太大,可能会出现堆外内存泄漏,导致进程被系统杀死。由于存在一定的风险,建议在启动参数中显式指定-XX:MaxDirectMemorySize的值,并满足以下规则:?Xmx*110%+MaxDirectMemorySize+系统预留内存<=容器内存?Xmx110%extra10%是保留其他堆外内存的保守估计。个别业务运行时线程比较多,需要自己判断。Xss线程的数量需要添加到上式的左侧。系统根据容器规格预留512M到1G内存。?适当增加MaxDirectMemorySize比例,用于更多I/O业务。2.2元空间元空间(metaspace)是JDK8中方法区的一种新实现,取代了之前的永久代,用于保存类、方法、数据结构等运行时信息和元信息。.很多研发开发者可能遇到过老版本的java.lang.OutOfMemoryError:PermGenSpace,意思是永久代空间不够。可以使用-XX:PermSize,-XX:MaxPermSize来指定永久代的初始大小和最大大小。Metaspace取代永久代,位置从JVM内存更改为系统本机内存,默认的最大空间限制也被取消。与此相关的主要有两个参数:?-XX:MaxMetaspaceSize指定元空间的最大空间,默认为容器的所有剩余空间。?-XX:MetaspaceSize指定元空间第一次扩展的大小,默认为20.8M。指定时,默认没有上限,需要特别注意内存泄漏。如果程序动态创建了很多类,或者出现java.lang.OutOfMemoryError:Metaspace,建议显式指定-XX:MaxMetaspaceSize。另外,Metaspace的实际分配大小是根据需要逐渐扩大的。每次扩展都需要一个FGC。-XX:MetaspaceSize的默认值比较小,需要频繁的GC才能扩展到需要的大小。通过如下日志可以看到Metaspace引起的FGC:[FullGC(MetadataGCThreshold)...]为了减少warm-up的影响,可以将-XX:MetaspaceSize,-XX:MaxMetaspaceSize指定为相同的值.另外,很多应用已经从JDK7升级到JDK8,但是启动参数中仍然存在-XX:PermSize、-XX:MaxPermSize。这些参数无效。建议将它们更改为-XX:MetaspaceSize、-XX:MaxMetaspaceSize。3、应用健康检查规则泰山应用健康现在支持扫描JVM相关风险,在应用TAB的JVM配置检测项下。主要包括以下检测:检测指标风险等级检测规则JVM版本mediumrisk版本不低于1.8.0_191JVMGC方法mediumrisk所有groupGC方法一致Xmx明确指定highrisk,Xms在50%~80以内%ofthecontainermemory明确指定了mediumrisk,它等于Xmx指定的值。明确规定了堆外内存,堆*1.1+堆外+系统预留<=容器内存。并行GC线程。InnerConcGCThreads低危ConcGCThreads在ParallelGCThreads的20%~50%以内(限CMS,G1)CICompilerCount低危指定CICompilerCount在推荐值的50%~150%以内(限1.8