垃圾回收机制

什么是垃圾?

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

为什么需要GC?

内存迟早都会被消耗完

JVM将整理出的内存分配给新的对象

没有GC就不能保证应用程序的正常进行

优点:自动内存管理,无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和内存溢出的风险,可以更专心地专注于业务开发

        没有垃圾回收器,java也会和cpp一样,各种悬垂指针,野指针,泄露问题让你头疼不已。

缺点:弱化Java开发人员在程序出现内存溢出时定位问题和解决问题的能力

GC主要关注于 方法区 和堆中的垃圾收集

频繁收集Young区

较少收集Old区

基本不收集Perm区(元空间)

垃圾回收相关算法

标记阶段

1. 引用计数算法

引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。对象被引用,引用计数器+1,相反-1,当值为0时,对象可被回收

优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。

缺点:

  • 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
  • 每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
  • 引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。

当p的指针断开的时候,内部的引用形成一个循环,这就是循环引用

Java并没有选择引用计数,是因为其存在一个基本的难题,也就是很难处理循环引用关系。

Python如何解决循环引用?

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

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

搜索被根对象集合所连接的目标是否可达,不可达,则为垃圾对象

GC Roots包括以下几类元素:

  1. 虚拟机栈中引用的对象   比如:各个线程被调用的方法中使用到的参数、局部变量等。
  2. 本地方法栈内引用的对象
  3. 方法区中类静态属性引用的对象
  4. 方法区中常量引用的对象    比如:字符串常量池(String Table)里的引用
  5. 所有被同步锁synchronized持有的对象

清除阶段:JVM中比较常见的三种垃圾收集算法

1. 标记一清除算法(Mark-Sweep)

标记:从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。清除没有标记的对象。

缺点

  • 效率不高
  • 在进行GC的时候,需要停止整个应用程序,用户体验较差
  • 这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表

何为清除?

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

2. 复制(Copying)算法

将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,清除那些未存活的对象。

优点

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

缺点

  • 就是需要两倍的内存空间。
  • 对象的地址发生改变,栈中对象的引用也会发生改变
  • 适用于对象存活数量不太多的情况下(比如年轻代),否则复制成本高

3. 标记-压缩(Mark-Compact)算法

老年代

标记一清除算法的确可以应用在老年代中,但是该算法执行效率低下,执行GC后还会产生内存碎片,所以JVM的设计者需要在此基础之上进行改进。标记-压缩(Mark-Compact)算法由此诞生。

执行过程

  1. 第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象
  2. 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。
  3. 之后,清理边界外所有的空间。

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理。二者的本质差异在于标记-清除算法是一种非移动式的回收算法,标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。

指针碰撞(Bump the Pointer)

如果内存空间以规整和有序的方式分布,即已用和未用的内存都各自一边,彼此之间维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,只需要通过修改指针的偏移量将新对象分配在第一个空闲内存位置上,这种分配方式就叫做指针碰撞(Bump tHe Pointer)。

优点

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

缺点

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

分代收集算法

前面所有这些算法中,并没有一种算法可以完全替代其他算法,它们都具有自己独特的优势和特点。分代收集算法应运而生。不同生命周期的对象可以采取不同的收集方式

目前几乎所有的GC都采用分代手机算法执行垃圾回收的

年轻代(Young Gen)

复制算法

年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。

老年代(Tenured Gen)

由标记-清除或者是标记-清除与标记-整理的混合实现。

老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。

分代的思想被现有的虚拟机广泛使用。几乎所有的垃圾回收器都区分新生代和老年代

增量收集算法

让垃圾收集线程和应用程序线程交替执行,解决了一次性将所有垃圾进行处理而造成系统长时间停顿的问题

缺点:线程切换和上下文转换是的垃圾回收成本上升,系统吞吐量下降

分区算法

将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值