对象的内存布局

堆的默认分配图
对象的内存布局

  • Java堆 = 老年代 + 新生代。
  • 新生代 = Eden + S0 + S1。
  • 新生代与老年代默认比例的值为 1:2 ,可以通过参数 –XX:NewRatio 配置。
  • 默认的,Eden : from : to = 8 : 1 : 1 ,可以通过参数–XX:SurvivorRatio 来设定。

方法区结构图
对象的内存布局
方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

对象的内存布局图
对象的内存布局
一个Java对象在堆内存中包括对象头、实例数据和补齐填充3个部分:

  • 对象头包括Mark Word(存储哈希码,GC分代年龄等) 和 类型指针(对象指向它的类型元数据的指针),如果是数组对象,还有一个保存数组长度的空间
  • 实例数据是对象真正存储的有效信息,包括了对象的所有成员变量,其大小由各个成员变量的大小共同决定。
  • 对齐填充不是必然存在的,仅仅起占位符的作用。

对象与Monitor关联结构图
对象的内存布局

对象是如何跟monitor有关联的呢?

一个Java对象在堆内存中包括对象头,对象头有Mark word,Mark word存储着锁状态,锁指针指向monitor地址。这其实是Synchronized的底层。

Java Monitor的工作机理图:
Java 线程同步底层就是监视锁Monitor~,如下是Java Monitor的工作机理图:
对象的内存布局

  • 想要获取monitor的线程,首先会进入_EntryList队列。
  • 当某个线程获取到对象的monitor后,进入_Owner区域,设置为当前线程,同时计数器_count加1。
  • 如果线程调用了wait()方法,则会进入_WaitSet队列。它会释放monitor锁,即将_owner赋值为- null,_count自减1,进入_WaitSet队列阻塞等待。
  • 如果其他线程调用 notify() / notifyAll() ,会唤醒_WaitSet中的某个线程,该线程再次尝试获取monitor锁,成功即进入_Owner区域。
  • 同步方法执行完毕了,线程退出临界区,会将monitor的owner设为null,并释放监视锁。

创建一个对象内存分配流程图
对象的内存布局

  • 对象一般是在Eden区生成。
  • 如果Eden区填满,就会触发Young GC。
  • 触发Young GC的时候,Eden区实现清除,没有被引用的对象直接被清除。
  • 依然存活的对象,会被送到Survivor区,Survivor =S0+S1.
  • 每次Young GC时,存活的对象复制到未使用的那块Survivor 区,当前正在使用的另外一块Survivor 区完全清除,接着交换两块Survivor 区的使用状态。
  • 如果Young GC要移送的对象大于Survivor区上限,对象直接进入老年代。
  • 一个对象不可能一直呆在新生代,如果它经过多次GC,依然活着,次数超过-XX:MaxTenuringThreshold的阀值,它直接进入老年代。简言之就是,对象经历多次滚滚长江,红尘世事,终于成为长者(进入老年代)

可达性分析算法判定对象存活
对象的内存布局
算法的核心思想:

  • 通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始根据引用关系向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可能再被使用。