Java堆内存
概述
- Java堆用来存放应用系统创建的对象和数组,所有线程共享Java堆
- Java堆是在运行期间动态分配内存大小,自动进行垃圾回收
- Java垃圾回收(GC)主要就是回收堆内存,对分代GC来说,堆也是分代的
Java堆的结构
Young Generation(新生代)
Eden Space(伊甸园)
Survivor(存活区,分别叫from和to)
Survivor Ratio(存活区和伊甸园的比例)
Old Generation(老年代)
Tenured Space(养老区)
Init Obj Alloc:JVM的大部分对象创建在新生代的伊甸园中
当多次垃圾回收,回收不掉的一些内容,或者虚拟机判断需要存下来的内容,会从To space转到老年代
- 新生代用来放新分配的对象;新生代中经过垃圾回收,没有回收掉的对象,被复制到老年代
- 老年代存储对象比新生代存储对象的年龄大得多
- 老年代存储一些大对象
- 整个堆大小=新生代+老年代
- 新生代=Eden+存活区
- 从前的持久代,用来存放Class,Method等元信息的区域,从JDK8开始去掉了,取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地内存
对象的内存布局
对象在内存中存储的布局(这里以HotSpot虚拟机为例来说明),分别:对象头,实例数据和对齐填充
- 对象头,包含两个部分
- 对象头
包含连个部分:HashCode,GC分代年龄,锁状态标志等 - 类型指针
对象指向它的类元数据的指针
- 实例数据
真正存放对象实例数据的地方 - 对齐填充
这一部分不一定存在,也没有什么特殊含义,仅仅是占位符,因为HotSpot要求对象其实地址都是8字节的整数倍,如果不是,就对齐
对象的访问定位
在JVM规范中只规定了reference类型是一个指向对象的引用,但没有规定这个引用具体如何去定位,访问堆中对象的具体位置
因此对象的访问方式取决于JVM的实现,目前主流的有:使用句柄或使用指针两种方式
- 使用句柄:Java堆中会划出一块内存来作为句柄池,reference中存储句柄的地址,句柄中存储对象的实例数据和类元数据的地址,如下图所示:
- 使用指针:Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址,如下图所示:
对于使用句柄来访问对象的方式,它的优点,对象被移动的时候只需要修改句柄当中的实例数据的指针,而reference不需要修改,但是他需要两次指针定位来访问数据,速度慢
而指针的方式,速度快,比句柄方式少一次指针定位的开销