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转到老年代
Java堆内存

  • 新生代用来放新分配的对象;新生代中经过垃圾回收,没有回收掉的对象,被复制到老年代
  • 老年代存储对象比新生代存储对象的年龄大得多
  • 老年代存储一些大对象
  • 整个堆大小=新生代+老年代
  • 新生代=Eden+存活区
  • 从前的持久代,用来存放Class,Method等元信息的区域,从JDK8开始去掉了,取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地内存

对象的内存布局

对象在内存中存储的布局(这里以HotSpot虚拟机为例来说明),分别:对象头,实例数据和对齐填充

  • 对象头,包含两个部分
  1. 对象头
    包含连个部分:HashCode,GC分代年龄,锁状态标志等
  2. 类型指针
    对象指向它的类元数据的指针
  • 实例数据
    真正存放对象实例数据的地方
  • 对齐填充
    这一部分不一定存在,也没有什么特殊含义,仅仅是占位符,因为HotSpot要求对象其实地址都是8字节的整数倍,如果不是,就对齐

对象的访问定位

在JVM规范中只规定了reference类型是一个指向对象的引用,但没有规定这个引用具体如何去定位,访问堆中对象的具体位置

因此对象的访问方式取决于JVM的实现,目前主流的有:使用句柄或使用指针两种方式

  • 使用句柄:Java堆中会划出一块内存来作为句柄池,reference中存储句柄的地址,句柄中存储对象的实例数据和类元数据的地址,如下图所示:
    Java堆内存
  • 使用指针:Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址,如下图所示:Java堆内存

对于使用句柄来访问对象的方式,它的优点,对象被移动的时候只需要修改句柄当中的实例数据的指针,而reference不需要修改,但是他需要两次指针定位来访问数据,速度慢

而指针的方式,速度快,比句柄方式少一次指针定位的开销