JVM学习笔记——垃圾回收

什么是垃圾?

在程序运行中没有任何指针指向的对象,这个对象就是要被回收的垃圾

垃圾回收算法

标记阶段

在垃圾回收之前,首先要区分出内存中那些事存活对象,那些是死亡对象。只有标记为已经死亡的对象gc才会进行垃圾回收,释放其所占用的空间,因此这个过程我们称为垃圾标记阶段

引用计数算法

对每个对象保存一个整形的引用计数器属性。用于记录对象被引用的情况

对于一个对象A,只要任何一个对象引用了A,A的引用计数器就加一;当引用失效时,引用计数器就减一。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收

优点:实现简单,垃圾对象便于识别,判定效率高,回收没有延迟性
缺点:
它需要单独的字段存储计数器,增加空间开销
每次赋值都要更新计数器,增加时间开销
无法处理循环引用情况,这是致命的
导致java在垃圾回收器中没有使用这类算法

那么有没有地方使用引用计数算法,它是如何解决这个算法带来的问题呢?

Python
手动解除:在合适时机解除引用关系
使用弱引用weakref,weakref是python提供的标准库,旨在解决循环引用

可达性分析算法(根搜索算法、追踪性垃圾收集)

相对于引用计数算法,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效的解决引用计数算法在循环引用中的问题,防止内存泄漏。这种垃圾收集算法通常也叫做追踪性垃圾收集

思路
  • 可达性分析算法是以根对象集合为起始点,按照从上至下的方法搜索被跟对象集合所连接的目标是否可达
  • 使用可达性分析算法后,内存中的存活对象都被根对象集合直接或间接连接着,搜索所走过的路径称为引用链
  • 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象
  • 在可达性分析算法中,只有能够被根对象集合直接或间接连接的对象才是活对象
GC Roots包括以下几类元素
  • 虚拟机栈中引用的对象
  • 本地方法栈中引用的对象
  • 方法区静态属性引用的对象
  • 方法区中常量引用的对象
  • 被synchronized锁持有的对象
  • 虚拟机内部的引用(基本数据类型对于的Class、常驻异常对象)
  • 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

除了以上固定的GC Roots集合外,根据用户选的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”加入。比如:分代收集和局部回收(Partial GC)
如果只针对java堆中某一块区域进行垃圾回收(比如:典型的只针对新生代),必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的,这个区域对象完全有可能被其他区域的对象引用,这时候就需要一并将关联的区域对象也加入GC Roots集合中去考虑,才能保证可达性分析的准确性
小技巧:
由于Root采用栈方式存放变量和指针,所以如果一个指针,他保存了堆内存里面的对象,但是自己有不存还在堆内存里面,它就是一个Root
注意:
如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行,这点不满足的话分析结果的准确性就无法保证
这点也是导致GC进行时必须“Stop The World”的一个重要原因。即使是号称不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的

对象的finalization机制

java语言提供了对象种植(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑

当垃圾回收器发现没有引用指向一个对象,既:垃圾回收此对象之前,总会先调用这个对象的finalize()方法

finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放。通常这个方法进行一些资源释放和清理的工作,比如关闭文件、套接字、和数据库连接等。

永远不要主动调用对象finalize()方法,应该交由垃圾回收机制调用:

  • finalize()时可能导致对象复活
  • finalize()方法执行时间没有保证,它完全由GC线程决定,极端情况下,若不发生GC,则finalize()方法没有执行机会
  • 一个糟糕的finalize()会严重影响GC性能
    因为finalize()方法的存在,对象会有三种状态:
  • 可触及的:从根节点开始,可以到达这个对象
  • 客服或的:对象的所有引用都被释放,但是对象由可能在finalize()中复活
  • 不可触及的:对象finalize()被调用,并且没有复活,纳闷呢就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize()只会被调用一次

清除阶段

标记清除算法(Mark-Sweep)

执行过程

当堆中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作:标记和清除
标记:从引用根节点开始遍历,标记所有被引用的对象。一般是在对象Header中记录为可达对象(做标记的对象是可以达到,无需收集的对象)
清除:对堆内存从头到尾进行线性遍历,如果发现某个对象在其Header中没有标记为可达对象会对它进行回收

缺点

效率不高
在进行GC时需要停止整个程序
清理出来的空间不是连续的,产生内存碎片,需要维护一个空闲列表

何为清除

这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表,下次由新对象需要加载时,判断垃圾的位置空间是否够,如果够就存放

复制算法(Copying)

思想

活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中所有对象,交换两块内存角色,完成垃圾回收

优点

没有标记和清楚过程,实现简单,运行高效
复制过去以后保证空间的连续性,不会出现碎片问题

缺点

需要两倍的内存空间
对于g1这种分拆称为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象的引用关系,不管是内存占用或者时间开销也不小

特别的

如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量不大

标记压缩(mark-Compact)算法

优点

消除了标记-清除算法中,内存区域分散的缺电,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
消除了复制算法中内存减半的高额代价

缺点

效率上说,标记-整理算法要低于复制算法
移动对象同事,如果对象被其他对象引用,则还需要调整引用的地址
移动过程中,需要全部暂停用户应用程序。既:SYW

增量收集算法

思想

如果一次性将所有的垃圾进行处理,需要早晨系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。一次反复直到垃圾收集完成
总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理和复制操作

缺点

使用这种方式,由于在垃圾回收过程中,间断性的执行力应用程序代码,所以减少系统停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的成本上升,造成系统吞吐量下降

分区算法

思想

分代算法将按照对象的生命周期长短划分为两个部分,分区算法将整个堆空间划分为连续的不同小区间
每个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收回收多少个小空间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值