jvm堆
堆的概述
- 一个jvm只有一个堆内存,是线程共享的
- 堆的大小是可以调节的,堆可以处于物理上不连续的内存空间中,但是在逻辑上应该认为是连续的
- 堆还可以划分线程私有的缓冲区TLAB(Thread local allocation buffer)
- 几乎所有的对象实例和数组在运行时都分配在堆上(逃逸分析)
- 在方法结束后,堆中的对象不会立马被移除,仅仅在GC的时候才会被回收
- 堆是GC执行垃圾回收的重点地区
堆的内存划分
- jdk1.7之前,逻辑上堆划分为年轻代+老年代+永久代。永久代就是方法区的具体的实现
- jdk1.8开始,逻辑上划分为年轻代+老年代+元空间,元空间是方法区的具体实现
- 年轻代和老年代的默认比是1:2,年轻代分为伊甸园区,幸存者0区,幸存者1区,默认比是8:1:1
对象分配过程
- new的对象放置在伊甸园区,该区是有大小限制
- 当eden的空间填满时,程序又需要存放对象,这时候jvm的垃圾回收期将对eden进行垃圾回收(minor gc),将不被引用的对象销毁,加载新的对象
- 然后将伊甸园区的剩余对象加载到幸存者区
- 如果再次出发垃圾回收,上次幸存者0区没有销毁的对象,会随着eden存活的对象放入幸存者1区,
- 如果再次触发垃圾回收,这是重新放回幸存者0区,然后再幸存者1区
- 当转移次数达到默认阈值15次时,数据就会被放入老年代。
- 当养老区内存不足的时候,再触发major gc 进行垃圾回收,
- 若养老区gc完之后依然无法进行内存的保存,则爆oom异常
- 特殊的:如果数据很大,可能直接放入老年区。如果在minor gc之后,幸存者区放不下也会放入老年区,
- 动态对象年龄判断:如果幸存者区中相同年龄的所有对象大小的总和,大于幸存者区的一半,则年龄大于或者等于该年龄的对象可以直接放入老年代。
逃逸分析和标量替换
- 逃逸分析: 如果一个对象只在方法中使用,则没有逃逸,如果在其他方法中有引用,则发生了逃逸
- 栈上分配:如果经过逃逸分析之后,发现对象并没有逃逸出方法,则可能优化成栈上分配,这样就无需放入堆中,则无需进行gc.
- 同步省略:分析锁对象是否只能被一个线程所使用,如果没有逃逸,则可以吧同步操作去掉
- 标量替换: java的基本数据类型就是标量,如果经过逃逸分析,发现一个对象没有逃逸,就可以吧这个对象拆解成若干个其中包含的若干个成员变量来代替,这个过程就是标量替换。
- 虽然有逃逸分析,但是hotspot并没有启用,因为逃逸分析的代价有时比栈上分配更多,所以栈上分配是没有的,标量替换是有的。