JVM 堆内存模型
JVM 的堆分为两部分,分别为新生代和老年代
- 新生代:
新生代又分为两个区。一个伊甸(Eden)区,一个幸存者(survivor)区,幸存者区又分为 so 和 s1 区,新生代采用的是复制算法,因为新生的对象很多,大部分都是朝生息死的,所以采用复制算法最合理,新生的对象都要先放在伊甸区,在 Eden 区满的时候,就要进行 Minor GC 了。将 Eden 区存活的对象复制到 so 或者 s1 区,然后清空 Eden 区。之后如果 Eden 区再满的时候,就把 Eden 和非空的幸存者区里存活的对象放入另一个幸存者区,然后清空,so 和 s1 互相交替,如此循环。
参数控制-XX:SurvivorRatio
用来设置新生代中 Eden 空间和 from/to 空间的比例
默认-XX:SurvivorRatio=8
Eden:From:To 8:1:1 - 老年代
经过一定次数的 Minor GC 后,在新生代存活下来的对象会进入老年代,大对象也会直接进入老年代,老年代采用的是标记-清除法或者标记-整理法。因为老年代大部分都是存活时间比较长的对象。当老年代满的时候,就会进行Full GC。其回收速度一般会比 Minor GC 慢十倍以上 。
参数控制-XX:MaxTenuringThreshold
设置对象经过几次 GC 进入老年代,默认 15
参数控制-XX: PretenureSizeThreshold
设置指定大小 如果超过这个大小对象直接进入老年代 (前提使用 ParNew 或者 Serial 收集器)。
参数控制-XX: NewRatio
设置新生代和老年代的比例
JVM 常用参数
JVM 参数 | 说明 |
---|---|
-XX:+PrintGC | 使用这个参数,虚拟机启动后,只要遇到 GC 就会打印日志 |
-XX:MaxTenuringThreshold | 设置对象经过几次GC进入老年代,默认15 |
-XX:+PrintGCDetails | 可以查看详细信息,包括各区情况 |
-Xms | 设置java程序启动时初始堆大小 |
-Xmx | 设置程序能获得的最大堆大小 |
-Xss | 指定每个线程的最大深度 |
-Xmn | 可以设置新生代的大小 |
-XX:SurvivorRatio | 用来设置新生代中 Eden 空间和 from/to 空间的比例 |
-XX: NewRatio | 设置新生代和老年代的比例 |
-XX:+PrintCommandLineFlags | 将虚拟机显式和隐式的参数输出 |
-XX:-UseTLAB | 禁止为本地线程分配缓冲 |
对象先进入伊甸区
参数控制 -XX:+PrintGC -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:SurvivorRatio=8 -XX:-UseTLAB
public class Lecture02 {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] byte1, byte2, byte3, byte4;
byte1 = new byte[2 * _1MB];
byte2 = new byte[2 * _1MB];
byte3 = new byte[2 * _1MB];
byte4 = new byte[3 * _1MB];
}
}
GC日志如下:
[GC (Allocation Failure) [DefNew: 8096K->670K(9216K), 0.0060545 secs] 8096K->6814K(19456K), 0.0060974 secs] [Times: user=0.00 sys=0.02, real=0.01 secs]
Heap
def new generation total 9216K, used 3746K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 37% used [0x00000000fec00000, 0x00000000fef01168, 0x00000000ff400000)
from space 1024K, 65% used [0x00000000ff500000, 0x00000000ff5a79e8, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
在执行到最后一行代码的时候,此时Eden区已经放入了3个2M的对象,而Eden的最大空间大约是8M。再最后一个3M的对象想进入Eden区时发现空间不足,所以发生了一次 Minor GC ,但是因为Eden区的三个对象都是2M,而s1或s0空间只有1M。所以三个对象直接进入了老年代。最后一个3M对象进入Eden区。从GC日志上可以看出eden space 8192K, 37% used
。Eden区用了大约3M,tenured generation total 10240K, used 6144K
老年代用了大约6M。如果把byte4 = new byte[3 * _1MB];
这一行代码注释掉,就不会发生GC了。注意禁用缓存,不然会影响结果。
大对象直接进入老年代
当对象大小超过指定大小,就会直接进入老年代,用参数-XX:PretenureSizeThreshold
设置指定的大小
参数控制:-XX:PretenureSizeThreshold=2m -XX:+PrintGC -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:SurvivorRatio=8 -XX:-UseTLAB -XX:+UseSerialGC
public class Lecture03 {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] b = new byte[5 * _1MB];
}
}
打印日志如下:
Heap
def new generation total 9216K, used 1923K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 23% used [0x00000000fec00000, 0x00000000fede0f40, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 5120K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
没有发生GC, tenured generation total 10240K, used 5120K,老年代使用了大约5M,注意前提使用 ParNew或者Serial收集器和禁用线程缓存,不然影响结果。
对象到达指定年龄,进入老年代
-XX:MaxTenuringThreshold
,设置对象经过几次GC进入老年代,默认15,每经过一次GC对像的年龄就加一,当到达指定年龄就会进入老年代。我测试的指定大小为3
参数控制:-XX:+PrintGC -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=3 -XX:PretenureSizeThreshold=10m -XX:-UseTLAB
public class Lecture04 {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
//设置的长期存活对象,不要超过整个s0区的大小,否则会直接进入老年代
byte[] b1 = new byte[_1MB/4];
//将Eden 区塞满,触发MinorGC
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 10; j++) {
byte[] b = new byte[_1MB];
}
}
}
}
GC部分日志如下
[GC (Allocation Failure) [PSYoungGen: 7328K->1016K(9216K)] 7328K->1024K(19456K), 0.0012449 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 8184K->1016K(9216K)] 8192K->1024K(19456K), 0.0007303 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 8184K->984K(9216K)] 8192K->992K(19456K), 0.0006177 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 8152K->0K(9216K)] 8160K->948K(19456K), 0.0007460 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 7168K->0K(9216K)] 8116K->948K(19456K), 0.0003422 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 7168K->0K(9216K)] 8116K->948K(19456K), 0.0002871 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
可见在经历三次GC后,存活的对象都进入老年代了,新生代已经没有对象了,因为达到了指定年龄,进入老年代了。