垃圾回收GC
1.java对象创建过程
创建步骤
- step1 :类加载检查
- step2:分配内存
内存分配并发问题(补充内容,需要掌握)
- step3:初始化零值
- step4:设置对象头
- step5:执行init方法
2.对象的访问定位有哪两种方式?
总结:
这两种对象访问⽅式各有优势。使⽤句柄来访问的最⼤好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,⽽ reference 本身不需要修改。使⽤直接指针访问⽅式最⼤的好处就是速度快,它节省了⼀次指针定位的时间开销。
3.JVM内存分配与回收
1)堆空间的基本结构
修正:
- 动态年龄计算的代码如下:
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//survivor_capacitysurvivor
size_t desired_survivor_size = (size_t)((((double)
survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
while (age < table_size) {
total += sizes[age];//sizes
if (total > desired_survivor_size) break;
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
...
}
2)堆内存中对象的分配基本策略
1> 对象优先在eden区分配
测试
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2;
allocation1 = new byte[30900*1024];
//allocation2 = new byte[900*1024];
}
}
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
allocation1 = new byte[32000*1024];
allocation2 = new byte[1000*1024];
allocation3 = new byte[1000*1024];
allocation4 = new byte[1000*1024];
allocation5 = new byte[1000*1024];
}
}
2> 大对象直接进入老年代
3> 长期存活的对象将进入老年代
4> 动态对象年龄判定
5> 主要进行GC的区域
- List item
总结:
6> 判定对象是否死亡的两种方法
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
- 引用计数法:
- 可达性分析算法:
7> 强引用、软引用、弱引用、虚引用
- 强引用(Strong Reference)
- 软引用 (Soft Reference)
- 弱引用 (Weak Reference)
- 虚引用 (Phantom Reference)
3)判断一个常量是废弃常量的策略
4)判断一个类是无用的类的策略
5)垃圾收集有哪些算法,各自的特点?
1> 标记-清除算法
2> 复制算法
3> 标记-整理算法
4> 分代收集算法
6)HotSpot为什么要分为新生代和老年代?
主要为了提升GC效率。上面提到的分代收集算法已经很好的解释了这个问题。
7)常见的垃圾回收器有哪些?
1> Serial 收集器
2> ParNew 收集器
3> Parallel Scavenge 收集器
4> Serial Old 收集器
5> Parallel Old 收集器
6> CMS 收集器
7> G1 收集器
8> ZGC收集器![ZGC](https://i-blog.csdnimg.cn/blog_migrate/828eafdde64c1e9ae00d5805d854c418.png)
ZGC只有三个STW阶段:初始标记,再标记,初始转移。其中,初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。即,ZGC几乎所有暂停都只依赖于GC Roots集合大小,停顿时间不会随着堆的大小或者活跃对象的大小而增加。与ZGC对比,G1的转移阶段完全STW的,且停顿时间随存活对象的大小增加而增加。
- ZGC关键技术:
ZGC通过着色指针和读屏障技术,解决了转移过程中准确访问对象的问题,实现了并发转移。大致原理描述如下:并发转移中“并发”意味着GC线程在转移对象的过程中,应用线程也在不停地访问对象。假设对象发生转移,但对象地址未及时更新,那么应用线程可能访问到旧地址,从而造成错误。而在ZGC中,应用线程访问对象将触发“读屏障”,如果发现对象被移动了,那么“读屏障”会把读出来的指针更新到对象的新地址上,这样应用线程始终访问的都是对象的新地址。那么,JVM是如何判断对象被移动过呢?就是利用对象引用的地址,即着色指针。下面介绍着色指针和读屏障技术细节。
着色指针:着色指针是一种将信息存储在指针中的技术。
ZGC仅支持64位系统,它把64位虚拟地址空间划分为多个子空间,如下图所示:
- 其中,[0~4TB) 对应Java堆,[4TB ~ 8TB) 称为M0地址空间,[8TB ~ 12TB) 称为M1地址空间,[12TB ~ 16TB) 预留未使用,[16TB ~ 20TB) 称为Remapped空间。
- 当应用程序创建对象时,首先在堆空间申请一个虚拟地址,但该虚拟地址并不会映射到真正的物理地址。ZGC同时会为该对象在M0、M1和Remapped地址空间分别申请一个虚拟地址,且这三个虚拟地址对应同一个物理地址,但这三个空间在同一时间有且只有一个空间有效。ZGC之所以设置三个虚拟地址空间,是因为它使用“空间换时间”思想,去降低GC停顿时间。“空间换时间”中的空间是虚拟空间,而不是真正的物理空间。后续章节将详细介绍这三个空间的切换过程。
- 与上述地址空间划分相对应,ZGC实际仅使用64位地址空间的第041位,而第4245位存储元数据,第47~63位固定为0。
ZGC将对象存活信息存储在42~45位中,这与传统的垃圾回收并将对象存活信息放在对象头中完全不同。
读屏障:读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。需要注意的是,仅“从堆中读取对象引用”才会触发这段代码。
Object o = obj.FieldA // 从堆中读取引用,需要加入屏障
<Load barrier>
Object p = o // 无需加入屏障,因为不是从堆中读取引用
o.dosomething() // 无需加入屏障,因为不是从堆中读取引用
int i = obj.FieldB //无需加入屏障,因为不是对象引用
ZGC中读屏障的代码作用:在对象标记和转移过程中,用于确定对象的引用地址是否满足条件,并作出相应动作。