Java 堆区
Java堆,是Java虚拟机管理的最大的一块内存,也是GC的主战场,里面存放的是几乎所有的对象实例和数组数据。
JIT编译器有栈上分配、标量替换等优化技术的实现导致部分对象实例数据不存在Java堆,而是栈内存。
从内存回收角度,Java堆被分为新生代和老年代;这样划分的好处是为了更快的回收内存;
从内存分配角度,Java堆可以划分出线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB);这样划分的好处是为了更快的分配内存;
分配
对象创建的过程是在堆上分配着实例对象,那么对象实例的具体结构如下:
对于填充数据不是一定存在的,仅仅是为了字节对齐。
HotSpot VM 的自动内存管理要求对象起始地址必须是 8 字节的整数倍。
对象头本身是8的倍数,当对象的实例数据不是 8 的倍数,便需要填充数据来保证8字节的对齐。该功能类似于高速缓存行的对齐。
另外,关于在堆上内存分配是并发进行的,虚拟机采用CAS加失败重试保证原子操作,或者是采用每个线程预先分配 TLAB 内存。
回收
有些在堆区保存的对象,通过一定的技术手段,自动转变为在栈中完成生命周期,这种技术就是逃逸分析。
把本来存放在堆内存的数据分配到栈中。
这样,数据的生命周期就能随着入栈和出栈而完成管理,不需要像堆内存一样进行内存繁杂的回收操作,减轻堆内存的压力。
java 对细分
Java 堆还可以细分为:
新生代和老年代;
新生代再细致一点有 Eden 空间,From Survivor 空间,To Survivor 空间等。
堆区分代
堆区分代回收是为了区分不同对象的生命周期,并做出合理分配和回收操作。
从分配的角度来看,线程本地缓冲区(Thread local allocation buffer, TLAB)有利于更高效地划分线程私有的缓冲区。
大部分对象生命很短,基本熬不过第一次经历的垃圾收集。
新创建的对象首先存放在 Eden 区。
经过垃圾回收且存活的对象会进入两个 Survivor 中的一个。
此时,这个 Survivor 区就称为 To Survivor。
而另一个区称为 From Survivor。From Survivor 会把本次回收存活的对象移到 To Survivor,然后清空区域内所有对象。
From Survivor 和 To Survivor
From Survivor 和 To Survivor名字是相对的。
对象移出的区就称为From Survivor,对象进入的区域称为To Survivor。
大多数时间两个区有一个是占用的,另一个已经清空的。
这样,在Survivor的对象会在两个区中来回经历GC,达到一定年龄后会被移到老年代。
因为这个对象多次垃圾回收依然存活,表明这个对象比较稳定,此后在老年代经历垃圾回收的频率非常低。
如果一个新的对象太大,以至于新生代经过一次垃圾回收后依然没有足够空间存放它。JVM会通过分配担保来把这个对象放在老年代。
如果老年代空间不够,经过一次Full GC还是没有空间,那虚拟机无法为这个对象创建内存空间,只能抛出OOM异常停止运行。 |