线程共享:方法区,堆 线程独享: 栈,本地方法栈,程序计数器。
在JDK8之前, 我们称之为永久代, 但是两者之间并不是等价的,只是在HotSpot设计团队, 把使用永久代来实现方法区而已, 这样就可以像管理堆一样管理方法区。 但BEA, JRockit, IBM J9等来说, 是不存在永久代概念的。 到jdk8时, 把字符串常量池,静态变量, 类型信息等全部移到元空间,采用本地内存实现的元空间
a. 对象内存的分布: 当遇到一个new指令时, 首先会去检查能否在常量池中定位到符号引用,并检查这个符号引用是否被类加载
b. 为新生对象分配内存, 有俩种方式。
【指针碰撞】:保证内存绝对规整,实用过的和空闲的分离开,中间放一个指针作为分界点,为新对象分配内存只需要指针往空闲区挪动对象大小的距离。
【空闲列表】: 如果内存不规整, 则需要维护一个列表,记录那些可用!
那么java堆是否规整是根据垃圾回收算法来决定的
c. 考虑如何保证并发情况下线程安全?
当每个线程初始化的时候, 都为其开辟一块空间【TLAB】,使其分配内存私有, 但是访问还是共享的。而对象的实例阶段也是可用在TLAB区被赋值为0,即可用不用赋初始值,编写代码时!
之后设置对象头信息。
d. 新对象创建出来后, 接着执行
a. 对象头, 分为俩类信息
一类,存储对象自身的运行时数据,hashCode, GC age, 锁状态,线程持有锁,偏向锁ID
二类, 类型指针,通过这个指针确定是那个类的实例, 如果是Java数组,还需要记录数组的长度
b. 实例数据部分: 是真正存储的有效信息,即我们程序中定义的各种类型字段内容
c. 对其填充, 就是占位符的作用
在Java中通过栈上的 reference 来操作堆上的具体对象,由于reference类型在虚拟机规范中只规定了指向对象的引用, 并没有定义以什么方式区定位堆中的具体位置。 所以对象访问是由虚拟机实现的。常见的访问方式:【句柄】,【直接指针】
句柄: 会在堆中开辟一块空间作为句柄池,reference存储的是句柄从地址,而句柄池的地址才真正指向具体的数据。优点:对象被移动【垃圾收集导致】不需要改变对象本身
直接指针访问: 存储的直接就是对象地址, 优点:访问速度块
根据以下假说建立:
a. 弱分代假说【朝生夕灭】
b. 强分代假说【老不死】之上
据以上假说奠定了垃圾收集器的统一原则:
1. 收集器应该把Java堆划分出不同的区域;
2. 根据年龄分配到不同的区域
3. 如果大多数对象都是朝生夕灭,即放到一起,只关注存活对象
4. 划分不同的区域后,有了【Minor GC】【Major GC】【Full GC】回收类型
5. 根据不同区域,以及存储对象存亡特征,匹配不同的垃圾收集算法
注意: 由于对象不是孤立的, 对象之间可能存在跨代引用, 即新生代对象可能存在被老年代引用,则需要在固定的GC Roots之外,额外遍历老年代对象图,确保可达性分析算法的准确性。无疑增加了很大的负担。 所以有了第三个假说
c. 跨代假说:
根据这条假说, 我们不应再为少量的跨代引用去扫描整个老年代, 也不要浪费空间专门记录每个对象是否存在那些跨代
我们只需要再新生代建立一个全局的数据结构(记忆集), 这个结构把老年代分为若干隔小块,标识出老年代的那一块内存会存在跨代引用。Minor GC时只会扫描存在跨代引用的内存块
7. 可达性分析算法流程
基本思路通过一系列的 GC Roots 的根对象作为起始节点集, 根据引用关系, 向下收缩, 走过的路径称为引用链, 如果某个对象到GC ROOTS间没用任何引用链, 或者用图论来说就是这个对象不可达, 那么证明此对象不能再被使用。
可达性分析算法理论上要求全过程必须都基于能保障一致性的快照才能够分析, , 这意味着必须冻结用户线程的运行。 在根节点枚举上,由于GC Roots相比整个堆,还是算比较少, 在加上OopMap的加持,它带来了相对短暂并且不会随堆的增大而增大, 即稳定的。但是遍历对象图, 则是很困难的,和堆的容量成正比关系。
【标记】阶段是所有追踪式垃圾收集算法的共同特征, 这个阶段会随着堆变大而增加停顿时间。 如果能够消减, 将是系统性的提升。
首先理解为什么对象图需要保证快照一致性下, 才能遍历对象图?
采用三色标记作为辅助工具, 把遍历过的对象按照【是否被访问】的条件标记成三种颜色,
白色:尚未访问过。分析结束都还是白色,即被回收
黑色:本对象已访问过,而且本对象 引用到 的其他对象 也全部访问过了。
灰色:本对象已访问过,但是本对象 引用到 的其他对象尚未全部访问完。全部访问后,会转换为黑色。
当用户线程与收集器并发工作时, 同时修改了对象图将会导致俩种后果:
1. 把原本消亡的对象标记存活, 就是本来黑色A ==> 灰色B, 但是这时又执行了黑色A ==> null
这个时候灰色B应该失去了引用, 但是它已经是灰色了, 所有本轮GC不会回收灰色B, 这被称为【浮动垃圾】
2. 漏标:黑色A ==> 灰色E ===> 白色G , 如果灰色E==白色G被断开, 这时黑色A ==> 白色G。 而白色G失去了灰色E的引用不会变成灰色|黑色,造成本应该是黑色的对象被错标未白色, 在垃圾回收时被清理, 这个时候问题就很严重, 因为本应该是A在引用我, 出现了对象丢失问题。
赋值器插入一条或多条黑色对象到白色对象的引用
赋值器删除了全部灰色对象到该白色对象的直接或间接引用
因此, 我们解决并发扫描问题时对象消失问题, 只需要破坏这个俩个条件的任意一个即可
1. 【增量更新】
当黑对象新指向白色对象引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中黑色对象为更, 再一次扫描即可。 即一旦黑色指向白色, 白色将变成灰色
2. 【原始快照】
灰色对象删除引用白色对象的关系时, 就将这个删除引用记录下来, 即并发扫描完后, 在将这些记录关系灰色对象为根, 重新扫描一次。 无论引用关系删除与否,都会按照刚才开始扫描的那一刻的对象图进行搜索。
无论引用关系记录还是删除, 虚拟机记录操作都是通过写屏障实现的, 在HotSpot虚拟机中,增量更新为CMS基于增量更新来做并发标记 原始快照: G1 Shenandoah