对象的内存布局
堆的默认分配图
- 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 到这个对象不可达)时,证明此对象不可能再被使用。