关于jvm:谈JVM-xmx-xms等内存相关参数合理性设置

60次阅读

共计 5840 个字符,预计需要花费 15 分钟才能阅读完成。

作者: 京东批发 刘乐

说到 JVM 垃圾回收算法的两个优化标的:吞吐量和进展时长,并提到这两个优化指标是有抵触的。那么有没有可能进步吞吐量而不影响进展时长,甚至缩短进展时长呢?答案是有可能的,进步内存占用(Memory Footprint)就有可能同时优化这两个标的,这篇文章就来聊聊内存相干内容。

内存占用个别指利用运行须要的所有内存,包含堆内内存(On-heap Memory)和堆外内存(Off-heap Memory)

1. 堆内内存

堆内内存是调配给 JVM 的局部内存,用来寄存所有 Java Class 对象实例和数组,JVM GC 操作的就是这部分内容。咱们先来回顾一下堆内内存的模型:

图 1. 堆内内存

堆内内存包含年老代(浅绿色),老年代(浅蓝色),在 JDK7 或者更老的版本,图中左边还有个永恒代(永恒代在逻辑上位于 JVM 的堆区,但又被称为非堆内存,在 JDK8 中被元空间取代)。JVM 有动静调整内存策略,通过 -Xms,-Xmx 指定堆内内存动静调整的上上限。在 JVM 初始化时理论只调配局部内存,可通过 -XX:InitialHeapSize 指定初始堆内存大小,未被调配的空间为图中 virtual 局部。年老代和老年代在每次 GC 的时候都有可能调整大小,以保障存活对象占用百分比在特定阈值范畴内,直到达到 Xms 指定的上限或 Xms 指定的下限。(阈值范畴通过 -XX:MinHeapFreeRatio, XX:MaxHeapFreeRatio 指定,默认值别离为 40,70)。

GC 调优中还有个的重要参数是老年代和年老代的比例,通过 -XX:NewRatio 设定,与此相关的还有 -XX:MaxNewSize 和 -XX:NewSize,别离设定年老代大小的上上限,-Xmn 则间接指定年老代的大小。

1.1 参数默认值

◦-Xmx: Xmx 的默认值比较复杂,官网文档上有时候写的是 1GB,但理论值跟 JRE 版本、JVM 模式(client, server)和零碎(平台类型,32 位,64 位)等都无关。通过查阅源码和试验,确定在生产环境下(server 模式,64 位 Centos,JRE 8),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。

NOTE:在 JRE 版本 1.8.0_131 之前,JVM 无奈感知 Docker 的资源限度,Xmx, Xms 未明确指定时,会应用宿主机的内存计算默认值。

1.2 最佳实际

因为每次 Eden 区满就会触发 YGC,而每次 YGC 的时候,降职到老年代的对象大小超过老年代残余空间的时候,就会触发 FGC。所以根本来说,GC 频率和堆内内存大小是成反比的,也就是说堆内内存越大,吞吐量越大。

如果 Xmx 设置过小,不仅节约了容器资源,在大流量下会频繁 GC,导致一系列问题,包含吞吐量升高,响应变长,CPU 升高,java.lang.OutOfMemoryError 异样等。当然 Xmx 也不倡议设置过大,否则会导致过程 hang 住或者应用容器 Swap。所以正当设置 Xmx 十分重要,特地是对于 1.8.0_131 之前的版本,肯定要明确指定 Xmx。举荐设置为容器内存的 50%,不能超过容器内存的 80%。

JVM 的动态内存策略不太适宜服务应用,因为每次 GC 须要计算 Heap 是否须要伸缩,内存抖动须要向零碎申请或开释内存,特地是在服务重启的预热阶段,内存抖动会比拟频繁。另外,容器中如果有其余过程还在生产内存,JVM 内存抖动时可能申请内存失败,导致 OOM。因而倡议服务模式下,将 Xms 设置 Xmx 一样的值。

NewRatio 倡议在 2~3 之间,最优抉择取决于对象的生命周期散布。个别先确定老年代的空间(足够放下所有 live data,并适当减少 10%~20%),其余是年老代,年老代大小肯定要小于老年代。

另外,以上倡议都是基于一个容器部署一个 JVM 实例的应用状况。有个别需要,须要在一个容器内启用多个 JVM,或者蕴含其余语言的,研发须要按业务需要在推荐值范畴内调配 JVM 的 Xmx。

2. 堆外内存

和堆内内存对应的就是堆外内存。堆外内存包含很多局部,比方 Code Cache,Memory Pool,Stack Memory,Direct Byte Buffers, Metaspace 等等,其中咱们须要重点关注的是 Direct Byte Buffers 和 Metaspace。

2.1 Direct Byte Buffers

Direct Byte Buffers 是零碎原生内存,不位于 JVM 里,广义上的堆外内存就是指的 Direct Byte Buffers。为什么要应用零碎原生内存呢? 为了更高效的进行 Socket I/ O 或文件读写等内核态资源操作,会应用 JNI(Java 原生接口),此时操作的内存须要是间断和确定的。而 Heap 中的内存不能保障间断,且 GC 也可能导致对象随时挪动。因而波及 Output 操作时,不间接应用 Heap 上的数据,须要先从 Heap 上拷贝到原生内存,Input 操作则相同。因而为了防止多余的拷贝,进步 I / O 效率,不少第三方包和框架应用 Direct Byte Buffers,比 Netty。

Direct Byte Buffers 尽管有上述长处,但应用起来也有肯定危险。常见的 Direct Byte Buffers 应用办法是用 java.nio.DirectByteBuffer 的 unsafe.allocateMemory 办法来创立,DirectByteBuffer 对象只保留了零碎调配的原生内存的大小和启始地位,这些原生内存的开释须要等到 DirectByteBuffer 对象被回收。有些非凡的状况下(比方 JVM 始终没有 FGC,设置 -XX:+DisableExplicitGC 禁用了 System.gc),这部分对象会继续减少,直到堆外内存达到 -XX:MaxDirectMemorySize 指定的大小或者耗尽所有的零碎内存。

MaxDirectMemorySize 不明确指定的时候,默认值为 0,在代码中理论为 Runtime.getRuntime().maxMemory(),略小于 -Xmx 指定的值(堆内内存的最大值减去一个 Survivor 区大小)。此默认值有点过大,MaxDirectMemorySize 未设置或设置过大,有可能产生堆外内存泄露,导致过程被零碎 Kill。

因为存在肯定危险,倡议在启动参数里明确指定 -XX:MaxDirectMemorySize 的值,并满足上面规定:

Xmx * 110% + MaxDirectMemorySize + 零碎预留内存 <= 容器内存

◦Xmx 110% 中额定的 10% 是留给其余堆外内存的,是个激进预计,个别业务运行时线程较多,需自行判断,上式中左侧还需加上 Xss 线程数

◦零碎预留内存 512M 到 1G,视容器规格而定

◦I/ O 较多的业务适当进步 MaxDirectMemorySize 比例

2.2 Metaspace

Metaspace(元空间)是 JDK8 对于办法区新的实现,取代之前的永恒代,用来保留类、办法、数据结构等运行时信息和元信息的。很多研发在老版本时可能遇到过 java.lang.OutOfMemoryError: PermGen Space,这阐明永恒代的空间不够用了,能够通过 -XX:PermSize,-XX:MaxPermSize 来指定永恒代的初始大小和最大大小。Metaspace 取代永恒代,地位由 JVM 内存变成零碎原生内存,也勾销默认的最大空间限度。与此有关的参数次要有上面两个:

◦-XX:MaxMetaspaceSize 指定元空间的最大空间,默认为容器残余的所有空间

◦-XX:MetaspaceSize 指定元空间首次裁减的大小,默认为 20.8M

因为 MaxMetaspaceSize 未指定时,默认无下限,所以须要特地关注内存泄露的问题,如果程序动静的创立了很多类,或呈现过 java.lang.OutOfMemoryError:Metaspace,倡议明确指定 -XX:MaxMetaspaceSize。另外 Metaspace 理论调配的大小是随着须要逐渐扩充的,每次扩充须要一次 FGC,-XX:MetaspaceSize 默认的值比拟小,须要频繁 GC 裁减到须要的大小。通过上面的日志能够看到 Metaspace 引起的 FGC:

[Full GC (Metadata GC Threshold) …]

为缩小预热影响,能够将 -XX:MetaspaceSize,-XX:MaxMetaspaceSize 指定成雷同的值。另外不少利用由 JDK7 降级到了 JDK8,然而启动参数中仍有 -XX:PermSize,-XX:MaxPermSize,这些参数是不失效的,倡议批改成 -XX:MetaspaceSize,-XX:MaxMetaspaceSize。

3. 利用衰弱度查看规定

泰山利用衰弱度当初已反对扫描 JVM 相干危险,在利用 TAB 的 JVM 配置检测项下。次要包含以下检测:

检测指标 危险等级 巡检规定
JVM 版本 中危 版本不低于 1.8.0_191
JVM GC 办法 中危 所有分组 GC 办法统一
Xmx 高危 明确指定,并且在容器内存的 50%~80% 范畴内
Xms 中危 明确指定,并且等于 Xmx 指定的值
堆外内存 中危 明确指定,并且 堆内 *1.1+ 堆外 + 零碎预留 <= 容器内存
ParallelGCThreads 高危 ParallelGCThreads 在容器 CPU 核数的 50%~100% 范畴内
ConcGCThreads 低危 ConcGCThreads 在 ParallelGCThreads 的 20%~50% 范畴内(限 CMS,G1)
CICompilerCount 低危 指定 CICompilerCount 在推荐值 50%~150% 内(限 1.8<JRE<1.8.0_131)

上一篇文章曾经说了 ParallelGCThreads,这里再补充一下新反对的两个检测,ConcGCThreads,CICompilerCount。

ConcGCThreads 个别称为并发标记线程数,为了缩小 GC 的 STW 的工夫,CMS 和 G1 都有并发标记的过程,此时业务线程仍在工作,只是并发标记是 CPU 密集型工作,业务的吞吐量会降落,RT 会变长。ConcGCThreads 的默认值不同 GC 策略略有不同,CMS 下是(ParallelGCThreads + 3) / 4 向下取整,G1 下是 ParallelGCThreads / 4 四舍五入。一般来说采纳默认值就能够了,然而还是因为在 JRE 版本 1.8.0_131 之前,JVM 无奈感知 Docker 的资源限度的问题,ConcGCThreads 的默认值会比拟大(20 左右),对业务会有影响。

CICompilerCount 是 JIT 进行热点编译的线程数,和并发标记线程数一样,热点编译也是 CPU 密集型工作,默认值为 2。在 CICompilerCountPerCPU 开启的时候(JDK7 默认敞开,JDK8 默认开启),手动指定 CICompilerCount 是不会失效的,JVM 会应用零碎 CPU 核数进行计算。所以当应用 JRE8 并且版本小于 1.8.0_131,采纳默认参数时,CICompilerCount 会在 20 左右,对业务性能影响较大,特地是启动阶段。倡议降级 Java 版本,非凡状况要应用老版本 Java 8,请加上 -XX:CICompilerCount=[n], 同时不能指定 -XX:+CICompilerCountPerCPU,下表给出了生产环境下常见规格的推荐值。

容器 CPU 核数 1 2 4 8 16
CICompilerCount 手动指定推荐值 2 2 3 3 8



4. 批改倡议

1)再次倡议降级 JRE 版本到 1.8.0_191 及以上;2)倡议在 Shell 脚本中,Export JAVA_OPTS 环境变量, 至多蕴含以下几项 ( 方括号中的值依据文中举荐选取):

-server -Xms[8192m] -Xmx[8192m] -XX:MaxDirectMemorySize=[4096m]

如果非凡起因要应用 1.8.0_131 以下版本,则同时须要加上以下参数 ( 方括号中的值依据文中举荐选取):

-XX:ParallelGCThreads=[8] -XX:ConcGCThreads=[2] -XX:CICompilerCount=[2]

上面的项倡议测试后应用,需自行确定具体大小(特地是应用 JRE8 但仍配置 -XX:PermSize,-XX:MaxPermSize 的利用):

-XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m

环境变量设置如下例子:

export JAVA_OPTS="-Djava.library.path=/usr/local/lib -server -Xms4096m -Xmx4096m -XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=512m -XX:MaxDirectMemorySize=2048m -XX:ParallelGCThreads=8 -XX:ConcGCThreads=2 -XX:CICompilerCount=2 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/Logs -XX:+UseG1GC [other_options...] -jar jarfile [args...]"

另外,如果利用未接入 UMP 或 PFinder,JAVA_OPTS 中尽量不要用 Shell 函数或者变量,否则衰弱度有可能会提醒解析失败。

NOTE: Java options 的应用应该依照上面的程序:

◦执行类:java [-options] class [args…]

◦执行包:java [-options] -jar jarfile [args…] 或 java -jar [-options] jarfile [args…]

即 options 要放到执行对象之前,局部利用应用了以下程序:

java -jar jarfile [-options] [args…] 或者 java -jar jarfile [args…] [-options]

这些 Java options 都不会失效。

正文完
 0