垃圾回收策略和算法,看这篇就够了

作者 | Craig无忌
来源 | 程序员大帝(ID:kingcoding)


前言

回收,旧手机,旧冰箱,旧空调,旧洗衣机,电瓶车摩托车,自行车,报纸,塑料......

还记得小时候,我喝完的饮料瓶子都不会扔,每次都放到阳台。小区里听到收废品的吆喝,感觉带着这些瓶瓶罐罐冲下楼,换几块钱买雪糕,想想都是童年的回忆啊。

我一直都觉得骑个三轮车,回收废品的大爷特别酷,因为感觉他的车上面就像哆啦A梦的口袋,翻一翻什么都有。不过这些年,随着垃圾分类,感觉收废品的大爷也越来越少了。

回过头想,如果没有这些收废品的大爷,那我攒的瓶子也卖不了钱,家里阳台那么多瓶子还占地方。所以你大爷就是你大爷,主动过来帮你清理垃圾,还给你钱。

所以为什么 Java 越来越流行,除了说它一处安装,到处运行的机制以外。还因为程序员也越来越懒,跟 C/C++ 相比,Java 最适合懒人的便是引入了自动垃圾回收的机制,也就是Garage Collection(下文简称 GC )。

网上对于 Java 垃圾回收的介绍堪称冠冕堂皇:

让程序员专注于程序本身,不用关心内存回收这些恼人的问题,真正让程序员的生产力得到了释放,程序员不用感知到它的存在。

说这么多,不就是程序员懒么, Java 直接帮你把脏活累活都干了。就像咱们现在人都爱点外卖,为什么?因为不用自己动手,吃完也不用洗碗。还有你去餐盘吃饭,吃完就走,服务员会替你收拾好这些餐盘,你不会关心服务员什么时候来收,怎么收。

大家可能会说既然 Java 这么方便,已经帮我们完成了对垃圾的清理与回收,那 GC 方面的知识我不用了解好像也没事吧。但是人有失手,马有失蹄,假如突然有一天外卖小哥带着你的外卖小哥跑路了,你必须要亲自动手下厨,总不能饿死吧。

所以对于 GC,道理也是一样的,线上的服务不遇到问题还好,出现 Bug 或者想自己做一些性能调优的时候,就需要对 GC 有深入了解才可以,这也是成为一名优秀 Java 程序员的必修课!

今天就把 JVM GC 相关的知识详细介绍一下,本文将会从以下几个方面来讲述相关知识,文字较多,相信大家耐心看了之后肯定有收获,码字不易,别忘了「在看」,「转发」哦。

  • JVM 内存区域

  • 回收策略

  • 垃圾回收经典算法

  • 垃圾回收器对比

JVM 内存区域


我们首先要知道垃圾回收主要回收的是哪些数据,这些数据主要在哪一块区域,所以我们一起来看下 JVM 的内存区域。

JDK8以前

在JDK8之前的虚拟机,主要包含:

(1)堆

对象实例和数组都是在堆上分配的,GC 也主要对这两类数据进行回收,这里是 GC 发生的主要区域!

(2)方法区(永久代)

方法区在 JVM 中是一个非常重要的区域,它与堆一样是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

方法区是堆的一个逻辑部分,为了区分Java堆,它还有一个别名 Non-Heap(非堆)。相对而言,GC 对于这个区域的收集是很少出现的。当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常。

随着动态类加载的情况越来越多,这块内存越来越不太可控。如果设置小了,当JVM加载的类信息容量超过了这个值,系统运行过程中就容易出现内存溢出 OOM:PermGen 的错误,设置大了又浪费内存。

(3)栈

栈是线程私有的,生命周期与线程相同,主要保存执行方法时的局部变量表、操作数栈、动态连接和方法返回地址等信息。这块区域是不需要进行 GC 的。

(4)程序计数器

程序计数器也是线程私有的,它里面记录了下一次需要执行的行号,这块区域也不需要进行 GC。

(5)本地方法栈

本地方法栈主要为了虚拟机执行 Java 的本地方法( Native Method)时服务,这块区域也不需要进行 GC。

JDK8之后

JDK8 最大的变化就是对 JVM 内存空间进行了改造,主要的区别是将方法区进行了移除,并新增了元空间,元空间是放置在 JVM 内存空间之外的直接内存中,并且 JDK8 中对于方法区的参数 PermSize 和 MaxPermSize 已经失效。

上文咱们已经介绍过,JDK8 之前方法区放在 JVM 之中,但是随着动态类加载的情况越来越多,很容易因为大小的限制导致内存溢出 OOM:PermGen 的错误。

所以JDK8 之后把使用元空间替代了原来的方法区,在这种架构下,元空间就突破了原来-XX:MaxPermSize 的限制。这样就从一定程度上解决了原来在运行时生成大量类造成经常 Full GC 问题,如运行时使用反射、代理等,所以升级以后Java堆空间可能会增加。

垃圾回收策略


凡事都讲解个策略,那么 Java 怎么判断堆中的对象实例或数据是不是垃圾呢,应不应该把它回收掉呢?

引用计数法

第一种最简单粗暴的就是引用计数法。当对象被引用,程序计数器 +1,释放时候 -1,当为 0 时证明对象未被引用,可以回收。


但是这个算法有明显的缺陷,对于循环引用的情况下,循环引用的对象就不会被回收。例如下图:对象 A,对象 B 循环引用,没有其他的对象引用 A 和 B,则 A 和 B 都不会被回收。

可达性分析

第二种策略明显好的多,也就是所谓可达性分析法。它指的,通过一系列称之为“GC Roots” 的对象作为起点;从此起点向下搜索,所走过的路径称之为引用链,当一个对象到 GC Roots 没有任何引用链相连接,代表此对象不可达。


在 Java 可以作为GC Roots 的对象包括:

1、虚拟机栈(帧栈中的局部变量表)中的引用对象;

2、方法区中类静态属性引用的对象;

3、方法区中常量引用的对象;

4、本地方法栈中JNI (即一般说的 Native 方法) 的引用对象;

画外音:GC Roots有哪些这个问题经常在面试中被问到,大家一定牢记!

垃圾回收经典算法


知道了应该对哪些对象进行回收,那接下来就要看如何回收了,经典的垃圾回收算法有三种。

标记 - 清除算法


在gc时候,首先扫描时对需要清理的无用对象进行标记,然后将这些对象直接清理。

操作起来非常很简单,但仔细想想有什么问题呢?

没错,内存碎片!如上图,如果清理了两个 1kb 的对象,再添加一个 2kb 的对象,是无法放入这两个位置的。

怎么解决呢,如果能把这些碎片的内存连起来就可以了!

标记 - 整理算法


标记 - 整理算法就是在标记 - 清理算法的基础上,多加了一步整理的过程,把空闲的空间进行上移,从而解决了内存碎片的问题。

但是缺点也很明显:每进一次垃圾清除都要频繁地移动存活的对象,效率十分低下。

复制算法


复制算法是将空间一分为二,在清理时,将需要保留的对象复制到第二块区域上,复制的时候直接紧凑排列,然后把原来的一块区域清空。

不过复制算法的缺点也显而易见,本来 JVM 堆假设有 100M 内存,结果由于将空间一分为二,真正能用的变成只有 50M 了!这肯定是不能接受的!另外每次回收也要把存活对象移动到另一半,效率低下。

分代算法

分代收集算法整合了以上算法,综合了这些算法的优点,最大程度避免了它们的缺点。与其说它是算法,倒不是说它是一种策略,因为它是把上述几种算法整合在了一起,我们先从下图看看对象的生存规律。

由图可知,大部分的对象都很短命,一般来说,98% 的对象都是朝生夕死的,所以分代收集算法根据对象存活周期的不同将堆分成新生代和老生代。

新生代和老年代的默认比例为 1 : 2,新生代又分为 Eden 区, from Survivor 区(简称 S0 ),to Survivor 区(简称 S1 ),三者的比例为 8: 1 : 1。

根据新老生代的特点选择最合适的垃圾回收算法,我们把新生代发生的 GC 称为 Young GC(也叫 Minor GC ),老年代发生的 GC 称为 Old GC(也称为 Full GC )。

大多数情况下,对象在新生代 Eden区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 MinorGC;

Minor GC 非常频繁,一般回收速度也比较快;出现了 Full GC,经常会伴随至少一次的 Minor GC,Full GC 的速度一般会比 Minor GC 慢10倍以上。

    

整个过程大致分为以下几个步骤:

(1)当 Eden 满了后,进行 Minor GC,将需要保存的数据复制到 S0 中;

(2)然后清空 Eden 和 S1 区域,需要保留的对象目前在 S0 中;

(3)下一次当 Eden 满了后,进行Minor GC,将原来 S0 存在的数据复制到S1中,将 Eden 中需要保存的数据也复制到 S1 中;

(4)清空 Eden 和 S0 区域,需要保存的对象目前都在 S1 中;

(5)Eden+S0 复制到 S1;

(6)Eden+S1 复制到 S0;

(7)Eden+S0 复制到 S1;

周而复始...


垃圾回收器对比


前面的内容更多的是方法论,真正执行垃圾回收的要靠各个垃圾回收器。

Java虚拟机规范并没有规定垃圾收集器应该如何实现,因此一般来说不同厂商,不同版本的虚拟机提供的垃圾收集器实现可能会有差别,一般会给出参数来让用户根据应用的特点来组合各个年代使用的收集器,主要有以下几种垃圾收集器。

Serial收集器

从名字看出这是一个单线程收集器。串行垃圾回收器在进行垃圾回收时,它会持有所有应用程序的线程,冻结所有应用程序线程,使用单个垃圾回收线程来进行垃圾回收工作。

它是 JDK1.3 之前新生代的回收器的唯一选择,在单线程的情况效果很好,因为单线程没有线程的切换的开销。但是在现在大部分都是多 CPU 的服务器,所以它现在被使用的很少了。

但是它还是 JVM 运行在 Client 模式下的默认垃圾收集器。因为一般桌面应用下新生代空间不是很大,使用这个垃圾回收器也可以保证回收的时间在 100 毫秒左右。

Serial-Old收集器

这个收集器就是 serial 收集器的老年版本,他同样还是单线程的垃圾回收器。它存在的主要意义的还是 JVM 运行在 client 模式下的默认老年代回收器跟 serial收集器一起使用,同样它还作为 CMS 垃圾回收器的后备垃圾回收器。


ParNew收集器

ParNew 垃圾收集器就是 serial 回收器的多线程版本,有很多的代码都是和 serial 收集器公用的。一个很重要的作用就是作为新生代的垃圾回收器跟 CMS垃圾回收器进行组合。但是在单核 CPU 的情况下,效率是没有 serial 垃圾回收器的效果好的。

可以通过-XX:UseConcMarkSweepGC 或者-XX:UseParNewGC 来指定使用它。默认情况它用于回收垃圾的线程的数目跟 CPU 的数目相同。可以通过-XX:parallelGCThreads 来指定使用的垃圾回收的线程的数目。


Parallel Scavenge收集器

与 ParNew 线程一样同样为多线程的垃圾回收器,但是这个垃圾回收器和其他回收器的关注点不同。其他的垃圾回收器是尽可能缩短垃圾回收时对用户线程的缩短时间。但是这个垃圾回收器关注的是一个吞吐量的概念。

吞吐量指的是运行用户代码的时间/(运行用户代码时间+垃圾回收时间)。缩短用户停顿时间对那些高交互性比如一些 web 项目看中的。而吞吐量是一些运行在后台的计算任务是看重的。

Parallel Old收集器

这个回收器是 Parallel scavenge 的老年代版本,经常和 Parallel scavenge 一起使用在对内存比较敏感和对吞吐量比较高的场合下使用,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6 中才开始提供


CMS收集器(划重点!!)

CMS( Concurrent Mark Sweep )收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或 B/S 系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS 基于“标记-清除”算法实现的,整个过程分为 4 个步骤,包括:

(1)初始标记:只标记根节点直接关联的引用对象,需要暂停用户线程(时间短);

(2)并发标记:标记其他引用对象,可以跟用户线程并发同时执行;

(3)重新标记:暂停用户线程,对并发标记期间新增加的引用关系变化再次标记(时间短);

(4)并发清除:跟用户线程并发进行。

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,并发标记阶段就是进行 GC Roots Tracing 的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发地执行。

CMS 收集器已经在很大程度上减少了用户线程的停顿时间,但是他也存在下面三个主要的缺点:

(1)跟用户线程竞争资源

CMS 默认的并发线程数目为(CPU 数目+3)/4,当 CPU 线程大于 4 的时候,CMS 垃圾收集器至少要占用 25% 的资源。当小于 4 的时候占用 CPU 资源更加明显。

(2)无法清除浮动垃圾

当收集器在进行并发清除垃圾的时候,由于用户线程还在执行,要预留一定的空间给用户线程进行使用,所以收集器一定不能在老年代已经占用 100% 的情况下再进行垃圾收集。

(3)内存碎片

因为这个垃圾回收器是使用的标记-清除算法,所以会产生大量的内存碎片。

有两个值可以进行控制:

-XX:UseCMSCompactAtFullCollection 默认开启,来指定需要 FULL GC 时,会对内存空间进行一次整理。

-XX:CMSFullGCsBeforeCompaction 来指定多少次不整理之后进行一次整理。


G1收集器(划重点!!)

G1 是目前技术发展的最前沿成果之一,HotSpot 开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS 收集器相比 G1 收集器有以下特点:

(1)空间整合:G1 收集器采用标记-整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次 GC。

(2)可预测停顿:这是 G1 的另一大优势,降低停顿时间是 G1 和 CMS 的共同关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为 N 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒,这几乎已经是实时垃圾收集器的特征了。

上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而 G1 不再是这样。使用 G1 收集器时,Java 堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分可以不连续 Region 的集合。


本文介绍了 JVM 垃圾回收的原理与垃圾收集器的种类,相信看到这里的各位人才应该对相关知识有了更深刻的认识。

理论有了,接下来我会持续更新相关内容,介绍下真实场景下如何对 JVM 进行调优以及故障排查。

更多推荐阅读

  • 中台架构详解(上) | 大咖说中台

  • 游戏行业应该如何建设数据中台?

  • 30 年开源老兵,10 年躬耕 OpenStack,开源 1000 万行核心代码

  • Python再夺冠,上古语言COBOL大流行,IEEE Spectrum 2020年度编程语言排行榜出炉!

  • 美国禁止与字节跳动及微信交易,腾讯股价暴跌,字节跳动回应了

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

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

相关文章

手把手教你在物联网为产品定义物模型

物联网平台支持为产品定义物模型,将实际产品抽象成由属性、服务、事件所组成的数据模型,便于云端管理和数据交互。产品创建完成后,您可以为它定义物模型,产品下的设备将自动继承物模型内容。 操作步骤 产品列表中,选…

SpringBoot/Cloud AOP 统一日志输出

文章目录1. 导入依赖2. aop拦截器3. logback配置4. 测试类5. 关键点6. 效果图1. 导入依赖 <!-- AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>&l…

社区网站类场景下的静态资源处理

社区网站类场景下静态资源处理 场景描述 解决的问题 本实践通过搭建WordPress博客系统&#xff0c;向用户展示如何l 静态资源&#xff08;图片、视频等&#xff09;CDN访问加速和刷新 将图片、附件等静态资源上传到阿里云OSS&#xff0c;并通过阿里l OSS对象跨国际区域进行复…

收藏!美国博士明确给出Python的高效学习技巧

我见过市面上很多的 Python 讲解教程和书籍&#xff0c;他们大都这样讲 Python 的&#xff1a;先从 Python 的发展历史开始&#xff0c;介绍 Python 的基本语法规则&#xff0c;Python 的 list, dict, tuple 等数据结构&#xff0c;然后再介绍字符串处理和正则表达式&#xff0…

云服务器ECS共享标准型S6全新发布, 行业内最具性价比

近日&#xff0c;阿里云弹性计算发布全新一代云服务ECS共享标准型S6&#xff0c;性能相对上一 代实例提升15%以上&#xff0c;价格相对上一代最高降低42%&#xff0c;是目前国内云计算厂商 更能够提供的最具性价比的云服务器产品。一些中小型网站、轻量数据库以及轻 量企业应…

SpringBoot/Cloud 统一返回优雅设计+自定义异常

文章目录1. 返回结果封装2. 自定义异常3. 校验工具类4. 使用案例5. 前端效果图1. 返回结果封装 package com.course.server.dto;public class ResponseDto<T> {/*** 业务上的成功或失败*/private boolean success true;/*** 返回码*/private String code;/*** 返回信息…

点游出行提供内地与香港游客高质量的旅程

公司介绍 我们公司是成都市点游出行科技有限责任公司&#xff0c;是一个服务于旅游行业的香港科学园园区企业。业务包括旅游行程规划、出行攻略、旅游视频、智能酒店、旅游酒店预订等等&#xff0c;以APP、小程序、网站等方式在阿里云开展国际旅行业务&#xff0c;APP名称叫做…

赠书 | SkyWalking 观测 Service Mesh 技术大公开

导读&#xff1a;本文摘自于SkyWalking创始人吴晟撰写的《Apache SkyWalking实战》一书&#xff0c;介绍了SkyWalking对Service Mesh的监控模型&#xff0c;并重点阐述了其与Istio的集成。通过本文&#xff0c;希望你对SkyWalking观测Service Mesh场景有深入的了解&#xff0c;…

鼐鼐家为用户打造3D互动体验式营销解决方案

公司介绍 我们公司是上海鼐鼐家软件有限公司&#xff0c;主要业务是装修设计平台&#xff0c;有一个官网和APP&#xff0c;APP是鼐鼐家软件&#xff0c;是我们公司自主研发基于HTML5的WEB 3D 云设计和云渲染技术平台。专业的在线3D设计工具&#xff0c;将领先的Web 3D技术和家居…

vue 集成 sweetalert2 提示组件

文章目录一、项目集成1. 引入方式2. 确认框封装3. 提示框封装4. 确认框使用5. 消息提示框使用6.项目效果一、项目集成 官网链接&#xff1a;https://sweetalert2.github.io 案例 1. 引入方式 CDN引入方式&#xff1a; 在index.html中全局引入 <script src"//c…

基于弹性计算的AI推理

场景描述 本方案适用于使用GPU进行AI在线推理的场 景。在推理之前&#xff0c;模型已经训练完成。例如,刷脸 支付中&#xff0c;我们在刷脸的时候&#xff0c;就是推理的一个过 程。再比如图像分类&#xff0c;目标检测&#xff0c;语音识别&#xff0c;语 义分析等返回结果…

vue 集成 sweetalert2 前端校验

文章目录1. 集成 sweetalert22. 校验工具类抽象3. 校验工具类4. 使用5. 效果图6. 后端集成1. 集成 sweetalert2 官网&#xff1a;https://sweetalert2.github.io 在index.html引入 <script src"//cdn.jsdelivr.net/npm/sweetalert211"></script>2. 校验…

vue 集成 Loading 加载效果

文章目录1. 工具类2. 插件官网3. 导入js4.. 使用5. 效果图1. 工具类 Loading {show: function () {$.blockUI({message: <img src"/static/image/loading.gif" />,css: {zIndex: "10011",padding: "10px",left: "50%",width: …

永远不要对 AI 说:“我不行!”

最近这些年&#xff0c;如果我要问什么技术最值钱&#xff0c;我想超过90%的人都会说是&#xff1a;人工智能。确实&#xff0c;随着近些年人工智能发展与普及&#xff0c;AI研发岗早已成为编程领域薪水最高的岗位之一。但扎心的是&#xff0c;一提到 AI &#xff0c;相信绝大部…

SringBoot/Cloud/Aalibab 事务管理

文章目录1. 事务管理场景2. 使用说明3. 事务失效场景1. 事务管理场景 当方法内部操作多张表时&#xff0c;应该添加事务管理&#xff0c;保证原子性&#xff08;同时成功或者同时失败&#xff09; 2. 使用说明 添加EnableTransactionManagement注解 在具体的方法上添加Trans…

CPU:别再拿我当搬砖工!

来源 | 编程技术宇宙责编 | 晋兆雨封图 | CSDN 下载自视觉中国数据搬运工Hi&#xff0c;我是CPU一号车间的阿Q&#xff0c;有段日子没见面了。还记得上回说到咱们厂里用上了DMA技术&#xff08;太慢不能忍&#xff01;CPU又拿硬盘和网卡开刀了&#xff01;&#xff09;之后&…

用函数计算搭建页面的前端CICD系统,提升访问体验

场景描述 传统动静不分离的产品架构&#xff0c;随着访问量在增长&#xff0c;性能会成为瓶颈。在这种情况下&#xff0c;用户可以通过利用OSS和CDN对网站进行架构优化&#xff0c;做到网站文件的动静分离&#xff0c;提升用户访问体验&#xff0c;实现成本可控。本方案使用函…

构建电商网站业务安全系统,防止“薅羊毛”

构建电商网站业务安全系统&#xff0c;防止“薅羊毛” 2020-02-19 新零售安全 场景描述 业务运营活动是电商行业开展业务必不可少的手段&#xff0c;但大流量带来的系统可用性、优惠券带来的“薅羊毛”等问题屡见不鲜&#xff0c;都会影响到运营效果、甚至出现负面影响。阿里云…

data.name.toLowerCase() is not a function问题

文章目录1. 现象2. 分析3. 解决方案1. 现象 Error in v-on handler: "TypeError: suffixs[i].toLowerCase is not a function" 2. 分析 主要原因是.toLowerCase()方法需要前面是字符串类型 3. 解决方案 案例&#xff1a; (data.name ) .toLowerCase()源码 修改…

云服务器ECS使用限制概览,让你的上云少走一些坑

限制概述 使用云服务器ECS有下列限制&#xff1a; 不支持安装虚拟化软件和二次虚拟化&#xff08;例如安装使用VMware Workstation&#xff09;。仅弹性裸金属服务器和超级计算集群支持二次虚拟化。不支持声卡应用。不支持直接加载外接硬件设备&#xff08;如硬件加密狗、U盘…