前言

数据库是大家会普遍重视的一个领域,异步通信一般用不到,虚拟机在大部分时候不会出问题,常被人忽视,所以我打算先学习虚拟机,从零单排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

参数解释:

  1. Xms20M:初始堆20M
  2. Xmx20M:最大堆20M
  3. Xmn10M:年轻代10M
  4. -XX:+PrintGCDetails:打印GC详细信息
  5. -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)。
你可能感兴趣的内容
遗失的JVM堆内存 收藏,2586 浏览
JVM的类加载机制 收藏,3201 浏览
0条评论

dexcoder

这家伙太懒了 <( ̄ ﹌  ̄)>
Owner