JVM - GC垃圾回收

news2024/9/20 18:32:43

文章目录

目录

文章目录

1. 自动垃圾回收

1.1 垃圾回收区域

2. 方法区回收

3. 堆回收

3.1 对象已死?

3.1.1 引用计数算法

3.1.2 可达性分析算法

3.1.3 再谈引用

强引用

软引用

弱引用

虚引用

3.2 垃圾收集算法

3.2.1 分代收集理论

3.2.2 垃圾回收算法的评价标准

3.2.3 标记-清除算法

3.2.4 标记-复制算法

3.2.5 标记-整理算法

3.2.6 分代垃圾回收

3.3 垃圾回收器

3.3.1 Serial收集器

3.3.2 Serial Old收集器

3.3.3 ParNew收集器

3.3.4 CMS收集器

3.3.5 Parallel Scavenge收集器

3.3.6 Parallel Old收集器

3.3.7 G1垃圾回收器

内存结构

回收方式

年轻代回收(Young GC)

混合回收

FULL GC

总结


1. 自动垃圾回收

在C/C++这类没有自动垃圾回收机制的语言中,一个对象如果不再使用,需要手动释放,否则就会出现 内存泄漏。我们称这种释放对象的过程为垃圾回收,而需要程序员编写代码进行回收的方式为手动回收。

Java中为了简化对象的释放,引入了自动的垃圾回收(Garbage Collection简称GC)机制。通过垃 圾回收器来对不再使用的对象完成自动的回收,垃圾回收器主要负责对堆上的内存进行回收。其他 很多现代语言比如C#、Python、Go都拥有自己的垃圾回收器。

自动根据对象是否使用由虚拟机来回收对象

优点:降低程序员实现难度、降低对象回收bug的可能性

缺点:程序员无法控制内存回收的及时性

之所以要去了解垃圾收集和内存分配, 是因为当需要排查各种内存溢出,内存泄漏等问题时,当垃圾收集成为系统达到高可用瓶颈时,我们就需要对这些"自动化" 的技术实施必要的监控和调节。

1.1 垃圾回收区域

运行时数据区域中的程序计数器,本地方法栈,虚拟机栈随着线程的销毁而被直接回收,不需要进行控制,垃圾回收本质上就是对方法区和堆内存进行垃圾回收。

2. 方法区回收

方法区的垃圾回收主要回收两部分:废弃的常量和不再使用的类型。

判断一个常量是否废弃是相对来说比较简单的,而要判断一个类型是否属于 “不再使用类型” 就比较复杂了。 需要满足下面三个条件。

  • 此类所有实例对象都已经被回收,在堆中不存在任何该类的实例对象以及子类对象。
  • 加载该类的类加载器已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用

3. 堆回收

3.1 对象已死?

在堆中存放着Java世界中几乎所有的对象实例, 垃圾回收器在对堆进行回收前,第一件事情就是要确定哪些对象还 “存活” , 那些对象 “死去”。

3.1.1 引用计数算法

引用计数是指:通过在对象中添加一个引用计数器, 每当有一个地方引用它时,计数器加1,当引用失效时,计数器减1。

这个算法有一个严重的弊端:存在循环引用问题。

public class TestReferenceCounting {
    public static TestReferenceCounting instance;
    public static void main(String[] args) {
        TestReferenceCounting obj1 = new TestReferenceCounting();
        TestReferenceCounting obj2 = new TestReferenceCounting();
        
        obj1.instance = obj2;
        obj2.instance = obj1;
    }
}

因此Java虚拟机没有采用引用计数算法来判断对象的存活情况。 

3.1.2 可达性分析算法

当前市面上大部分语言的内存管理子系统,都是通过可达性分析算法来判断对象是否存活的。这个算法的基本思路就是通过一系列称为“GC Roots” 的根节点作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程中走过的路程称之为 “引用链” ,如果某个对象没在这个引用链上,那么就说这个对象不可达。

在Java技术体系中,固定可以作为GC Roots的对象如下:

  • 线程Thread对象。
  • 系统类加载器加载的java.lang.Class对象,引用类中的静态变量。
  • 监视器对象,用来保存同步锁synchronized关键字持有的对象。
  • 本地方法调用时使用的全局对象。

3.1.3 再谈引用

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达引用链,这些都和引用离不开关系。

在JDK1.2 版本之前,Java里面的对象只有引用和被引用两种状态,如果我们想要想要描述一类对象:在内存充足的情况下留在内存中,内存不足的情况被回收掉,显然是无法实现的。

在JDK1.2版本之后, Java对引用概念进行了扩充,将引用分为强引用(Strongly Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference)。

强引用

最传统的 “引用”。

软引用

软引用相对于强引用是一种比较弱的引用关系,如果一个对象只有软引用关联到它,当程序内存不足时,就会将软引用中的数据进行回收。

在JDK 1.2版之后提供了SoftReference类来实现软引用,软引用常用于缓存中。

使用场景 - 缓存

弱引用

弱引用的整体机制和软引用基本一致,区别在于弱引用包含的对象在垃圾回收时,不管内存够不够都会直接被回收。 在JDK 1.2版之后提供了WeakReference类来实现弱引用,弱引用主要在ThreadLocal中使用。 弱引用对象本身也可以使用引用队列进行回收。

虚引用

虚引用也叫幽灵引用/幻影引用,不能通过虚引用对象获取到包含的对象。虚引用唯一的用途是当对象被垃圾回 收器回收时可以接收到对应的通知。Java中使用PhantomReference实现了虚引用,直接内存中为了及时知道 直接内存对象不再使用,从而回收内存,使用了虚引用来实现。

3.2 垃圾收集算法

从如何判断对象是否消亡的角度出发, 垃圾回收算法可以分为 “引用计数垃圾收集” 和“追踪式垃圾收集” 两大类。 这两类也被称为 “直接垃圾回收” 和 “间接垃圾回收” 。由于Java虚拟机并没有使用引用计数算法来判断对象是否存活, 那我们便将关注点转到追踪式垃圾收集上。

3.2.1 分代收集理论

分代收集名为理论,实际上是一套符合大多数程序运行实际情况的经验法则, 它建立在两个分代假说之上。

1) 弱分代假说:绝大多数对象都是朝生夕死的。

2)强分代假说:熬过越多次垃圾回收的对象越不容易消亡。

根据这一理论,收集器应该将Java堆划分出不同的区域,然后按照年龄将对象分配到不同的区域中

进行存储。Java也。

Java堆通常被划分为是这么做的以下几个区域:

  1. 年轻代(Young Generation)

    • 这是新创建对象的主要存储区域。年轻代又可以细分为三个部分:
      • Eden区:新对象首先在Eden区分配内存。
      • Survivor区:Eden区的垃圾收集后,存活的对象会被移动到Survivor区。Survivor区又分为两个部分,通常称为S0和S1,交替使用。
  2. 老年代(Old Generation)

    • 当对象在年轻代中经历了多次垃圾收集仍然存活时,它们会被移动到老年代。老年代的垃圾收集频率较低,通常采用标记-清除或标记-整理算法。
  3. 永久代(Permanent Generation)(在Java 8及之前版本):

    • 存放类的元数据、常量池等信息。永久代的大小是固定的,可能会导致内存溢出。在Java 8及之后的版本中,永久代被元空间(Metaspace)取代,元空间使用本地内存而不是堆内存。

3.2.2 垃圾回收算法的评价标准

1.吞吐量:吞吐量指的是 CPU 用于执行用户代码的时间与 CPU 总执行时间的比值,即吞吐量 = 执行用户代码时间 / (执行用户代码时间 + GC时间)。吞吐量数值越高,垃圾回收的效率就越高。

2.最大暂停时间:最大暂停时间指的是所有在垃圾回收过程中的STW时间最大值。比如如下的图中,黄色部分的STW就是最 大暂停时间,显而易见上面的图比下面的图拥有更少的最大暂停时间。最大暂停时间越短,用户使用系统时 受到的影响就越短。

 3.堆使用效率: 不同垃圾回收算法,对堆内存的使用方式是不同的。比如标记清除算法,可以使用完整的堆内存。而复制算 法会将堆内存一分为二,每次只能使用一半内存。从堆使用效率上来说,标记清除算法要优于复制算法。 

3.2.3 标记-清除算法

标记清除算法的核心思想分为两个阶段:

1.标记阶段,将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出 所有存活对象。

2.清除阶段,从内存中删除没有被标记也就是非存活对象。

优点:实现简单,只需要在第一阶段给每个对象维护标志位,第二阶段删除对象即可。

缺点:

1.碎片化问题 由于内存是连续的,所以在对象被删除之后,内存中会出现很多细小的可用内存单元。如果我们需要的是一 个比较大的空间,很有可能这些内存单元的大小过小无法进行分配。

2.分配速度慢。由于内存碎片的存在,需要维护一个空闲链表,极有可能发生每次需要遍历到链表的最后才 能获得合适的内存空间。

3.2.4 标记-复制算法

复制算法的核心思想是: 1.准备两块空间From空间和To空间,每次在对象分配阶段,只能使用其中一块空间(From空间)。 2.在垃圾回收GC阶段,将From中存活对象复制到To空间。 3.将两块空间的From和To名字互换。

优缺点

3.2.5 标记-整理算法

标记整理算法也叫标记压缩算法,是对标记清理算法中容易产生内存碎片问题的一种解决方案。 核心思想分为两个阶段: 1.标记阶段,将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出 所有存活对象。 2.整理阶段,将存活对象移动到堆的一端。清理掉存活对象的内存空间。

优缺点

3.2.6 分代垃圾回收

现代优秀的垃圾回收算法,会将上述描述的垃圾回收算法组合进行使用,其中应用最广的就是分代垃圾回收 算法(Generational GC)。 分代垃圾回收将整个内存区域划分为年轻代和老年代。

IBM公司曾有一项专门研究对新生代 "朝生夕灭" 的特点做了更量化的诠释。新生代中的对象有98%熬不过第一轮收集。因此并不需要按照1:1的比例来划分新生代的内存空间。分代垃圾回收就是这么做的。把新生代划分为一块较大的Eden空间和两块较小的Surivivor空间, 每次分配内存只使用Eden和其中一块Surivivor。 发生垃圾收集时,将Eden和Surivivor中仍然存活的对象一次性复制到另一块Surivivor空间上,然后直接清理掉Eden和已经使用过的那块Surivivor空间。

如果Surivivor空间不足以容纳一次Minor GC(新生代收集),就需要依赖其他内存区域(大多数是老年代)进行分配担保。

3.3 垃圾回收器

如果说收集算法是内存回收的方法论, 那垃圾收集就是内存回收的实践者。《Java虚拟机规范》中对垃圾收集器应该怎样实现没有做出任何规定,因此不同的厂商,不同版本的虚拟机所包含的垃圾回收器可能会有很大差异。

HotSpot虚拟机的垃圾回收器图示。

3.3.1 Serial收集器

Serial是最基础,历史最久远的垃圾收集器,在JDK1.3之前是HotSpot虚拟机新生代收集器的唯一选择。

Serial是是一种单线程串行回收年轻 代的垃圾回收器。它的单线程的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是在强调它在进行垃圾回收时,必须暂停所有其他的工作线程,直到它收集结束。

3.3.2 Serial Old收集器

Serial Old 是 Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记整理算法。

3.3.3 ParNew收集器

ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾回收之外,其余的行为包括Serial收集器可用的控制参数,收集算法,Stop The World, 对象分配规则,回收策略等都与Serial收集器完全一致。

3.3.4 CMS收集器

在JDK5时,HotSpot推出了一款在强交互应用中几乎可以称之为划时代的垃圾收集器 - CMS收集器。

这款收集器是HotSpot虚拟机中第一款真正意义上支持并发的垃圾收集器,它首次实现了让垃圾收集线程和用户线程(基本上)同时工作。

CMS执行步骤:

1.初始标记,用极短的时间标记出GC Roots能直接关联到的对象。

2.并发标记, 标记所有的对象,用户线程不需要暂停。

3.重新标记,由于并发标记阶段有些对象会发生了变化,存在错标、漏标等情况,需要重新标记。

4.并发清理,清理死亡的对象,用户线程不需要暂停。

3.3.5 Parallel Scavenge收集器

Parallel Scavenge是JDK8默认的年轻代垃圾回收器, 多线程并行回收,关注的是系统的吞吐量。具备自动 调整堆内存大小的特点。

3.3.6 Parallel Old收集器

Parallel Old是为Parallel Scavenge收集器 设计的老年代版本,利用多线程并发收集。

3.3.7 G1垃圾回收器

JDK9之后默认的垃圾回收器是G1(Garbage First)垃圾回收器。

Parallel Scavenge关注吞吐量,允许用户设置最大暂停时间 ,但是会减少年轻代可用空间的大小。

CMS关注暂停时间,但是吞吐量方面会下降。

而G1设计目标就是将上述两种垃圾回收器的优点融合:

1.支持巨大的堆空间回收,并有较高的吞吐量。

2.支持多CPU并行垃圾回收。

3.允许用户设置最大暂停时间。

内存结构

G1的整个堆会被划分成多个大小相等的区域,称之为区Region,区域不要求是连续的。分为Eden、Survivor、 Old区。Region的大小通过堆空间大小/2048计算得到,也可以通过参数-XX:G1HeapRegionSize=32m指定(其 中32m指定region大小为32M),Region size必须是2的指数幂,取值范围从1M到32M。

回收方式

G1垃圾回收有两种方式: 1、年轻代回收(Young GC) 2、混合回收(Mixed GC)。

年轻代回收(Young GC)

回收Eden区和Survivor区中不用的对象。会导致STW,G1中可以通过参数 -XX:MaxGCPauseMillis=n(默认200) 设置每次垃圾回收时的最大暂停时间毫秒数,G1垃圾回收器会尽可能地 保证暂停时间。

执行流程:

1、新创建的对象会存放在Eden区。当G1判断年轻代区不足(max默认60%),无法分配对象时需要回收时会执行 Young GC。

2、标记出Eden和Survivor区域中的存活对象,

3、根据配置的最大暂停时间选择某些区域将存活对象复制到一个新的Survivor区中(年龄+1),清空这些区域。

 

G1在进行Young GC的过程中会去记录每次垃圾回收时每个Eden区和Survivor区的平均耗时,以作为下次回收时的 参考依据。这样就可以根据配置的最大暂停时间计算出本次回收时最多能回收多少个Region区域了。 比如 -XX:MaxGCPauseMillis=n(默认200),每个Region回收耗时40ms,那么这次回收最多只能回收4个Region。

4、后续Young GC时与之前相同,只不过Survivor区中存活对象会被搬运到另一个Survivor区。

5、当某个存活对象的年龄到达阈值(默认15),将被放入老年代。

6、部分对象如果大小超过Region的一半,会直接放入老年代,这类老年代被称为Humongous区。比如堆内存是 4G,每个Region是2M,只要一个大对象超过了1M就被放入Humongous区,如果对象过大会横跨多个Region。

混合回收

7、多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值时 (-XX:InitiatingHeapOccupancyPercent默认45%)会触发混合回收MixedGC。回收所有年轻代和 部分老年代的对象以及大对象区。采用复制算法来完成。

混合回收分为:初始标记(initial mark)、并发标记(concurrent mark)、最终标记(remark或者Finalize Marking)、并发清理(cleanup)

G1对老年代的清理会选择存活度最低的区域来进行回收,这样可以保证回收效率最高,这也是G1(Garbage first)名称的由来。

最后清理阶段使用复制算法,不会产生内存碎片。

FULL GC

如果清理过程中发现没有足够的空Region存放转移的对象,会出现Full GC。单线程执行标记-整理算法, 此时会导致用户线程的暂停。所以尽量保证应该用的堆内存有一定多余的空间。


总结

以上就是这篇博客的主要内容了,大家多多理解,下一篇博客见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2127933.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android U 多任务启动分屏——Launcher流程(下分屏 更新中)

前文 Android U 多任务启动分屏——Launcher流程(上分屏) 最近任务onClick事件的监听 在最近任务中每个任务都是一个TaskView,对TaskView操作,就是每个任务的操作。 代码路径:packages/apps/Launcher3/quickstep/…

安装Anaconda(过程)

Anaconda是一个开源的Python发行版本,用来管理Python相关的包,安装Anaconda可以很方便的切换不同的环境,使用不同的深度学习框架开发项目,本文将详细介绍Anaconda的安装。 一、安装 1、安装方式 官网:“https://www.…

C#环境搭建和入门教程--vs2022之下

目录 1.环境搭建 2.先让程序跑起来 3.C#代码结构 4.变量,输入输出介绍 5.内容输入和类型转换 1.环境搭建 我们的这个c#基础学习主要就是在这个vs2022上面进行的,我们的这个c/c使用的都是这个平台 我们首先检查一下我们的这个环境是不是完全的配置了…

什么是API网关(API Gateway)?

1. 什么是API网关(API Gateway)? 在微服务体系结构中,客户端可能与多个前端服务进行交互。 API 网关位于客户端与服务之间。 它充当反向代理,将来自客户端的请求路由到服务。 它还可以执行各种横切任务,例…

技术美术一百问(01)

———————————————————问题篇———————————————————— 基础: 解释BRDF? 什么是Lightmap? 游戏里的各种液体怎么实现? 渲染流水线中,屏幕中的一个像素是怎么绘制出来的&#xff…

【推荐100个unity插件之33】比 Unity 自带协程更高效的异步处理方式,提供一个高性能和0GC的async/await异步方案——UniTask插件

文章目录 前言github地址GC(Garbage Collection,垃圾回收)GC 的影响 UniTask优缺点使用案例案例完结 前言 UniTask 是一个轻量级的异步编程库,专门为 Unity 设计,旨在提供比 Unity 自带协程更高效的异步处理方式。它是…

(11)(2.1.1) PWM、OneShot和OneShot125 ESC(一)

文章目录 前言 1 PWM 2 OneShot 3 参数说明 前言 大多数 ArduPilot 飞行器使用由无刷电机 ESC 控制的无刷电机。这些 ESC 使用的最常见协议是PWM、OneShot、OneShot125 和 DShot。本页介绍前三种(PWM、OneShot 和OneShot125)。 !Warning…

中间件安全(一)

本文仅作为学习参考使用,本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 一,中间件。 1,什么是中间件。 是一类能够为一种或多种应用程序合作互通、资源共享,同时还能够为该应用程序提供相关的服务的软件。中间件是一类…

哈尔滨的珍同学

写在前面 10225 字 | 朋友 | 旅行 | 友谊 | 情感 | 感触 | 思考 | 消极内容 全文篇幅过于庞大,请慎重考虑是否阅读。 与佳芯小姐的短文,将另行发布。 TL;DR 我不知道我和珍的关系还能维持多久。 按理来说,与朋友见面应当是一件开心的事情。这…

Linux系统连接蓝牙、WiFi方法分享,适用瑞芯微RK3562、RK3568、RK3588等开发板

本文适用于瑞芯微RK3562、RK3568、RK3588等各类开发板。本教程使用到的是深圳触觉智能开发的RK3562开发板,型号EVB3562,RK3562采用四核Cortex-A53 CPU,频率可达2.0GHz;最大支持 8GB 内存;内置独立的 NPU,可用于轻量级人…

卷轴模式系统源码开发:探索游戏世界——游戏模式的设计

在电子游戏的发展历程中,卷轴模式(Scrolling Mode)作为一种经典且广泛应用的游戏界面呈现方式,为玩家提供了沉浸式的探索体验。从早期的《超级马里奥兄弟renxb001》到现代的《塞尔达传说》系列,卷轴模式不仅定义了众多…

PPT中的图形与图片:插入、调整与格式设置技术详解

目录 引言 一、图形与图片的插入 1. 插入图形 2. 插入图片 二、图形与图片的调整 1. 调整大小与位置 2. 裁剪与旋转 3. 图形与图片的合并与组合 三、图片格式与布局设置 1. 图片格式设置 2. 图片布局设置 示例案例:制作产品展示PPT 四、结论 引言 在现…

Python中如何将图片资源打包进exe文件

目录 一、安装PyInstaller 二、准备图片资源 三、修改图片资源的引用方式 1. 使用Base64编码 2. 修改资源路径的引用 1. 打包命令 2. 打包后的文件 3. 运行exe文件 五、案例与测试 六、总结 在Python开发中,经常需要将图片等资源文件与Python脚本一起打包成独立的可…

【d41】【Java】【力扣】21.合并两个有序链表

题目 21. 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4]示例 2: 输入:l1 [],…

【Python小知识 - 1】:pip下载离线包.whl

文章目录 .whl文件介绍一、批量下载1、requirements.txt准备2、下载相关包及依赖3、离线包下载 二、单个离线包下载1、下载相关包及依赖2、离线包下载 .whl文件介绍 .whl 文件是 Python 的一种打包格式,称为 Wheel。Wheel 是一种现代的 Python 包格式,旨…

由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(五)

概述 在 WWDC 24 中,苹果推出了数据库框架 SwiftData 2.0 版本。其新加入的历史记录追踪(History Trace)机制着实让秃头码农们“如痴如醉”了一番。 我们在之前的博文中已经介绍了 History Trace 是如何处理数据新增操作的。而在这里,我们将再接再厉来完成数据删除时的全盘…

OpenCV结构分析与形状描述符(19)查找二维点集的最小面积外接旋转矩形函数minAreaRect()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 找到一个包围输入的二维点集的最小面积旋转矩形。 该函数计算并返回指定点集的最小面积边界矩形(可能是旋转的)。开发者…

arm64-v8a 和 armeabi-v7a、armeabi 有什么区别

有时下载软件包有好几种选择,包括 arm64-v8a、armeabi-v7a、armeabi、x86_64 等。如果不清楚它们的含义,随便选择下载可能导致软件不能安装。以下是相关知识介绍。 从这些命名大概可以猜出它与 32 位或 64 位软件有关,部分确实可以这样理解&…

Java 每日一刊(第4期):Java 23 即将发布

文章目录 前言Java 23 即将发布,迎接新的功能时代JDK 24 的早期访问版本:Project Loom 的进展JConf.dev 大会:Java 社区的重要盛会本期小知识 创新不是逻辑思维的产物,而是富有远见的心灵创造。 前言 这里是分享 Java 相关内容的…

SprinBoot+Vue山西文旅网的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质…