【JVM】一文搞懂常见GC算法
文章内容
- 1、概述
- 2、如何确定垃圾对象?
- 3、GC算法
- 4、GC算法总结
- 5、常见的垃圾收集器
1、概述
GC目的:程序运行过程中可能会产生许多垃圾对象,持续占用内存会造成内存泄漏,最终可能导致内存溢出,迫使系统中断运行。
首先需要搞懂内存泄漏
和内存溢出
的概念!!!
- 内存泄漏(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。通常少次数的内存无法及时回收并不会到程序造成什么影响,但是如果在内存本身就比较少获取多次导致内存无法正常回收时,就会导致内存不够用,最终导致内存溢出。
- 内存溢出 (out of memory):指程序申请内存时,没有足够的内存供申请者使用,导致数据无法正常存储到内存中。也就是说给你个int类型的存储数据大小的空间,但是却存储一个long类型的数据,这样就会导致内存溢出。
通俗的讲,比如2G内存分配给应用500M,但是由于一些对象没有进行回收,持续占用,导致应用可用内存小于500M(内存少点儿,就好像泄漏一部分)。如果这种情况一直发生,就会最终出现申请内存的大小超过可用内存,此时就会报OOM(内存不够用了,需求过量内存需要溢出点才能满足)。
什么是垃圾(garbage)?没有引用指向的一个或多个对象称为垃圾。
GC特点:频繁收集Young区;较少收集Old区;基本不动Perm区;
JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代,因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC)。
- 普通GC(minor GC):只针对新生代区域的GC。
- 全局GC(full GC):针对年老代的GC。
GC应用:
- 年轻代中使用的是Minor GC,采用的是复制算法(Copying)
- 老年代一般由标记清除或者标记清除与标记整理的混合实现
2、如何确定垃圾对象?
一般有两种方法:
- 引用计数法(reference count):有一个引用指向一个对象,计数就加1 ,直到这个数为0,就会被当作垃圾进行回收。
弊端:无法解决循环引用(对象A维护了一个成员属性,类型是对象B,对象B中维护了一个成员属性,类型是对象A,然后分别给这两个成员属性赋值,在将对象A赋值为null,将对象B赋值为null,这样对象A和对象B就都是null,但是a和b存在引用关系,这样a和b永远不会被回收)的问题。 - 可达性分析:所谓的可达性就是通过一系列称为“GC Roots”的对象为起点,从这些节点开始向下搜索,搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是GC Roots到这个对象不可达)时,则说明此对象是不可用的。如下图所示,ABC可达,DE不可达。
GC Roots对象有哪些?(出自知乎 Java中的GCRoots到底有哪些?)- 两个栈: Java栈 和 Native 栈中所有引用的对象;
- 两个方法区:方法区中的常量和静态变量;
- 所有线程对象;
- 所有跨代引用对象;
- 和已知 GCRoots 对象同属一个CardTable 的其他对象。(后两个不太不理解,望大佬指点)
3、GC算法
GC常用的算法有三个:
- Mark-Sweep(标记清除)
- Copying(复制)
- Mark-Compact(标记整理)
1)标记清除(Mark-Sweep)
标记清除算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
劣势:
- 效率问题,标记和清除过程的效率都不高;
- 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2)复制算法(Copying)
复制算法就是将内存空间按容量分成两块。当这一块内存用完的时候,就将还存活着的对象复制到另外一块上面,然后把已经使用过的这一块一次清理掉。这样使得每次都是对半块内存进行内存回收。内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。
劣势:复制算法弥补了标记清除算法中,内存布局混乱的缺点,不过与此同时,它的缺点也是相当明显的。它浪费了一半的内容,这太要命了。如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重复一遍,复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视,所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。
3)标记整理(Mark-Compact)
标记整理算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。
特点:
- 消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可。
- 消除了复制算法当中,内存减半的高额代价。
劣势:标记整理算法唯一的缺点就是效率不高,不仅要标记所有的存活对象,还要整理所有存活对象引用的地址,从效率上来说,标记整理算法低于复制算法。
4、GC算法总结
内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。
内存整齐度:复制算法=标记整理算法>标记清楚算法
内存利用率:标记整理算法=标记清除算法>复制算法
可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标,标记/整理算法相对来说更平滑一些,但效率上依旧不尽如人意,它比复制算法多了一个标记的阶段,又比标记/清除多了一个整理内存的过程。
难道就没有一种最优算法吗?答案是无,没有最好的算法,只有最合适的算法,即分代收集算法(Generational Collection )。
年轻代(Young Gen):年轻代特点是区域相对老年代较小,对象存活率低。这种情况复制算法的回收整理,速度是最快的,复制算法的效率只和当前存活对象大小有关,因而很适用于年轻代的回收,而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代(Tenure Gen):老年代的特点是区域较大,对象存活率高。这种情况,存在大量存活率高的对象,复制算法明显变得不合适,一般是由标记清除或者是标记清除与标记整理的混合实现。
- Mark阶段的开销与存活对象的数量成正比,这点上说来,对于老年代,标记清除或者标记整理有一些不符,但可以通过多核/线程,通过并发、并行的形式提标记效率。
- Sweep阶段的开销与所管理区域的大小形正相关,但Sweep“就地处决”的特点,回收的过程没有对象的移动,使其相对其它有对象移动步骤的回收算法,仍然是效率最好的,但是需要解决内存碎片问题。
- Compact阶段的开销与存活对象的数据成正比,如上一条所描述,对于大量对象的移动是很大开销的,作为老年代的第一选择并不合适。
基于上面的考虑,老年代一般由标记清除或者标记清除与标记整理的混合实现。以hotspot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对象的回收效率很高,而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器做为补偿措施:当内存回收不佳(碎片导致Concurrent Mode Failure时),将采用Serial Old执行Full GC以达到对老年代内存的整理。
永久代(Permanent Space)元空间:存储class类,常量,方法描述:回收废弃常量和无用类。
分代收集法:也就是Jvm的垃圾回收所使用的算法,这种算法,既提高了内存空间的使用,而且根据各代的特点,针对处理,减少了cpu的使用率,提高了性能。
5、常见的垃圾收集器
收集器 | 收集对象和算法 | 收集器类型 | 说明 | 适用场景 |
---|---|---|---|---|
Serial | 新生代,复制算法 | 单线程 | 简单高效,适合内存不大的情况 | |
ParNew | 新生代,复制算法 | 并行的多线程收集器 | ParNew垃圾收集器是Serial收集器的多线程版本 | 搭配CMS垃圾回收器的首选 |
Parallel Scavenge | 新生代,复制算法 | 并行的多线程收集器 | 类似ParNew,更加关注吞吐量,达到一个可控制的吞吐量 | 本身是Server级别多CPU机器上的默认GC方式,主要适合后台运算不太需要太多交互的任务 |
收集器 | 收集对象和算法 | 收集器类型 | 说明 | 适用场景 |
---|---|---|---|---|
Serial Old | 老年代,标记整理算法 | 单线程 | Client模式下虚拟机使用 | |
Parallel Old | 老年代,标记整理算法 | 并行的多线程收集器 | Parallel Scavenge收集器的老年代版本,为了配置Parallel Scavenge的面向吞吐量的特性而开发的对应组合 | 在注重吞吐量以及CPU资源敏感的场合采用 |
CMS | 老年代,标记清除算法 | 并行的多线程收集器 | 尽可能的缩短垃圾收集时用户线程停止时间;缺点在于,1.内存碎片,2.需要更多的CPU资源,3.浮动垃圾问题,需要更大的堆空间 | 重视服务的响应速度,系统停顿时间和用户体验的互联网或者B/S架构系统。互联网后端目前CMS是主流的垃圾回收器 |
G1 | 跨新生代和老年代;标记整理+化整为零 | 并行与并发收集器 | JDK1.7才正式引入,采用分区回收的思维,基本不牺牲吞吐量的前提下完成低停顿的内存回收;可预测的停顿是其最大的优势 |
【JVM】一文搞懂常见GC算法相关推荐
- 一文搞懂BPE分词算法
大家好,我是Xueliang,又和大家见面了. 我最近在打机器翻译的一个比赛,主要使用基于BERT的模型.在这其中,一个小的知识点引起了我的好奇,就是在将英语训练语料输入到BERT模型之前,需要对其进 ...
- 一文搞懂K-means聚类算法
一步步教你轻松学K-means聚类算法 阅读目录 目录 聚类 K-means(k均值)聚类算法 案例描述 从文件加载数据集 计算两个向量的欧氏距离 构建一个包含 K 个随机质心的集合 K-Means ...
- 一文搞懂模型量化算法
1,模型量化概述 1.1,模型量化优点 1.2,模型量化的方案 1.2.1,PTQ 理解 1.3,量化的分类 1.3.1,线性量化概述 2,量化算术 2.1,定点和浮点 2.2,量化浮点 2.2,量化 ...
- 一文搞懂Vue Diff算法
为什么需要diff算法? 对于一个容器(比如我们常用的#app)而言,它的内容一般有三种情况: 1.字符串类型,即是文本. 2.子节点数组,即含有一个或者多个子节点 3.null,即没有子节点 在vu ...
- 一文搞懂K近邻算法(KNN),附带多个实现案例
简介:本文作者为 CSDN 博客作者董安勇,江苏泰州人,现就读于昆明理工大学电子与通信工程专业硕士,目前主要学习机器学习,深度学习以及大数据,主要使用python.Java编程语言.平时喜欢看书,打篮 ...
- 一文搞懂k近邻(k-NN)算法(一)
原文链接 一文搞懂k近邻(k-NN)算法(一) 前几天和德川一起在学习会上讲解了k-NN算法,这里进行总结一下,力争用最 通俗的语言讲解以便更多同学的理解. 本文目录如下: 1.k近邻算法的基本概念, ...
- 一文搞懂 Raft 算法
一文搞懂Raft算法 正文 raft是工程上使用较为广泛的强一致性.去中心化.高可用的分布式协议.在这里强调了是在工程上,因为在学术理论界,最耀眼的还是大名鼎鼎的Paxos.但Paxos是:少数真正理 ...
- 一文搞懂HMM(隐马尔可夫模型)-Viterbi algorithm
***一文搞懂HMM(隐马尔可夫模型)*** 简单来说,熵是表示物质系统状态的一种度量,用它老表征系统的无序程度.熵越大,系统越无序,意味着系统结构和运动的不确定和无规则:反之,,熵越小,系统越有序, ...
- 网络知识扫盲,一文搞懂 DNS
在找工作面试的过程中,面试官非常喜欢考察基础知识,除了数据结构与算法之外,网络知识也是一个非常重要的考察对象. 而网络知识,通常是很抽象,不容易理解的,有很多同学就在这里裁了跟头.为了更好地通过面试, ...
最新文章
- 解决Chrome中UEditor插入图片的选择框加载过慢问题
- mysql commit 机制_1024MySQL事物提交机制
- linux shell 统计行数的8种方法
- Faster R-CNN论文及源码解读
- Java 数组 之 一维数组
- 逻辑斯蒂(logistic)回归深入理解、阐述与实现
- 移植gettimeofday
- HCIE-Security Day33:IPSec:深入学习ipsec ikev2、IKEV1和IKEV2比较
- ORACLE ORA-01000: 超出打开游标的最大数(解决及原因)
- 火狐浏览器不能看网页视频了的解决方法
- 数据挖掘之决策树与决策规则
- 为工业机器人的学生分享一款ABB公司的机器人仿真软件
- oracle expdp作业外表报错ORA-20011KUP-11024ORA-29913
- stm32wb55 flash
- ORAN C平面传输和基本功能
- Mysql分页Limt
- 自己搭建网站一个月多少钱?
- Python比较2个json数据是否相等
- antdv select下拉菜单自由扩展—添加输入框的使用问题
- 产品管理服务--数据库设计