Java程序员进阶三条必经之路:数据库、虚拟机、异步通信。
前言
数据库是大家会普遍重视的一个领域,异步通信一般用不到,虚拟机在大部分时候不会出问题,常被人忽视,所以我打算先学习虚拟机,从零单排Java高性能问题。
堆内存存储结构
Java6是以年代来规划内存的,而Java7的G1收集器则相反,这里以Java6为准。
Survivor1和Survivor2是一样大的,必有一个始终为空,容量小于Eden。
垃圾回收机制
年轻代采用复制算法,当回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor上,然后清理掉Eden和刚才用过的Survivor空间。每进行一次Minor GC(年轻代回收),对象的年龄就增加1岁(初始为0),当年龄增加到一定程度(默认15岁),就会被移到老年代。老年代的回收算法因篇幅有限在此略过。
从《深入理解Java虚拟机》第二版93页上抄一个例子来做个示范:
package com.jiuyan.mountain.jvm;
public class Test {
private static final int MB = 1024 * 1024;
public static void main(String[] args) {
byte[] bytes1, bytes2, bytes3, bytes4;
bytes1 = new byte[2 * MB];
bytes2 = new byte[2 * MB];
bytes3 = new byte[2 * MB];
bytes4 = new byte[4 * MB];
}
}
命令行执行:
java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 com/jiuyan/mountain/jvm/Test
参数解释:
- Xms20M:初始堆20M
- Xmx20M:最大堆20M
- Xmn10M:年轻代10M
- -XX:+PrintGCDetails:打印GC详细信息
- -XX:SurvivorRatio=8:Eden和一个Survivor的空间比例是8:1。
输出:
Heap
PSYoungGen total 9216K, used 6799K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 83% used [0x00000000ff600000,0x00000000ffca3f28,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)
PSPermGen total 21504K, used 2751K [0x00000000f4600000, 0x00000000f5b00000, 0x00000000fec00000)
object space 21504K, 12% used [0x00000000f4600000,0x00000000f48afc08,0x00000000f5b00000)
JVM没有进行垃圾回收,byte1、byte2、byte3、byte4总共10M内存,而年轻代只有9M内存,不应该啊。结果Eden有6M内存(bytes1,bytes2,bytes3),老年代有4M内存(bytes4),说明bytes4直接被分配到了老年代,因为在Survivor空间中当相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
那么我就调用System.gc()来主动触发一次GC。
输出:
[GC-- [PSYoungGen: 6635K->6635K(9216K)] 10731K->14827K(19456K), 0.0035280 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[Full GC [PSYoungGen: 6635K->2275K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 14827K->10467K(19456K) [PSPermGen: 2743K->2742K(21504K)], 0.0079080 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 2441K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 29% used [0x00000000ff600000,0x00000000ff8624d8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400030,0x00000000ff600000)
PSPermGen total 21504K, used 2750K [0x00000000f4600000, 0x00000000f5b00000, 0x00000000fec00000)
object space 21504K, 12% used [0x00000000f4600000,0x00000000f48af8d0,0x00000000f5b00000)
日志分析:
- GC和Full GC说明了这次垃圾收集的停顿类型,而不是用来区分年轻代还是老年代的。如果有“Full”,说明这次GC发生了STW(Stop-The-World)。
- PSYoungGen是采用Parallel Scavenge收集器的年轻代,ParOldGen是采用Parallel Old收集器的老年代,Tenured是采用Serial Old收集器的老年代。
- [PSYoungGen: 6635K->6635K(9216K)]表示GC前年轻代占用内存6M,GC后占用内存6M,内存区域总容量9M。10731K->14827K(19456K)表示GC前堆占用内存10M,GC后占用内存14M,堆总容量20M。
- GC过程是把年轻代中的4M内存复制到了老年代,所以才会出现10731K->14827K(19456K),Full GC过程是把年轻代中的4M内存回收掉,所以才会出现PSYoungGen: 6635K->2275K(9216K)。