jvm的垃圾回收

垃圾回收回收的是什么

  • 对象:堆中的对象
    类的元数据回收:类的元数据可以被垃圾回收,但前提是对应的类被卸载。
  • 元空间(Metaspace):在JDK 8及之后,类的元数据存储在元空间中,这个区域使用本地内存并且可以动态增长。
    类卸载条件:要回收类的元数据,必须确保该类不再被使用,并且加载该类的类加载器可以被回收。
    配置选项:可以通过JVM选项来调整元空间的大小和类卸载行为,以优化内存使用。

文件句柄、网络连接、数据库连接等外部资源不会由Java垃圾回收器直接回收。这些资源通常需要通过显式的关闭或释放操作来管理,比如对象的 finalize() 方法被调用时。

判断对象已死

程序计数器,虚拟机栈,本地方法区,随一个线程生,随一个线程死,在编译时就确定了空间大小,不需要判断对象状态。
堆和方法区却拥有很大的不确定性,需要判断其中的对象是否已死。

引用计数法:缺点明显

给每个对象一个引用数,被引用一次+1,取消引用-1。
缺点:当两个对象互相引用时,无法判断已死。

可达性分析算法:主流使用(java,C#)

GC ROOT 对象及其引用的对象被认为是“活动”的,即它们是程序中正在使用的对象,不会被垃圾回收器回收。因此,垃圾回收器会从 GC ROOT 对象出发,沿着对象之间的引用链,标记所有可达的对象,然后清除未被标记的对象,以释放内存空间。

GC ROOT是一定存活的对象
GC ROOT有哪些:
  • 虚拟机栈中的引用

  • 本地方法栈中的引用

  • 方法区中的类静态属性引用的对象: 类的静态属性保存在方法区中,如果一个对象被类的静态属性引用所引用,那么这个对象也是 GC ROOT。

  • 方法区中的常量引用的对象: 常量池中的字符串常量、类的静态常量等,如果一个对象被方法区中的常量引用所引用,那么这个对象也是 GC ROOT。

  • JNI(Java Native Interface)中的引用: 本地代码中也可能会引用 Java 对象。如果一个对象被 JNI 中的引用所引用,那么这个对象也是 GC ROOT。

  • synchronized持有的对象。

再谈引用

JDK1.2后对引用进行了扩充,

强引用(Strong Reference): 强引用是最常见的引用类型,它是指通过 new 关键字创建的对象所持有的引用。只要存在强引用指向一个对象,这个对象就不会被垃圾回收器回收,即使内存不足时也不会回收这些对象。当没有强引用指向一个对象时,这个对象就可以被垃圾回收器回收。

软引用(Soft Reference): 软引用是一种比较弱的引用类型,用于描述一些还有用但非必需的对象。当内存不足时,垃圾回收器会回收软引用所引用的对象。软引用通常用于实现缓存等功能,可以避免内存溢出的情况发生。

弱引用(Weak Reference): 弱引用比软引用更弱,它只能保持对一个对象的非必需引用。当对象只被弱引用所引用时,即使内存充足,垃圾回收器也会回收这个对象。弱引用通常用于实现一些特定的功能,比如监视对象的生命周期等。

虚引用(Phantom Reference): 虚引用是一种比较特殊的引用类型,它通常不被用于获取对象的实例,而是用于跟踪对象被垃圾回收器回收的状态。虚引用是一种最弱的引用类型,它不能单独使用,必须和引用队列(ReferenceQueue)一起使用。当对象被回收时,虚引用会被添加到引用队列中,通知程序对象的回收情况。

非死不可与自我拯救

当可达性算法分析一个对象该死亡时,虚拟机会判断这个对象是否调用过了finalize()方法,如果调用过,则判定为没有必要执行垃圾回收。
如果判断为有必要执行finalize()方法,那么这个对象会被放置在一个F-Queue队列中,并且在稍后由一个虚拟机自己建立的,低调度优先级的线程去执行其finalize()方法。
对象可以在finalize()方法中拯救自己,如果此时它和GC ROOT建立了联系,则拯救了自己。

回收方法区的垃圾

在《java虚拟机规范》中提到,方法区可以没有垃圾回收,因为方法区的垃圾回收性价比很低,只有很少的内存可以被回收,确实有些虚拟机也没有实现方法区的垃圾回收。
方法区回收的主要是:废弃的常量和不再使用的类的元数据。

垃圾回收算法

垃圾回收算法主要是:引用式和追踪式,主流的垃圾回收算法使用的都是追踪式,我们讨论的也是追踪式的。

当前的商业虚拟机的垃圾收集器大多数都遵循了“分代收集”的理论

在虚拟机中,设计者一般都会至少把java堆划分为新生代和老年代。

对象的跨代引用只占极少数

如果存在跨代引用,因为老年代的对象的存在,新生代的对象不会被回收,但是随着逃过的回收次数的增多,新生代的对象会进入老年代,此时跨代引用就消失了。

新生代跨代引用的解决方式

记忆集:它记录了从老年代(Old Generation)指向新生代(Young Generation)的引用。
将老年代划分为了不同的小区域(卡片)。
在新生代建立了一个记忆集,这个结构把老年代划分成若干个小块,标识出来哪块老年代的内存存在跨代引用,此时在新生代发生了垃圾回收时,只需要将一小块的老年代的对象加入到gc ROOT进行扫描。
具体的过程如下:
在对象引用发生变化时,如果一个老年代对象引用了一个新生代对象,JVM 会将这个信息记录在记忆集中。
当新生代发生垃圾回收时,GC 只需要扫描记忆集中标记的老年代的小块(卡片),将这些小块中的对象作为 GC Root 进行扫描,从而追踪到新生代的存活对象。

老年代的跨代引用和新生代的相似都是使用了记忆集和卡片分区,加入ROOT

标记-复制算法

半区复制:使得堆内存减半。

Appel式回收:优化的半区复制(针对朝生暮死的机制)

HotSpot虚拟机的很多新生代收集器(Serial,ParNew)均采用了这种策略来设计新生代的内存布局。
将新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和一块Servivor,当发生垃圾回收时,将Eden和Survivor中仍然存活的对象一次性复制到另一个Survivor中,然后清理Eden和Servivor。
如果另一个Survivor的空间不够时,这些对象会通过分配担保机制直接加入老年代。

标记-清除算法

用ROOT遍历标记需要清除的对象,然后清除。
缺点:StopTheWorld耗时较短,处理完后的内存不连续,分配内存更麻烦。

标记-整理算法:Parallel Old收集器(更关注吞吐量)

先用ROOT标记,然后整理堆内存,将存活的对象都向堆内存的一端移动,然后直接清理掉边界以外的内存。
缺点:需要StopTheWorld耗时较长。
优点:分配内存不需要太难。

标记-整理算法和标记-清楚算法的结合:CMS收集器

虚拟机大多数时间都是用标记-清除算法,暂时容忍内存碎片的存在,知道难以忍受后使用标记-整理算法。

根节点枚举:必须要STW,所有垃圾回收器都需要停顿来根节点枚举

安全点

所有线程都停下来,然后进行垃圾回收等工作。

安全区域

在安全区域中,对象的引用关系不会发生变化,因此,在这段区域内垃圾回收都是安全的。

记忆集与卡表

为什么要用卡表来记录一片区域中有跨代引用的对象?
一个对象一个对象记录会花费大量的性能,使用卡表性能更高。
在垃圾回收时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代引用,将他们加入到ROOTS中一起扫描。

并发垃圾回收

并发垃圾回收主要目标是将垃圾回收工作尽可能多地与应用程序线程并行执行,以减少STW的停顿时间

  1. 初始标记:需要STW
  2. 并发标记:不需要STW
  3. 最终标记:需要STW
  4. 并发清理:不需要STW

实现并发垃圾回收的方法

增量更新
增量更新方法通过记录引用的变化,确保垃圾回收器不会漏掉新的引用。

原始快照
在并发标记过程中不记录引用的修改,只使用开始时的对象图的快照。

经典垃圾回收器

HotSpot虚拟机开发团队一直为消除STW而努力,从Serial到Parallel再到Concurrent Mark Sweep(CMS)和Garbage First(g1),直到现在的ZGC和Shenandoah。
下面按照出现的时间来进行介绍:

Serial收集器

单线程,只使用一条线程去完成垃圾回收,进行回收过程中必须STW,直到其结束。
新生代标记-复制算法,老年代标记-整理算法。
它仍然是HotSpot虚拟机的默认新生代的收集器。
简单高效,消耗内存小,收集百兆,只需要百毫秒内。

ParNew

ParNew是Serial的多线程并行版本,就是相对于在新生代运行多个Serial,而老年代只运行一个,所以也需要STW。
不少服务端的HotSpot仍然运行ParNew,因为除了Serial外,只要它能和CMS收集器配合工作。
随着G1的出现,ParNew和CMS绑定,ParNew作为CMS的默认的新生代的垃圾回收器。

Parallel Scavenge收集器:更关注吞吐量

新生代收集器,标记-复制算法,并行收集。
自适应堆内存调优策略:设置最大堆,最大停顿时间,吞吐量后,虚拟机会根据JVM的信息来进行调优。

Serial Old收集器

Serial Old是Serial Old的老年代版本,使用单线程和标记-整理发。

Parallel Old :更关注吞吐量

是Parallel Scavenge的老年代版本,标记整理法,多线程。

CMS收集器(Concurrent Mark Sweep):以最短STW为目标

标记-清除算法,当内存碎片化无法忍受时,使用标记-整理算法。
并发的标记过程:

  1. 初始标记:需要STW
  2. 并发标记:不需要STW
  3. 最终标记:需要STW
  4. 并发清理:不需要STW
    缺点:消耗cpu资源较多。

Garbage First(g1)

作为CMS的继承者和超越者。
全代回收。每个Region都需要一个卡表。
打破了固定的代分区,将所有堆内存划分为区域,每个区域可以是Eden,可以是Survivor或者老年代,并且回收器根据区域的分代来使用回收策略。并且g1还有专门用来存储大对象的一个特殊区域,g1将其当作老年代来看待。
g1能够预测每次的STW的时间,就是因为它将划分的区域作为每次回收的基本单位,每次回收都是Region的整数倍。
g1会跟踪每个Region的价值(可回收的内存和花费的时间),然后在后台维护一个优先级队列,每次根据价值选择回收的Region。通过将旧Region的存活对象复制到空的Region上,再清理掉旧Region的内存,这个过程必须STW。
可以由用户指定预期停顿的时间大小
缺点:花费内存和处理器性能较高。

选择合适的垃圾回收器

吞吐量,时延,内存消耗。,不可能三角。

对象优先在Eden分配

当Eden没有空间后,虚拟机将发起一次Gc。

大对象直接进入老年代

避免大对象在两个Survivor之间来回复制。

长期存活的对象将进入老年代

记录对象的年龄

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值