首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >JVM参数AlwaysPreTouch作用是什么

JVM参数AlwaysPreTouch作用是什么

作者头像
码农戏码
发布2026-06-25 19:59:04
发布2026-06-25 19:59:04
1320
举报

在 https://xie.infoq.cn/article/a97c795c86b4c6e5c721fa394 这篇文章中作者有个疑问:

使用top命令 和 NMT 分别查看的内存大小为什么不一致

代码语言:javascript
复制
top

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 36257 dou+      20   0   10.8g  54200  17668 S  99.7   0.0  13:04.15 java
代码语言:javascript
复制
Native Memory Tracking:

Total: reserved=2813709KB, committed=1497485KB
-                 Java Heap (reserved=1048576KB, committed=1048576KB)
                            (mmap: reserved=1048576KB, committed=1048576KB) 

-                     Class (reserved=1056899KB, committed=4995KB)
                            (classes #442)
                            (malloc=131KB #259) 
                            (mmap: reserved=1056768KB, committed=4864KB) 

-                    Thread (reserved=258568KB, committed=258568KB)
                            (thread #127)
                            (stack: reserved=258048KB, committed=258048KB)
                            (malloc=390KB #711) 
                            (arena=130KB #234)

-                      Code (reserved=266273KB, committed=4001KB)
                            (malloc=33KB #309) 
                            (mmap: reserved=266240KB, committed=3968KB) 

-                        GC (reserved=164403KB, committed=164403KB)
                            (malloc=92723KB #6540) 
                            (mmap: reserved=71680KB, committed=71680KB) 

-                  Compiler (reserved=152KB, committed=152KB)
                            (malloc=4KB #36) 
                            (arena=148KB #21)

-                  Internal (reserved=14859KB, committed=14859KB)
                            (malloc=14827KB #3632) 
                            (mmap: reserved=32KB, committed=32KB) 

-                    Symbol (reserved=1423KB, committed=1423KB)
                            (malloc=936KB #111) 
                            (arena=488KB #1)

-    Native Memory Tracking (reserved=330KB, committed=330KB)
                            (malloc=118KB #1641) 
                            (tracking overhead=211KB)

-               Arena Chunk (reserved=178KB, committed=178KB)
                            (malloc=178KB) 

-                   Unknown (reserved=2048KB, committed=0KB)
                            (mmap: reserved=2048KB, committed=0KB) 

    ......

此时疑问就产生了,为什么 NMT 中的 committed ,即日志详情中 Total: reserved=2813709KB, committed=1497485KB 中的 1497485KB 与 top 中 RES 的大小 54200KB 存在如此大的差异?

解答这个问题?需要先理解TOP的VIRT 和 RES 含义,以及操作系统的内存管理

TOP命令中 VIRT 和 RES 两个区域做下解释:

VIRT(virtual memory usage):进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等,另外 VIRT = SWAP + RES,其中 swap 区域是需要内存置换的区域(可参考操作系统虚拟内存原理)。

RES(resident memory usage):又称为 RSS,是进程在 RAM 中真正占用的内存大小。RES 包含了它所链接的动态库被加载到物理内存中的内存、栈内存和堆内存。动态链接库占用的内存会被多个进程共享,所以RSS并不能准确反映单进程的内存占用情况。

RSS 内存区域包括了线程堆栈、直接内存、通过mmap映射的文件和JVM字节码等。详细说明参考: https://stackoverflow.com/questions/38597965/difference-between-resident-set-size-rss-and-java-total-committed-memory-nmt

RSS = Heap size + MetaSpace + OffHeap size

操作系统对内存的分配管理典型地分为两个阶段:保留(reserve)和提交(commit)。

保留阶段告知系统从某一地址开始到后面的 dwSize 大小的连续虚拟内存需要供程序使用,进程其他分配内存的操作不得使用这段内存;提交阶段将虚拟地址映射到对应的真实物理内存中,这样这块内存就可以正常使用

JVM 中所谓的 commit 内存,只是将内存 mmaped 映射为可读可写可执行的状态!而在 Linux 中,在分配内存时又是 lazy allocation 的机制,只有在进程真正访问时才分配真实的物理内存。所以 NMT 中所统计的 committed 并不是对应的真实的物理内存,自然与 RES 等统计方式无法对应起来

JVM 为我们提供了一个参数 -XX:+AlwaysPreTouch,使我们可以在启动之初就按照内存页粒度都访问一遍 Heap,强制为其分配物理内存以减少运行时再分配内存造成的延迟(但是相应的会影响 JVM 进程初始化启动的时间)

开启 AlwaysPreTouch 参数后,NMT 统计的 commited 已经与 top 中的 RES 差不多了,之所以不完全相同是因为该参数只能 Pre-touch 分配 Java heap 的物理内存,至于其他的非 heap 的内存,还是受到 lazy allocation 机制的影响。

-XX:+AlwaysPreTouch 是 JVM 的一个重要参数,它的作用是在 JVM 启动时预先接触(pre-touch)所有堆内存页

详细作用机制

默认行为(不使用该参数)

代码语言:javascript
复制
// JVM默认行为:延迟分配物理内存
-Xms4g -Xmx4g

•JVM 启动时只保留4GB的虚拟地址空间•物理内存是按需分配的(懒分配)•当应用程序实际访问内存时,才会触发缺页中断,分配物理内存

使用 -XX:+AlwaysPreTouch

代码语言:javascript
复制
// 启用预分配
-Xms4g -Xmx4g -XX:+AlwaysPreTouch

•JVM 在启动过程中会立即分配并初始化所有4GB物理内存•通过写入每个内存页来强制操作系统分配物理内存

主要作用

1. 提高运行时性能

代码语言:javascript
复制
# 没有预分配:运行时可能遇到缺页中断
Page Fault -> 分配物理内存 -> 继续执行
# 有预分配:运行时无缺页中断,直接执行

2. 稳定内存占用

代码语言:javascript
复制
// 应用启动后的内存行为对比
public class MemoryIntensiveApp {
    public void process() {
        byte[] data = new byte[1024 * 1024]; // 1MB
        // 没有预分配:这里可能触发缺页中断,导致延迟
        // 有预分配:内存已准备好,无延迟
    }
}

性能影响分析

启动时间影响

代码语言:javascript
复制
# 测试启动时间差异
time java -Xms4g -Xmx4g -jar app.jar          # 快速启动
time java -Xms4g -Xmx4g -XX:+AlwaysPreTouch -jar app.jar  # 较慢启动

结果示例:

•无预分配:启动时间 2-3 秒•有预分配:启动时间 10-15 秒(取决于堆大小)

运行时性能提升

代码语言:javascript
复制
// 内存密集型操作的性能对比
public class Benchmark {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        // 大量内存分配操作
        for (int i = 0; i < 10000; i++) {
            byte[] chunk = new byte[1024 * 64]; // 64KB
            Arrays.fill(chunk, (byte)1); // 强制内存访问
        }

        long duration = System.currentTimeMillis() - start;
        System.out.println("耗时: " + duration + "ms");
    }
}

适用场景

推荐使用的情况

代码语言:javascript
复制
# 1. 对延迟敏感的应用
java -Xms8g -Xmx8g -XX:+AlwaysPreTouch -jar trading-app.jar

# 2. 需要稳定性能的服务
java -Xms4g -Xmx4g -XX:+AlwaysPreTouch -jar web-service.jar

# 3. 内存密集型批处理
java -Xms16g -Xmx16g -XX:+AlwaysPreTouch -jar data-processor.jar

不推荐使用的情况

代码语言:javascript
复制
# 1. 短期运行的应用
java -Xms1g -Xmx1g -jar short-lived-tool.jar  # 不需要预分配

# 2. 内存受限的环境
# 预分配会立即占用所有内存,可能影响其他进程

# 3. 需要快速启动的场景
java -Xms2g -Xmx2g -jar cli-tool.jar  # 快速启动更重要

与其他参数的配合

与 GC 参数配合

代码语言:javascript
复制
# 配合 G1GC 使用
java -Xms8g -Xmx8g \
     -XX:+UseG1GC \
     -XX:+AlwaysPreTouch \
     -XX:MaxGCPauseMillis=200 \
     -jar application.jar

在容器环境中的使用

代码语言:javascript
复制
# Dockerfile 示例
FROM openjdk:11-jre

# 在容器中预分配内存有助于稳定性
CMD ["java", "-Xms1g", "-Xmx1g", "-XX:+AlwaysPreTouch", "-jar", "/app.jar"]
代码语言:javascript
复制
# Kubernetes 配置
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        resources:
          requests:
            memory: "2Gi"  # 确保足够内存用于预分配
          limits:
            memory: "2Gi"
        command:
        - java
        - -Xms1g
        - -Xmx1g
        - -XX:+AlwaysPreTouch
        - -jar
        - /app.jar

实际效果验证

监控内存分配

代码语言:javascript
复制
# 使用 pmap 查看内存分配
pmap -x <pid> | grep total

# 使用 /proc 文件系统
cat /proc/<pid>/status | grep -E "VmSize|VmRSS"

JVM 内部统计

代码语言:javascript
复制
// 通过 JMX 监控内存
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;

MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
System.out.println("Heap Memory Usage: " + memoryBean.getHeapMemoryUsage());
System.out.println("Non-Heap Memory Usage: " + memoryBean.getNonHeapMemoryUsage());

总结

-XX:+AlwaysPreTouch 的核心价值:

•✅ 优点:提高运行时性能,减少缺页中断,稳定内存占用•❌ 缺点:增加启动时间,立即占用所有物理内存•🎯 适用:长期运行、对性能敏感的服务•⚠️ 注意:在内存受限或需要快速启动的环境中慎用

这个参数是用 启动时间换取运行时性能 的典型例子,需要根据具体应用场景权衡使用。

Reference

https://xie.infoq.cn/article/a97c795c86b4c6e5c721fa394

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农戏码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 详细作用机制
    • 默认行为(不使用该参数)
    • 使用 -XX:+AlwaysPreTouch
  • 主要作用
    • 1. 提高运行时性能
    • 2. 稳定内存占用
  • 性能影响分析
    • 启动时间影响
    • 运行时性能提升
  • 适用场景
    • 推荐使用的情况
    • 不推荐使用的情况
  • 与其他参数的配合
    • 与 GC 参数配合
    • 在容器环境中的使用
  • 实际效果验证
    • 监控内存分配
    • JVM 内部统计
  • 总结
  • Reference
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档