Java JVM 优化指南:从 Java 8 到 Java 21

同事最近遇到一次OOM,导出内存镜像后发现很小,说明内存分配的少了,Docker容器分配了4G内存,内存占用这么少,很显然是没有基本的JVM配置导致的,本文针对 Java8 到 Java 21 给出一个基础的配置,首先保证能合理的使用内存。

本文针对4G内存进行配置,分配3/4的内存给JVM使用,留1/4给堆外内存以及操作系统使用。如果你分配了不同的内存大小,按照类似比例计算即可。

0. 为什么需要配置 JVM?

我们需要先了JVM运行时堆的默认值:

  • 初始堆(Xms)大小为物理内存的 1/64
  • 最大堆(Xmx)大小为物理内存的 1/4

当给容器分配4G内存时,默认最多使用1G,大部分内存都被浪费了,所以我们必须合理配置才能充分利用内存。

1. 针对4G Java 8环境推荐的JVM参数配置

对于在4G内存环境中运行的Java 8应用程序,以下是推荐的JVM参数配置:

1
-Xms3072m -Xmx3072m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=8M -XX:InitiatingHeapOccupancyPercent=45 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof -XX:MaxRAMPercentage=75.0

2. 推荐JVM参数的介绍

让我们逐一解释这些参数:

  • -Xms3072m -Xmx3072m: 设置初始堆大小和最大堆大小为3GB。这是4GB总内存的75%,为系统和本地内存留出足够空间。
  • -XX:+UseG1GC: 使用G1垃圾收集器,它在大多数场景下都表现良好。
  • -XX:MaxGCPauseMillis=200: 设置目标最大GC暂停时间为200毫秒,平衡吞吐量和延迟。
  • -XX:G1HeapRegionSize=8M: 设置G1区域大小为8MB,适合大多数应用。
  • -XX:InitiatingHeapOccupancyPercent=45: 当堆使用率达到45%时开始并发GC周期,提供良好的缓冲。
  • -XX:+HeapDumpOnOutOfMemoryError: 在发生OOM错误时自动生成堆转储,有助于问题诊断。
  • -XX:HeapDumpPath=/path/to/heapdump.hprof: 指定堆转储文件的保存路径。
  • -XX:MaxRAMPercentage=75.0: 限制JVM最多使用系统内存的75%。

这种配置旨在优化内存使用,提高GC效率,同时为诊断潜在问题提供支持。

注意 -XX:HeapDumpPath=/path/to/heapdump.hprof 参数需要修改路径。容器环境(含K8S)使用时可以考虑指定到挂载的存储路径,避免容器自动重启丢失。

3. 如何应用JVM参数

使用上面的JVM参数替代下文中的 [JVM参数] 部分。

物理机环境

在命令行中直接启动Java应用时使用:

1
java [JVM参数] -jar your-application.jar

Docker环境

在Dockerfile中设置:

1
2
3
4
5
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENV JAVA_OPTS="[JVM参数]"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]

Docker Compose环境

在 docker-compose.yml 文件中设置:

1
2
3
4
5
6
7
8
9
10
version: '3'
services:
myapp:
image: myapp:latest
environment:
- JAVA_TOOL_OPTIONS=-Xms3072m -Xmx3072m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=8M -XX:InitiatingHeapOccupancyPercent=45 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:MaxRAMPercentage=75.0
volumes:
- ./tmp:/tmp
ports:
- "8080:8080"

Kubernetes环境

在部署配置文件中设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
image: myapp:latest
env:
- name: JAVA_TOOL_OPTIONS
value: "[JVM参数]"

4. 针对低版本Java 8的额外配置

对于Java 8u191之前的版本,需要添加以下参数来启用容器感知:

1
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

同时,将-XX:MaxRAMPercentage=75.0替换为-XX:MaxRAMFraction=1

5. 针对Java 11的调整

Java 11默认启用了容器感知,因此可以移除容器相关的特殊参数。主要调整如下:

  • 移除-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap
  • 保留-XX:MaxRAMPercentage=75.0
  • 考虑添加-XX:+UseContainerSupport(虽然默认已启用)

6. 针对Java 17的调整

Java 17进一步改进了GC和性能。建议的调整包括:

  • 考虑使用ZGC:-XX:+UseZGC
  • 如果使用G1GC,可以尝试-XX:G1PeriodicGCInterval=0来禁用周期性GC
  • 添加-XX:+UseStringDeduplication以减少内存使用

7. 针对Java 21的调整

Java 21引入了更多优化和新特性。建议的调整包括:

  • 使用新的Generational ZGC:-XX:+UseZGC -XX:+ZGenerational
  • 启用预览特性:--enable-preview
  • 考虑使用虚拟线程:-Djdk.virtualThreadScheduler.parallelism=<number of cores>
  • 如果使用G1GC,可以尝试-XX:G1PeriodicGCSystemLoadThreshold=<threshold>来基于系统负载触发周期性GC

记住,这些是建议的起点。实际配置应该根据您的具体应用程序需求和性能测试结果进行微调。始终在生产环境使用前进行彻底的测试和评估。

欢迎大家提出自己的见解和经验,帮助更多的Java开发者优化他们的JVM配置。


Java JVM 优化指南:从 Java 8 到 Java 21
https://blog.mybatis.io/post/41686ab2.html
作者
Liuzh
发布于
2024年10月16日
许可协议