这篇文章主要讲解了“如何理解Java jvm垃圾回收”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何理解Java jvm垃圾回收”吧!
常见面试题
如何判断对象是否死亡
简单介绍一下强引用、软引用、弱引用、虚引用
如何判断常量是一个废弃常量
如何判断类是一个无用类
垃圾收集有哪些算法、各自的特点?
常见的垃圾回收器有哪些?
介绍一下CMS,G1收集器?
minor gc和full gc有什么不同呢?

1.JVM内存回收和分配
1.1主要的区域?
在伊甸区先产生对象
然后发生一次gc之后去到幸存区幸存区
如果年龄大于阈值那么就会升级到老年代
阈值的计算
如果某个年龄段的大小大于幸存区的一半,那么就取阈值或者是这个年龄最小的那个作为新的阈值升级到老年代
gc测试
场景就是先给eden分配足量的空间,然后再申请大量空间,问题就是幸存区的空间不够用
public class GCTest {
public static void main(String[] args) {
byte[] allocation1, allocation2;
allocation1 = new byte[50900*1024];
allocation2 = new byte[9500*1024];
}
}

1.2大对象进入老年代
1.3长期存活的对象进入老年代
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//survivor_capacity是survivor空间的大小
size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
while (age < table_size) {
//sizes数组是每个年龄段对象大小
total += sizes[age];
if (total > desired_survivor_size) {
break;
}
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
...
}
1.4主要进行gc的区域
gc的类型
Young Gc:收集新生代的
Old Gc:只收集老年代的
Mixed Gc:新生代和部分老年代
Young Gc
Full Gc
1.5空间分配担保?
2.对象已经死亡?

2.1引用计数法
2.2可达性分析
gc Roots的对象

2.3再谈引用
强引用:垃圾回收器不会对他进行回收
软引用:内存空间不足会回收
弱引用:gc就回收
虚引用:随时会被回收而且需要引用队列
虚引用、软引用、弱引用的区别?
2.4不可达对象不一定回收
2.5如何判断一个常量是废弃常量?
2.6如果判断一个类没有用?
3.垃圾回收算法
hotspot为什么要区分老年代和新生代?
原因就是不同的存活对象需要不同的垃圾回收算法
跨代收集假说?
如果老年代和新生代互相引用,新生代的年龄就会被拉长。但是为了知道新生代什么时候被gc,这个时候可以给新生代加上一个记忆集(把老年代划分为很多个格子,代表谁引用了我),避免扫描整个老年代
4.垃圾回收器
4.1Serial收集器
4.2ParNew收集器
Serial的多线程版本,但是还是会STW
新生代是标记复制,老年代是标记整理
4.3Parallel Scavenge收集器
4.4SerialOld
4.5Parallel Old收集器
4.6CMS收集器
注重最小响应时间
垃圾收集器和用户线程同时工作
初始标记记录gc root直接相连的对象
并发标记遍历整个链,但是可以和用户线程并发运行
重新标记修正那些更新的对象的引用链,比并发标记短
并发清除
问题?
内存碎片多对cpu资源敏感

4.7G1收集器
同时满足响应快处理多的问题
特点
并行和并发,使用多个cpu执行gc线程来缩短stw,而且还能与java线程并发执行
分代收集
空间整合:大部分时候使用标记复制
可预测停顿:响应时间快,可以设置stw时间
分区之间的跨代引用,young这里使用了rset(非收集区指向收集区)记录,老年代那个区域指向了我,老年代使用了卡表划分了很多个区域,那么minor gc的时候就不需要遍历整个其它所有区域去看看当前的区域的对象到底有没有被引用。

补充字符串池的本质
第一个问题是String a="a"的时候做了什么?
第二个问题new String(“a”)发生了什么?
第三个问题intern的原理?
String s1=new String(“a”)
String s2=s1.intern();
很明显s1不等于s2如果上面的问题都清晰知道。s1引用的是堆,而s2引用的是常量池的
第四个问题
String s3=new String(“1”)+new String(“1”);
String s5=s3.intern();
String s4=“11”
那么地方他们相等吗?当然是相等的,s3会把1存入常量池,但是不会吧11存入常量池因为,还没编译出来。调用了intern之后才会把对象存入常量池,而这个时候存入的对象就是s3指向的那个。所以s4指向的也是s3的。如果是s0="11"的话那就不一样了,s3.intern只会返回常量池的对象引用地址,而不是s3的,因为s3是不能重复intern 11进去的。jdk1.6的话那么无论怎么样都是错的,intern是复制一份,而不是把对象存入常量池(因为字符串常量池在方法区,而jdk1.7它在堆所以可以很好的保存s3的引用)
下面的代码正确分析应该是三个true,但是在test里面就会先缓存了11导致false, true,false的问题。
@Test
public void test4(){
String s3 = new String("1") + new String("1");
String s5 = s3.intern();
String s4 = "11";
System.out.println(s5 == s3);
System.out.println(s5 == s4);
System.out.println(s3 == s4);
System.out.println("======================");
String s6 = new String("go") +new String("od");
String s7 = s6.intern();
String s8 = "good";
System.out.println(s6 == s7);
System.out.println(s7 == s8);
System.out.println(s6 == s8);
}
finalize的原理
其实就是对象重写了finalize,那么第一次gc的时候如果发现有finalize,就会把对象带到F-Queue上面等待,执行finalize方法进行自救,下面就是一个自救过程,new了一个GCTest对象,这个时候test不引用了,那么正常来说这个GCTest就会被回收,但是它触发了finalize的方法,最后再次在finalize中使用test引用它所以对象没有被消除
但是finalize是一个守护线程,防止有的finalize是个循环等待方法阻塞整个队列,影响回收效率
最后一次标记就是在F-queue里面标记这个对象(如果没有引用)然后释放
finalize实际上是放到了Finalizer线程上实现。然后然引用队列指向这个双向链表,一旦遇到gc,那么就会调用ReferenceHandler来处理这些节点的finalize调用,调用之后断开节点,节点就会被回收了
finalize上锁导致执行很慢
public class GCTest {
static GCTest test;
public void isAlive(){
System.out.println("我还活着");
}
@Override
protected void finalize() throws Throwable {
System.out.println("我要死了");
test=this;
}
public static void main(String[] args) throws InterruptedException {
test = new GCTest();
test=null;
System.gc();
Thread.sleep(500);
if(test!=null){
test.isAlive();
}else{
System.out.println("死了");
}
test=null;
System.gc();
if(test!=null){
test.isAlive();
}else{
System.out.println("死了");
}
}
}
感谢各位的阅读,以上就是“如何理解Java jvm垃圾回收”的内容了,经过本文的学习后,相信大家对如何理解Java jvm垃圾回收这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是天达云,小编将为大家推送更多相关知识点的文章,欢迎关注!