JVM垃圾回收的基础知识
什么是垃圾?
没有任何引用指向的对象,就是垃圾
如何找到垃圾?(2 种方法)
过程:先找到正在使用的对象,然后把没有正在使用的对象进行回收
1.引用数-Reference-Count
被引用数为 0 的即为垃圾,在 Java 领域,至少主流的 Java 虚拟机里面都没有选用引用计数算法来管理内存。原因是该算法不能回收循环引用,有缺陷,怎么办呢?根可达算法可以弥补这个缺陷(Root Searching)
2.根可达算法-Root Searching(这个必须要牢牢记住)
如何理解根可达?
算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到 GC Roots 间没有任何引用链相连,或者用图论的话来说就是从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的GC Roots(根对象)都有哪些东西?
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
- 类的静态成员变量的引用:T.class 对静态变量初始化能够访问到的对象是根对象
- 常量池:一个 class 用到的其他的 class 对象叫做根对象
- JNI 指针:本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象。
- 什么是根对象?
一个线程启动时需要的对象就是根对象
引用数和根可达算法都提到了“引用”,Java中有哪些引用类型?
强引用(不被回收)
当内存空间不足,系统撑不住了,JVM 就会抛出OutOfMemoryError 错误。即使程序会异常终止,这种对象也不会被回收。这种引用属于最普通最强硬的一种存在,只有在和 GC Roots 断绝关系时,才会被消灭掉软引用(内存够不回收,不够再回收)
在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。弱引用(只要发生GC,就会被回收)
当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。弱引用拥有更短的生命周期虚引用
这是一种形同虚设的引用,在现实场景中用的不是很多
如何清除垃圾?(三种算法,必须背过)
标记清除-Mark-Sweep
先标记,再清除,存活对象比较多的时候,清除的效率比较高,但是需要扫描两遍(如何理解 2 遍?第一次扫描先找到那些有用的,第二次扫描再找到那些没用的并清除),效率偏低,很容易产生碎片拷贝-Copying
拷贝也称为“半区复制”(Semispace Copying)的垃圾收集算法,它将可用 内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。如果内存中多数对象都是存 活的,这种算法将会产生大量的内存间复制的开销,但对于多数对象都是可回收的情况,算法需要复 制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有 空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。这样实现简单,运行高效,不过其缺陷 也显而易见,这种复制回收算法的代价是将可用内存缩小为了原来的一半,空间浪费太多了一 点,但是效率是最高的
优点:适用于存活对象较少的情况,而且只扫描一次,效率比较高,且没有碎片
缺点:空间浪费,移动复制对象,需要调整对象引用标记压缩为紧凑-Mark-Compact
清理垃圾的过程中,把存活的对象全部扔到前面的位置,然后大块的内存就出来了
优点:不会产生碎片,不会产生内存减半的问题
缺点:也需要扫描两次,第一次标记有用对象,第二次移动对象,如果移动的过程是多线程的效率就会低很多
堆内存逻辑分区
研究表明,大部分对象,可以分为 2 类
- 大部分对象的生命周期都很短;
- 其他对象则很可能会存活很长时间
大部分死的快,其他的活的长。这个假设称之为弱代假设。
现在的垃圾回收器,都会在物理上或者逻辑上,把这两类对象进行区分。我们把死的快的对象所占的区域,叫作年轻代(Young generation)。把其他活的长的对象所占的区域,叫作老年代(Old generation)
年轻代
- 年轻代采用的算法
采用复制算法(Copying)。因为年轻代发生 GC 后,只会有非常少的对象存活,复制这部分对象是非常高效的。
复制算法会造成一定的空间浪费,所以年轻代中间也会分很多区域:年轻代分为:一个伊甸园空间(Eden ),两个幸存者空间(Survivor )
- 年轻代的 GC 过程
一个对象产生之后首先在栈上分配,如果栈上空间不够,会进入伊甸区(Eden),当年轻代中的 Eden 区分配满的时候,就会触发年轻代的 GC(Minor GC)。具体过程如下:
- 在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区(以下简称 from);
- Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理。存活的对象会被复制到 to 区;接下来,只需要清空 from 区就可以了。
所以在这个过程中,总会有一个 Survivor 分区是空置的。Eden、from、to 的默认比例是 8:1:1,所以只会造成 10% 的空间浪费。这个比例,是由参数-XX:SurvivorRatio进行配置的(默认为 8)。
- 一个对象的分配逻辑
1.栈上分配
栈上分配对象比在堆上分配要快很多
2.TLAB 上分配
TLAB 的全称是 Thread Local Allocation Buffer,JVM 默认给每个线程在 Eden 区中开辟一个 buffer 区域,默认占用 1%Eden 的空间,用来加速对象分配,这个道理和 Java 语言中的 ThreadLocal 类似,避免了对公共区的操作,以及一些锁竞争。
3.Eden 分配
老年代
- 老年代垃圾回收算法
老年代一般使用“标记-清除”、“标记-整理”算法,因为老年代的对象存活率一般是比较高的,空间又比较大,拷贝起来并不划算,还不如采取就地收集的方式。 - 对象是怎么进入老年代的?
提升(Promotion):年龄达到阈值则进入老年代
如果对象够老,会通过“提升”进入老年代。关于对象老不老,是通过它的年龄(age)来判断的。每当发生一次 Minor GC,存活下来的对象年龄都会加 1。直到达到一定的阈值,就会把这些“老顽固”给提升到老年代。这些对象如果变的不可达,直到老年代发生 GC 的时候,才会被清理掉。
这个阈值,可以通过参数‐XX:+MaxTenuringThreshold 进行配置,最大值是 15,因为它是用 4bit 存储的(所以网络上那些要把这个值调的很大的文章,是没有什么根据的)。分配担保:Survivor 空间不够,直接分配到老年代空间
每次存活的对象,都会放入其中一个幸存区,这个区域默认的比例是 10%。但是我们无法保证每次存活的对象都小于 10%,当 Survivor 空间不够,就需要依赖其他内存(指老年代)进行分配担保。这个时候,对象也会直接在老年代上分配大对象直接在老年代分配
超出某个大小的对象将直接在老年代分配。这个值是通过参数-XX:PretenureSizeThreshold进行配置的。默认为 0,意思是全部首选 Eden 区进行分配。动态对象年龄判定
有的垃圾回收算法,并不要求 age 必须达到 15 才能晋升到老年代,它会使用一些动态的计算方法。比如,如果幸存区中相同年龄对象大小的和,大于幸存区空间大小的一半,大于或等于 age 的对象将会直接进入老年代。
再来看一眼一个对象的分配逻辑
ZGC 之前都是分代算法
JVM 内存分代模型
除了 ZGC,Epsilon , Shenandiah 之外,都是使用逻辑分代模型
G1 是逻辑分代,物理不分代,除此之外不仅逻辑分代,而且物理分代
YGC:年轻代空间耗尽时触发
FullGC:在老年代无法继续分配空间的时候触发,新生代,老年代同时进行回收
逃逸分析
https://www.cnblogs.com/javastack/p/12923778.html
逃逸:某个变量只在某个方法内部有效叫做无逃逸,如果不止在某个方法内有效,则是逃逸的。类的成员变量是逃逸的,方法变量则是无逃逸的
- 查看 JVM 参数
java -XX:+PrintCommandLineFlags -version
垃圾收集器跟内存大小的关系
- Serial 几十兆
- PS 上百兆 - 几个G
- CMS - 4~6G以下的堆内存
- G1 - 6G以上的
- ZGC - 4T - 16T(JDK13)
常见垃圾回收器组合参数设定
-XX:+UseSerialGC 年轻代和老年代都用串行收集器
-XX:+UseParNewGC 年轻代使用ParNew,老年代使用 Serial Old
-XX:+UseParallelGC 年轻代使用Paraller Scavenge,老年代使用Serial Old
-XX:+UseParallelOldGC 新生代Paraller Scavenge,老年代使用Paraller Old
-XX:+UseConcMarkSweepGC,表示年轻代使用ParNew,老年代的用CMS + Serial Old
-XX:+UseG1GC 使用G1垃圾回收器
-XX:+UseZGC 使用ZGC垃圾回收器
STW
如果在垃圾回收的时候(不管是标记还是整理复制),又有新的对象进入怎么办?
为了保证程序不会乱套,最好的办法就是暂停用户的一切线程。也就是在这段时间,你是不能 new 对象的,只能等待。表现在 JVM 上就是短暂的卡顿,什么都干不了。这个头疼的现象,就叫作 Stop the world。简称 STW。
标记阶段,大多数是要 STW 的。如果不暂停用户进程,在标记对象的时候,有可能有其他用户线程会产生一些新的对象和引用,造成混乱。
现在的垃圾回收器,都会尽量去减少这个过程。但即使是最先进的 ZGC,也会有短暂的 STW过程。我们要做的就是在现有基础设施上,尽量减少 GC 停顿。
1. Serial(回收时会停顿,现在用的很少)
单线程清理垃圾,(STW)Stop The World
其中有一个 safe point 的概念需要注意:线程并不是立马就停下来,而是找一个安全点停止
单机 CPU 效率最高,内存比较小的时候可以接受这种垃圾回收器,内存越大,垃圾回收的时间越长,因此这种垃圾回收器在 server 端使用的越来越少。通常使用在客户端上
2.Serial Old 组合
单线程在老年代回收垃圾
3.ParallelScavenge + Parallel Old 组合(简称 PS+PO 还有很多公司在使用,如果不设置,就是这么一个组合)
PS 使用复制算法清除垃圾
PO 使用标记整理算法清除垃圾
多线程清理垃圾
4.ParNew(Parallel New)+ CMS 组合
ParNew 是 ParallelScavenge 的一个增强变种,为了和 CMS 搭配使用
(STW)Stop The World
5.CMS(非常重要!!!)
老年代空间不够,才会触发 CMS,全称:Concurrent mark sweep 即并发清理垃圾。里程碑式的诞生,由于无法忍受 STW,开启了并发回收,(垃圾回收线程和工作线程同时进行),但是毛病很多,目前没有任何一个 JDK 版本默认是 CMS
- 初始标记(intiial mark):STW,只标记GC Roots对象所以耗时很少
- 并发标记(concurrrent mark):单线程的话耗时较多,因此开启了并发标记,缩短标记时间
- 重新标记(remark):STW,由于并发标记期间仍然会产生新的垃圾,因此还有重新标记,由于新产生的垃圾并不多,但耗时很少
- 并发清理(concurrent sweep):多线程,清理过程中仍然会有垃圾的产生,叫做浮动垃圾,这些浮动垃圾只能等 CMS 下一次垃圾回收的时候才会再次被清理
CMS 的缺点
- 产生内存碎片,如果老年代空间不足,但又有新的对象需要放入老年代,这个时候会触发 Serial old 垃圾回收器使用单线程在那标记清除,如果老年代内存空间很大,那么相对而言效率很低很低,一次停顿时间可能就会很长很长,如何解决呢?
- 产生浮动垃圾
- 可能会导致FULL GC,这是非常严重的!!!!!
full gc的概念:整体内存需要回收叫做full gc
CMS 的优点:
并发标记回收,停顿时间少
5.什么时候触发 FGC?
内存不够用的时候会触发,默认会使用PS+PO,Full GC 清理old区,设置下面这个参数,降低阈值尽量避免full gc,有的人设置为80%,有的人设置为70%,因环境而异
--XX:CMSinitiatingOccupancyFraction
欢迎添加我的个人微信一起探讨
JVM垃圾回收的基础知识的更多相关文章
- 关于GC(中):Java垃圾回收相关基础知识
Java内存模型 (图源: 深入理解JVM-内存模型(jmm)和GC) 区域名 英文名 访问权限 作用 备注 程序计数器 Program Counter Register 线程隔离 标记待取的下一条执 ...
- JVM垃圾回收重要理论剖析【纯理论】
JVM学习到这里,终于到学习最兴奋的地方了---垃圾回收,在学习它之前还得对JVM垃圾回收相关理论知识进行了解,然后再通过实践来加深对理论的理解,下面直接开始了解相关的理论: JVM运行时内存数据区域 ...
- JVM基础系列第8讲:JVM 垃圾回收机制
在第 6 讲中我们说到 Java 虚拟机的内存结构,提到了这部分的规范其实是由<Java 虚拟机规范>指定的,每个 Java 虚拟机可能都有不同的实现.其实涉及到 Java 虚拟机的内存, ...
- 一网打尽JVM垃圾回收知识体系
垃圾回收的区域 堆:Java 中绝大多数的对象都存放在堆中,是垃圾回收的重点 方法区:此中的 GC 效率较低,不是重点 由于虚拟机栈的生命周期和线程一致,因此不需要 GC 对象判活 在垃圾收集器对堆进 ...
- 垃圾回收机制GC知识再总结兼谈如何用好GC(转)
作者:Jeff Wong 出处:http://jeffwongishandsome.cnblogs.com/ 本文版权归作者和博客园共有,欢迎围观转载.转载时请您务必在文章明显位置给出原文链接,谢谢您 ...
- Java GC(垃圾回收)机制知识总结
目录 Java GC系列 Java关键术语 Java HotSpot 虚拟机 JVM体系结构 Java堆内存 启动Java垃圾回收 Java垃圾回收过程 垃圾回收中实例的终结 对象什么时候符合垃圾回收 ...
- JVM内存管理和JVM垃圾回收机制
JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采 ...
- jvm - 垃圾回收
jvm - 垃圾回收 注意 : 本系列文章为学习系列,部分内容会取自相关书籍或者网络资源,在文章中间和末尾处会有标注 垃圾回收的意义 它使得java程序员不再时时刻刻的关注内存管理方面的工作. 垃圾回 ...
- Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法
在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...
随机推荐
- The Data Warehouse Toolkit 阅读笔记
前言 这篇笔记的主要内容来至于The Data Warehouse Toolkit,该书可以称为数仓建模的圣经 什么是星型模型 以一个业务实时为主表.比如一笔订单就是一个业务事实.订单有商品的SKU信 ...
- 跨平台C# UI库
https://github.com/AvaloniaUI/Avalonia https://www.cnblogs.com/leolion/p/7144896.html https://github ...
- 在Mac上打开多个Unity实例
alias koa_unity="open -n /Applications/Unity\ 5.2.2/Unity.app" alias rob_unity="open ...
- Roads in the North (树的直径)
Building and maintaining roads among communities in the far North is an expensive business. With thi ...
- 11 vue 自定义全局方法
//global.js// 定义vue 全局方 // 定义vue 全局方法 建议自定义的全局方法加_ 以示区分 export default { install(Vue, options = ...
- Spring IoC 到底是什么
前言 「上一篇文章」我们对 Spring 有了初步的认识,而 Spring 全家桶中几乎所有组件都是依赖于 IoC 的. 刚开始听到 IoC,会觉得特别高大上,但其实掰开了很简单. 跟着我的脚步,一文 ...
- Kubernetes的资源控制器和Service(四)
一.定义和分类 1,定义 k8s 中内建了很多控制器(controller ),这些相当于一个状态机,用来控制 Pod 的具体状态和行为. 2,类型 ReplicationController.Rep ...
- 快速生成网络mp4视频缩略图技术
背景 由于网络原因,在下载视频之前我们往往会希望能够先生成一些视频的缩略图,大致浏览视频内容,再确定是否应花时间下载.如何能够快速得到视频多个帧的缩略图的同时尽量少的下载视频的内容,是一个值得研究的问 ...
- 使用wireshark分析MQTT协议
网络上搜索到两种用wireshark工具分析MQTT协议的方法,都是使用wireshark插件,一种是Wireshark Generic Dissector:另一种是使用lua脚本插件(推荐使用这种方 ...
- NSOperation类
NSOperation 抽象类 NSOperation 是一个"抽象类",不能直接使用 抽象类的用处是定义子类共有的属性和方法 在苹果的头文件中,有些抽象类和子类的定义是在同一个头 ...