是什么

Oracle官网上对于AlwaysPreTouch的解释如下,

Pre-touch the Java heap during JVM initialization. Every page of the heap is thus demand-zeroed during initialization rather than incrementally during application execution.

当启用了该参数的时候,JVM启动阶段就会将堆初始化好,而不是在应用运行阶段动态增长。

应用场景

不少人会疑惑,这个参数有什么用处?如果在应用启动后,有大量的数据需要从新生代拷贝到老年代,并且你的应用属于对延迟敏感的应用,那么这个参数就会有用了。这是因为当大量的数据拷贝的时候,会涉及到大量的内存操作,包括内存申请。当启用了这个参数,这个阶段的内存申请就可以忽略了,直接分配好的内存做拷贝即可。

例子

public class GCTest {
    public static Map<Integer, LargeObject> holder = new HashMap();

    public static void main(String[] args) {
        for (int i = 0; i < 4000 * 1024; i++) {
            if (i % (1024 * 10) == 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            holder.put(i, new LargeObject());
        }
    }

    private static class LargeObject {
        byte[] content;
        public LargeObject() {
            content = new byte[1024];
        }
    }
}

使用如下的参数运行上面的程序

-Xmx5g
-Xms5g
-Xmn2g
-XX:PermSize=512m
-XX:MaxPermSize=512m
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:SurvivorRatio=8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
-XX:+PrintTenuringDistribution
-XX:+PrintSafepointStatistics
-XX:+PrintGCApplicationStoppedTime

可以得到如下的日志,

    2019-06-17T00:38:53.224-0800: Total time for which application threads were stopped: 0.0000963 seconds, Stopping threads took: 0.0000303 seconds
2019-06-17T00:38:54.228-0800: Total time for which application threads were stopped: 0.0001227 seconds, Stopping threads took: 0.0000299 seconds
2019-06-17T00:38:55.189-0800: [GC (Allocation Failure) 2019-06-17T00:38:55.189-0800: [ParNew
Desired survivor size 107347968 bytes, new threshold 1 (max 15)
- age   1:  214511352 bytes,  214511352 total
: 1677824K->209663K(1887488K), 1.6348205 secs] 1677824K->1553934K(5033216K), 1.6348809 secs] [Times: user=3.73 sys=2.10, real=1.64 secs] 
2019-06-17T00:38:56.824-0800: Total time for which application threads were stopped: 1.6350482 seconds, Stopping threads took: 0.0000188 seconds
2019-06-17T00:38:59.005-0800: [GC (Allocation Failure) 2019-06-17T00:38:59.005-0800: [ParNew
Desired survivor size 107347968 bytes, new threshold 1 (max 15)
- age   1:  214682016 bytes,  214682016 total
: 1887487K->209664K(1887488K), 0.9228701 secs] 3231758K->3284881K(5033216K), 0.9229299 secs] [Times: user=2.84 sys=2.94, real=0.92 secs] 
2019-06-17T00:38:59.928-0800: Total time for which application threads were stopped: 0.9230800 seconds, Stopping threads took: 0.0000358 seconds

两次young gc的时间分别是1.64s和0.92s。加上-XX:+AlwaysPreTouch再运行一次可以看到如下的日志,

2019-06-17T00:39:43.948-0800: Total time for which application threads were stopped: 0.0002106 seconds, Stopping threads took: 0.0000550 seconds
2019-06-17T00:39:45.174-0800: [GC (Allocation Failure) 2019-06-17T00:39:45.174-0800: [ParNew
Desired survivor size 107347968 bytes, new threshold 1 (max 15)
- age   1:  214511040 bytes,  214511040 total
: 1677824K->209664K(1887488K), 0.9713961 secs] 1677824K->1553935K(5033216K), 0.9714646 secs] [Times: user=3.72 sys=0.62, real=0.97 secs] 
2019-06-17T00:39:46.146-0800: Total time for which application threads were stopped: 0.9716097 seconds, Stopping threads took: 0.0000305 seconds
2019-06-17T00:39:47.070-0800: Total time for which application threads were stopped: 0.0002052 seconds, Stopping threads took: 0.0000513 seconds
2019-06-17T00:39:48.259-0800: [GC (Allocation Failure) 2019-06-17T00:39:48.259-0800: [ParNew
Desired survivor size 107347968 bytes, new threshold 1 (max 15)
- age   1:  214682752 bytes,  214682752 total
: 1887488K->209664K(1887488K), 0.4816425 secs] 3231759K->3284890K(5033216K), 0.4816978 secs] [Times: user=2.87 sys=0.02, real=0.49 secs] 
2019-06-17T00:39:48.741-0800: Total time for which application threads were stopped: 0.4818360 seconds, Stopping threads took: 0.0000286 seconds

可以看到两次gc的时间分别降到了0.97s和0.49s。

OpenJDK中的实现

下面的代码基于OpenJDK8。

  if (pre_touch || AlwaysPreTouch) {
    os::pretouch_memory(previous_high, unaligned_new_high);
  }

如果启用了这个参数就会调用os::pretouch_memory进行预处理。

void os::pretouch_memory(char* start, char* end) {
  for (volatile char *p = start; p < end; p += os::vm_page_size()) {
    *p = 0;
  }
}

从start开始导致end,设置内容为0。

需要注意

启用这个参数会导致启动的时候变慢,尤其当堆设置的比较大的时候。