引入:
内存泄露:服务器,追求的是7*24小时运行,不能随便重启 如果申请的内存,没有手动释放,后面继续申请的话,就会使剩下的内存越来越少,最终内存没了,后续申请就会失效=>进程就会出现严重Bug了,甚至崩溃
作用:
自动的判定某个内存是否会继续使用(如果不会,就会把这个内存当成“垃圾”),就会自动的把垃圾给释放掉
对于Java来说,垃圾回收,回收的其实是“对象“(GC并非是判定某几个字节是不是垃圾,而是判定对象是不是垃圾,进一步进行垃圾回收),而不是“字节””
JVM中有好几个内存区域,GC回收的是哪里的对象?
①栈空间不需要GC回收,栈里面包含很多“栈帧”,每个栈帧对应一个方法,该方法执行结束,此时这个栈帧就销毁了,栈帧上的局部变量啥的自然销毁
②程序计数器同理,线程销毁,自然跟着销毁
③方法区,类对象,很少会涉及到内存的卸载
④堆,是GC的主战场
垃圾回收,分为两步:
1、判定对象是否是“垃圾”
答:如果一个对象,在后续代码中,不会继续使用到了(一个对象,没有任何应用指向他了,就可以认为是垃圾),就可以视为垃圾了
2、释放对象的内存
思路一:引用计数(并非是 JVM 引用的方案)
给这个对象里面安排一个计数器,每次有引用指向他,就把计数器+1,每次引用销毁,就把如果计数器为 0,就说明这个对象是个垃圾

class Test{ int m;
int n }
Test a=new Test();
Test b=new Test();
a=null;
b=null;
c=null;
此时引用计数的值是0,该对象就是垃圾了
此方案的缺陷:
1.空间利用率低,浪费更多的内存空间(如果给引用计数器分配 2 个字节,对象本体只有 4 个字节,就浪费了 50% 的内存空间;如果代码都是这种小对象,而且数量很多就会浪费很多)
2.可能存在循环应用问题,导致对象不能被识别为垃圾

class Test{ public Test t; }
Test a=new Test();
Test b=new Test();
a.t=b;
b.t=a;
a=null;
b=null;
此时,这俩的引用计数不为0,不能被当做垃圾;与此同时,你想要使用对象 1,就得先访问到对象2;要想使用对象2,就得要访问到对象1,;这俩对象谁都用不了,谁都不能释放
方案二:可达性分析(java 中实际采取的方案)
JVM 首先会从现有的代码中的能直接访问到引用(1、栈上的局部变量 2、常量池的引用 3、方法区的静态成员)的出发,尝试比遍历所有能访问的对象,只要对象能访问到,就会标记成“可达”
完整遍历后,可达之外的对象就是“不可达”,就是相当于垃圾了

此时,这个树上的所有节点都是“可达的”,也就都不是垃圾
root.right.right=null
此时,f就无法被访问到了,通过root遍历,f就是不可达了
root.right=null通过root,连c也访问不到了,f也就访问不到了
Node root=a;把a,b,c,d,e,f,g都设为空;,虽然进行了一系列的置空操作,但是由于root引用存在,可以直接访问到a,通过a就可以进一步访问到后续所有节点
上述遍历的过程就是“可达性分析”:和引用计数不同,引用计数,消耗的是空间,通过应用计数的数值,非常快速的知道这个对象是否是垃圾;可达性分析,消耗的是时间,并不会引入额外的空间开销,需要消耗时间
注:①代码执行过程中,一个对象是否是垃圾,往往是“动态变化”的 ②上述可达性分析的描述,是持续的,周期性的(每个一段时间,都得扫描一次)
如何清理垃圾,释放对象?
1、标记-清除(直接释放)

直接释放对象,就可能引起“内存碎片”
申请内存的时候,都是申请的连续的内存空间
释放内存,就可能会破坏原有的连续性,导致:“有内存但申请不了”
这种问题不处理的话,内存碎片就会越来越多,越来越碎,内存就更难申请了
2、复制算法
复制算法,通过冗余的内存空间,把有效对象复制到另一部分空间,来避免内存碎片

把一个内存分成两份,用一份,丢一份
在左侧区域中,有效对象复制到右侧区域,接下来就可以使右侧区域了用了一段时间之后,也会有很多对象,也是同理,把有效对象复制到左侧,把右侧区域统一释放
如果复制的内容少还好,如果复制的内容多,开销就会很大,内存利用率也不高(浪费了一半)
3、标记整理
顺序表删除元素、搬运

这样的方式,避免了刚才复制算法内存利用率低的问题,但是,很明显,这里的搬运元素成本也是比较高的
4、Java代码中,对象也就分成大体两类:
1)生命周期特别短的
2)生命周期特别特别长的
3)不长不短的(出现概率更低)
按照对象的年龄(GC是周期性进行扫描,一个对象每经过一个GC,就称“长了一岁”),来制定不同的回收策略

如果一个对象在幸存区,经过了好多轮都还没挂,仍然坚挺,仍然没挂,说明这个对象应该就是“生命周期特别长”的了,这个时候就来到了老年代
新生代,扫描频次是比较高的
老年代,扫描频次就降低了
新生代,每一轮GC留下的有效对象不多,复制算法的开销不大
老年代,不太会有对象真销毁,此时标记整理的开销也不大
176万+

被折叠的 条评论
为什么被折叠?



