垃圾回收

 引入:

内存泄露:服务器,追求的是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留下的有效对象不多,复制算法的开销不大

老年代,不太会有对象真销毁,此时标记整理的开销也不大

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值