堆内部结构大致分为
- 新生代
- 老年代
- 永久代在 JDK 8 之后就没有了
新生代
先说新生代,我们知道新生代主要分为一个Eden和两个Survior区。新生代是大部分对象创建和销毁的地方。新创建的对象,都会先放在Eden中。在GC的时候,就会将Eden中和其中一个当前Suivior(也称为From-Suivior)中存活的对象移动到另外一个空闲的Suivior(to-Suivior)。这种设计有个好处就是可以防止内存碎片化。
在Eden区域中,还有一个概念叫TLAB,也就是Thread Local Alloction Buffer。OpenJdk衍生出来的JVM都提供了TLAB的设计。这是JVM为每一个线程分配的一个私有缓存区域。否则,多线程同时分配内存时,为避免操作同一地址,可能需要使用加锁等机制。进而影响分配的速度。
tart、end 就是起始地址,top(指针)则表示已经分配到哪里了。所以我们分配新对象,JVM 就会移动 top,当 top 和 end 相遇时,即表示该缓存已满,JVM 会试图再从 Eden 里分配一块儿。
看到上图,新生代除了前面的Eden,TLAB和Survivor这三个区域,还有一个Vitrual区域。这是因为堆的大小设置是可以设置为动态变化的,有个最小堆大小和一个最大堆大小。初始时堆的大小并不是直接就是最大堆大小的体积,而是需要的时候在慢慢调大,而可以调大利用的部分就是Vitrual,表示暂时不可用的区域。
老年代
放置长生命周期的对象,通常都是从 Survivor 区域拷贝过来的对象。当然,也有特殊情况,我们知道普通的对象会被分配在 TLAB 上;如果对象较大,JVM 会试图直接分配在 Eden 其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM 就会直接分配到老年代。
相关参数设置
- -Xmx value :最大堆体积
- -Xms value:初始的最小堆体积
- -XX:NewRatio=value:老年代和新生代的比例 默认是2;表示老年代的体积大小是新生代的2倍。新生代是堆大小的 1/3。
- -XX:NewSize=value:直接指定新生代的大小
- -XX:SurvivorRatio=value:表示Eden 和 Survivor 的大小比率。默认是8,表示一个Survivor是Eden的1/8的大小,是新生代的1/10;比如新生代大小为10,Eden就为8,两个Survivor分别是1.