---
title: "JVM 配置"
date: 0001-01-01
summary: "JVM 配置 #  本页介绍 config/jvm.options 文件中的 JVM 启动参数。这些参数在 Easysearch 启动时由 JVM 读取，修改后需要重启节点生效。
Easysearch 2.5.0 默认使用并推荐 JDK 25，Bundle 包默认内置 JDK 25。 JDK 21 仅用于兼容已部署且暂不方便升级 JDK 的客户环境。安全 agent 在 JDK 21 及以上统一加载，并要求禁用 JDK SecurityManager。 其他 JDK 的验证范围见安装指南。 请以目标发行包的默认配置为基础调整参数。下文保留的旧 JDK 版本条件仅用于解释配置语法和历史配置，不代表当前版本支持这些运行时。
 堆内存设置 #  堆内存是 JVM 配置中最重要的参数。
# 堆大小设为物理内存的 50%，但不超过 31 GB # -Xms 和 -Xmx 必须相同，避免运行时堆调整 -Xms16g -Xmx16g    参数 说明     -Xms JVM 初始堆大小   -Xmx JVM 最大堆大小    设置原则 #   -Xms 和 -Xmx 设为相同值：避免运行时堆大小调整带来的性能波动。 不超过物理内存的 50%：剩余内存留给操作系统文件缓存（Lucene 严重依赖 OS 页面缓存）。 不超过 31 GB（建议设为 31g 或 30g）：超过约 32 GB 后 JVM 无法使用压缩对象指针（Compressed OOPs），实际可用内存反而减少。 不低于 1 GB：过小的堆会导致频繁 GC。  验证压缩指针 #  GET _nodes/stats/jvm?"
---


# JVM 配置

本页介绍 `config/jvm.options` 文件中的 JVM 启动参数。这些参数在 Easysearch 启动时由 JVM 读取，修改后需要**重启节点**生效。

Easysearch 2.5.0 默认使用并推荐 **JDK 25**，Bundle 包默认内置 JDK 25。
JDK 21 仅用于兼容已部署且暂不方便升级 JDK 的客户环境。安全 agent 在 JDK 21 及以上统一加载，并要求禁用 JDK SecurityManager。
其他 JDK 的验证范围见[安装指南]({{< relref "../../install-guide/_index.md" >}})。
请以目标发行包的默认配置为基础调整参数。下文保留的旧 JDK 版本条件仅用于解释配置语法和历史配置，不代表当前版本支持这些运行时。

---

## 堆内存设置

堆内存是 JVM 配置中最重要的参数。

```bash
# 堆大小设为物理内存的 50%，但不超过 31 GB
# -Xms 和 -Xmx 必须相同，避免运行时堆调整
-Xms16g
-Xmx16g
```

| 参数 | 说明 |
|------|------|
| `-Xms` | JVM 初始堆大小 |
| `-Xmx` | JVM 最大堆大小 |

### 设置原则

1. **`-Xms` 和 `-Xmx` 设为相同值**：避免运行时堆大小调整带来的性能波动。
2. **不超过物理内存的 50%**：剩余内存留给操作系统文件缓存（Lucene 严重依赖 OS 页面缓存）。
3. **不超过 31 GB**（建议设为 `31g` 或 `30g`）：超过约 32 GB 后 JVM 无法使用压缩对象指针（Compressed OOPs），实际可用内存反而减少。
4. **不低于 1 GB**：过小的堆会导致频繁 GC。

### 验证压缩指针

```bash
GET _nodes/stats/jvm?filter_path=**.using_compressed_ordinary_object_pointers
```

返回 `true` 表示压缩指针生效。

### 通过环境变量覆盖

如果不想修改 `jvm.options` 文件，可以通过环境变量覆盖：

```bash
ES_JAVA_OPTS="-Xms4g -Xmx4g" ./bin/easysearch
```

> 环境变量的优先级高于 `jvm.options` 文件中的设置。

---

## 垃圾回收器

Easysearch 2.5.0 在默认 JDK 25 和兼容运行时 JDK 21 上均使用 G1GC。发行配置中还保留以下版本条件：

- **JDK 8-10**：使用 CMS（Concurrent Mark-Sweep）
- **JDK 11+**：默认使用 G1GC

```bash
# JDK 8-10: CMS 垃圾回收器
8-10:-XX:+UseConcMarkSweepGC
8-10:-XX:CMSInitiatingOccupancyFraction=75
8-10:-XX:+UseCMSInitiatingOccupancyOnly

# JDK 11+: G1GC（推荐）
11-:-XX:+UseG1GC
11-:-XX:G1ReservePercent=25
11-:-XX:InitiatingHeapOccupancyPercent=30
```

| 参数 | 说明 |
|------|------|
| `8-10:-XX:+UseConcMarkSweepGC` | JDK 8-10 使用 CMS 垃圾回收器 |
| `8-10:-XX:CMSInitiatingOccupancyFraction=75` | JDK 8-10：老年代占用 75% 时触发 CMS 回收 |
| `11-:-XX:+UseG1GC` | JDK 11+ 使用 G1GC，支持更大的堆和更稳定的暂停时间 |
| `11-:-XX:G1ReservePercent=25` | G1GC 预留 25% 堆用于浮动垃圾 |
| `11-:-XX:InitiatingHeapOccupancyPercent=30` | 堆占用 30% 时启动并发回收 |

> **⚠️ 注意**：版本号前缀（如 `8-10:`、`11-`）表示该参数仅在指定 JDK 版本范围内生效。除非有充分的理由和测试数据，**不建议更改默认 GC 配置**。

---

## OOM 保护

```bash
# 发生 OutOfMemoryError 时生成堆转储文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=data

# 发生 OOM 时的错误日志路径
-XX:ErrorFile=logs/hs_err_pid%p.log
```

## Lucene 性能优化（JDK 9+）

```bash
# JDK 9+：启用 Lucene 性能优化模块
# jdk.unsupported：提供 sun.misc.Unsafe 用于低级内存操作
# jdk.management：提供扩展的管理接口
9-:--add-modules=jdk.unsupported,jdk.management
```

## Vector API 支持（JDK 20+）

```bash
# JDK 21-26：禁用 JVMCI 编译器，使 Lucene 可以启用 Vector API
21-26:-XX:+UnlockExperimentalVMOptions
21-26:-XX:-UseJVMCICompiler
21-26:-XX:-UnlockExperimentalVMOptions

# JDK 20+：加载 Vector API 孵化模块
20-:--add-modules=jdk.incubator.vector
```

Vector API 可以加速向量计算。启动器会自动将 `org.apache.lucene.vectorization.upperJavaFeatureVersion`
设置为实际运行 JDK 的主版本，使 Lucene 在 JDK 26、27 上也会尝试启用 Vector API；还需满足 JVM 类型、模块可用性等条件。
满足启用条件时，JDK 25 及以上使用发行包中的 JDK 25 向量化实现，该实现已在 JDK 26、27 上验证向量计算及 HNSW 持久化重开查询。
该参数由启动器生成，无须写入 `jvm.options`；这些启用条件不等于任意未来 JDK 的兼容保证。
如需覆盖自动值，可在 `jvm.options.d/*.options` 或 `ES_JAVA_OPTS` 中设置同名 `-D` 参数，后者优先；
Lucene 对该值与内置默认上限 25 取较大值，因此设为 25 可在 JDK 26、27 上恢复按版本回退的行为。
`--add-modules` 只负责加载模块，不能单独保证 Lucene 启用向量化；`Using incubator modules` 提示仍可能出现。
JDK 27 已不再识别 `UseJVMCICompiler`，保留旧配置升级 JDK 时，需将上述三行的版本条件同步为 `21-26:`。

---

## OOM 保护（旧语法）

```bash
# 发生 OutOfMemoryError 时生成堆转储文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=data

# 发生 OOM 时立即退出 JVM（让进程管理器自动重启）
-XX:+ExitOnOutOfMemoryError
```

| 参数 | 说明 |
|------|------|
| `-XX:+HeapDumpOnOutOfMemoryError` | OOM 时自动生成 heap dump 文件，用于事后分析 |
| `-XX:HeapDumpPath` | Heap dump 文件的存储路径（相对于 `$ES_HOME`） |
| `-XX:+ExitOnOutOfMemoryError` | OOM 时立即退出 JVM，而不是挂起 |

---

## GC 日志

Easysearch 默认启用 GC 日志，不同 JDK 版本的配置方式不同：

### JDK 8 GC 日志

```bash
8:-XX:+PrintGCDetails
8:-XX:+PrintGCDateStamps
8:-XX:+PrintTenuringDistribution
8:-XX:+PrintGCApplicationStoppedTime
8:-Xloggc:logs/gc.log
8:-XX:+UseGCLogFileRotation
8:-XX:NumberOfGCLogFiles=32
8:-XX:GCLogFileSize=64m
```

### JDK 9+ GC 日志

```bash
9-:-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m
```

| 参数部分 | 说明 |
|------|------|
| `gc*,gc+age=trace,safepoint` | 记录 GC 事件、年龄分布、安全点 |
| `file=logs/gc.log` | 输出到 `logs/gc.log` |
| `utctime,pid,tags` | 日志中包含 UTC 时间、进程 ID、标签 |
| `filecount=32,filesize=64m` | 最多 32 个轮转文件，每个 64 MB |

> GC 日志对排查性能问题非常重要，建议保持默认开启。

---

## 其他常用 JVM 参数

### 临时目录

```bash
# 临时文件目录
-Djava.io.tmpdir=${ES_TMPDIR}
```

### 安全检查（JDK 21 及以上统一加载 agent）

```bash
# Easysearch 2.5.0；其他发行版本使用对应包内的实际文件名
-Djava.security.manager=disallow
21-:-javaagent:lib/easysearch-security-agent-2.5.0.jar
```

`21-:` 表示 JDK 21 及以上加载同一个 Java agent 执行安全检查，每次启动只应加载一次。
通过 `java.security.manager=disallow` 禁用 JDK 自身的 SecurityManager；
启动时会显式校验没有安装 JDK SecurityManager，且 `java.security.manager` 的值严格为 `disallow`。
缺少该参数、将其改为其他值，或已安装 JDK SecurityManager，都会拒绝启动；JDK 25 也必须保留该参数。
请保留发行包生成的 agent 参数及相邻的 bootstrap jar；缺少 agent 时服务会拒绝启动。
升级并保留旧 `jvm.options` 时，必须同步新发行包的 agent 文件名和必要参数，同时保留自己的堆大小等设置。
还应保留发行配置中的 `21-:--enable-preview` 和 `19-:--enable-native-access=ALL-UNNAMED`；
JDK 21 需要这些参数来加载相应的 Lucene 和 custom-codecs 实现。
安全 agent 已在 JDK 21、25、26 上完成运行验证；Easysearch 2.5.0 发行包也已在 JDK 26、27 上通过实际启动验证，
可正常运行。默认和推荐运行时仍为 JDK 25。

`easysearch.yml` 中的 `security.enabled` 控制认证与 TLS 等安全模块功能，不用于关闭 JVM 安全 agent 或上述启动校验。

agent 读取核心策略和 `plugin-security.policy`，结合配置目录、数据目录等动态权限，
检查选定的文件操作、`Socket`/`SocketChannel` 连接及 `System.exit`/`Runtime.halt` 调用。
这些检查不覆盖所有文件或网络 API，也不复刻旧 SecurityManager 的反射、线程组或异步权限上下文。
插件安装时会标明已跳过的权限类型；声明这些权限不代表相应操作仍受强制检查。
第三方插件仍需作为可信代码管理。

### Locale 配置

```bash
# JDK 9-20：使用 SPI 和系统兼容性 Provider
9-20:-Djava.locale.providers=SPI,COMPAT

# JDK 21+：使用 SPI 和 CLDR Provider
21-:-Djava.locale.providers=SPI,CLDR
```

### 编译优化（JDK 23+）

```bash
# JDK 23：修复 MethodHandle 性能问题
23:-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.setAsTypeCache
23:-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.asTypeUncached
```

### Native Access（JDK 19+）

```bash
# JDK 19+：为所有未命名模块启用原生访问
19-:--enable-native-access=ALL-UNNAMED
```

| 参数 | 说明 |
|------|------|
| `-Djava.io.tmpdir` | JVM 临时文件目录 |
| `-javaagent` | 通过 `21-:` 在 JDK 21 及以上加载发行包提供的安全 agent |
| `-Djava.security.manager=disallow` | 禁用 JDK 自身的 SecurityManager |
| `-Djava.locale.providers` | JVM 使用的 Locale 提供者 |

### TLS 原生库加载（Linux/macOS）

在 Linux/macOS 的 x86_64、ARM64 平台上，启动脚本会从安全模块的 `tongsuo-openjdk-*.jar` 中提取当前平台的
Conscrypt 原生库到 `$ES_HOME/modules/security/native/`，供 TLS 加载；目标文件已存在时复用。
首次提取时，启动用户需能创建并写入该目录，所选 JDK 也必须包含 `jar` 工具。

---

## jvm.options 文件格式

`jvm.options` 文件中每行一个 JVM 参数，支持版本条件：

```bash
# 这是注释

# 无条件参数（所有 JVM 版本生效）
-Xms16g
-Xmx16g

# 版本条件参数
# JDK 8~10
8-10:-XX:+UseConcMarkSweepGC
# JDK 11+
11-:-XX:+UseG1GC
# JDK 9~20
9-20:-Djava.locale.providers=SPI,COMPAT
# JDK 21+
21-:-Djava.locale.providers=SPI,CLDR
```

### 版本条件语法

| 格式 | 说明 | 示例 |
|------|------|------|
| 无前缀 | 所有版本生效 | `-Xms16g` |
| `N` | 仅 JDK N 生效 | `23:-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.setAsTypeCache` |
| `N-` | JDK N 及以上版本 | `11-:-XX:+UseG1GC` (JDK 11+) |
| `N-M` | JDK N 到 M 版本 | `8-10:-XX:+UseConcMarkSweepGC` (JDK 8-10) |

- 以 `#` 开头的行是注释。
- 注释应单独占一行，不要放在 JVM 参数末尾。
- 空行会被忽略。

---

## jvm.options.d 目录

除了主 `jvm.options` 文件外，Easysearch 还会读取 `config/jvm.options.d/` 目录下所有以 `.options` 结尾的文件。每个文件的格式与主 `jvm.options` 完全相同。

### 使用场景

- **自定义参数与默认参数分离**：将自定义 JVM 参数放在独立文件中，避免修改默认的 `jvm.options` 文件，便于升级时保留定制配置
- **容器化部署**：通过挂载配置文件到 `jvm.options.d/` 目录，在不修改镜像的情况下调整 JVM 参数
- **按关注点组织**：将不同类型的参数（如 GC、调试、安全）放在不同文件中

### 示例

创建自定义堆内存配置：

```bash
# config/jvm.options.d/heap.options
-Xms16g
-Xmx16g
```

创建调试参数文件：

```bash
# config/jvm.options.d/debug.options
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/easysearch
-XX:ErrorFile=/var/log/easysearch/hs_err_pid%p.log
```

### 加载顺序

1. 先读取 `config/jvm.options` 主文件
2. 再按文件名字母顺序读取 `config/jvm.options.d/*.options`
3. 如果同一参数出现多次，后加载的值会覆盖先加载的值

> **注意**：`jvm.options.d/` 目录中的文件必须以 `.options` 为扩展名，其他文件会被忽略。

---

## 配置示例

以下是局部参数示例，不可用来整份替换发行包的 `jvm.options`。调整时保留前述安全 agent、
`-Djava.security.manager=disallow`、preview、native access 等必要参数。

### 生产环境（64 GB 服务器，JDK 25）

```bash
# 堆内存：物理内存的 50%，不超过 31 GB
-Xms31g
-Xmx31g

# G1GC（JDK 11+）
11-:-XX:+UseG1GC
11-:-XX:G1ReservePercent=25
11-:-XX:InitiatingHeapOccupancyPercent=30

# OOM 保护
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=data
-XX:ErrorFile=logs/hs_err_pid%p.log

# GC 日志
9-:-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m

# 其他
-Djava.io.tmpdir=${ES_TMPDIR}
```

### 开发环境（8 GB 笔记本）

```bash
-Xms1g
-Xmx1g
```

### 历史 CMS 配置（JDK 8-10，64 GB 服务器，不适用于 Easysearch 2.5.0）

```bash
-Xms31g
-Xmx31g

# CMS GC（JDK 8-10）
8-10:-XX:+UseConcMarkSweepGC
8-10:-XX:CMSInitiatingOccupancyFraction=75
8-10:-XX:+UseCMSInitiatingOccupancyOnly

# OOM 保护
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=data
-XX:ErrorFile=logs/hs_err_pid%p.log
```

---

## 常见问题

### 启动失败：堆内存分配失败

```
Initial heap size set to an invalid value
```

通常是因为 `-Xms` 或 `-Xmx` 超过了物理内存或操作系统限制。

### 频繁 Full GC

- 检查堆大小是否足够。
- 使用 `GET _nodes/stats/jvm` 查看堆使用率。
- 分析 GC 日志（`logs/gc.log`）定位原因。

### 压缩指针未启用

堆大小超过约 32 GB 时压缩指针失效。建议设为 `31g` 或以下。

---

## 延伸阅读

- [内存与缓存]({{< relref "./memory.md" >}}) — `bootstrap.memory_lock` 与缓存配置
- [硬件配置]({{< relref "../hardware.md" >}}) — 内存规格选型建议
- [系统调优]({{< relref "../settings.md" >}}) — `memlock`、文件描述符等 OS 参数

