什么是垃圾?

没有任何引用指向的对象,就是垃圾

如何找到垃圾?(2 种方法)

过程:先找到正在使用的对象,然后把没有正在使用的对象进行回收

1.引用数-Reference-Count



被引用数为 0 的即为垃圾,在 Java 领域,至少主流的 Java 虚拟机里面都没有选用引用计数算法来管理内存。原因是该算法不能回收循环引用,有缺陷,怎么办呢?根可达算法可以弥补这个缺陷(Root Searching)

2.根可达算法-Root Searching(这个必须要牢牢记住)

  • 如何理解根可达?

    算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到 GC Roots 间没有任何引用链相连,或者用图论的话来说就是从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的

  • GC Roots(根对象)都有哪些东西?

  1. 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
  2. 类的静态成员变量的引用:T.class 对静态变量初始化能够访问到的对象是根对象
  3. 常量池:一个 class 用到的其他的 class 对象叫做根对象
  4. 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)。具体过程如下:
  1. 在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区(以下简称 from);
  2. 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 分配

老年代

  • 老年代垃圾回收算法

    老年代一般使用“标记-清除”、“标记-整理”算法,因为老年代的对象存活率一般是比较高的,空间又比较大,拷贝起来并不划算,还不如采取就地收集的方式。
  • 对象是怎么进入老年代的?
  1. 提升(Promotion):年龄达到阈值则进入老年代

    如果对象够老,会通过“提升”进入老年代。关于对象老不老,是通过它的年龄(age)来判断的。每当发生一次 Minor GC,存活下来的对象年龄都会加 1。直到达到一定的阈值,就会把这些“老顽固”给提升到老年代。这些对象如果变的不可达,直到老年代发生 GC 的时候,才会被清理掉。

    这个阈值,可以通过参数‐XX:+MaxTenuringThreshold 进行配置,最大值是 15,因为它是用 4bit 存储的(所以网络上那些要把这个值调的很大的文章,是没有什么根据的)。

  2. 分配担保:Survivor 空间不够,直接分配到老年代空间

    每次存活的对象,都会放入其中一个幸存区,这个区域默认的比例是 10%。但是我们无法保证每次存活的对象都小于 10%,当 Survivor 空间不够,就需要依赖其他内存(指老年代)进行分配担保。这个时候,对象也会直接在老年代上分配

  3. 大对象直接在老年代分配

    超出某个大小的对象将直接在老年代分配。这个值是通过参数-XX:PretenureSizeThreshold进行配置的。默认为 0,意思是全部首选 Eden 区进行分配。

  4. 动态对象年龄判定

    有的垃圾回收算法,并不要求 age 必须达到 15 才能晋升到老年代,它会使用一些动态的计算方法。比如,如果幸存区中相同年龄对象大小的和,大于幸存区空间大小的一半,大于或等于 age 的对象将会直接进入老年代。

再来看一眼一个对象的分配逻辑



ZGC 之前都是分代算法

JVM 内存分代模型

除了 ZGC,Epsilon , Shenandiah 之外,都是使用逻辑分代模型

G1 是逻辑分代,物理不分代,除此之外不仅逻辑分代,而且物理分代

YGC:年轻代空间耗尽时触发

FullGC:在老年代无法继续分配空间的时候触发,新生代,老年代同时进行回收

逃逸分析

https://www.cnblogs.com/javastack/p/12923778.html

逃逸:某个变量只在某个方法内部有效叫做无逃逸,如果不止在某个方法内有效,则是逃逸的。类的成员变量是逃逸的,方法变量则是无逃逸的

  • 查看 JVM 参数
java -XX:+PrintCommandLineFlags -version

垃圾收集器跟内存大小的关系

  1. Serial 几十兆
  2. PS 上百兆 - 几个G
  3. CMS - 4~6G以下的堆内存
  4. G1 - 6G以上的
  5. 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 的缺点

  1. 产生内存碎片,如果老年代空间不足,但又有新的对象需要放入老年代,这个时候会触发 Serial old 垃圾回收器使用单线程在那标记清除,如果老年代内存空间很大,那么相对而言效率很低很低,一次停顿时间可能就会很长很长,如何解决呢?
  2. 产生浮动垃圾
  3. 可能会导致FULL GC,这是非常严重的!!!!!

    full gc的概念:整体内存需要回收叫做full gc

CMS 的优点:

并发标记回收,停顿时间少

5.什么时候触发 FGC?

内存不够用的时候会触发,默认会使用PS+PO,Full GC 清理old区,设置下面这个参数,降低阈值尽量避免full gc,有的人设置为80%,有的人设置为70%,因环境而异

--XX:CMSinitiatingOccupancyFraction

欢迎添加我的个人微信一起探讨

JVM垃圾回收的基础知识的更多相关文章

  1. 关于GC(中):Java垃圾回收相关基础知识

    Java内存模型 (图源: 深入理解JVM-内存模型(jmm)和GC) 区域名 英文名 访问权限 作用 备注 程序计数器 Program Counter Register 线程隔离 标记待取的下一条执 ...

  2. JVM垃圾回收重要理论剖析【纯理论】

    JVM学习到这里,终于到学习最兴奋的地方了---垃圾回收,在学习它之前还得对JVM垃圾回收相关理论知识进行了解,然后再通过实践来加深对理论的理解,下面直接开始了解相关的理论: JVM运行时内存数据区域 ...

  3. JVM基础系列第8讲:JVM 垃圾回收机制

    在第 6 讲中我们说到 Java 虚拟机的内存结构,提到了这部分的规范其实是由<Java 虚拟机规范>指定的,每个 Java 虚拟机可能都有不同的实现.其实涉及到 Java 虚拟机的内存, ...

  4. 一网打尽JVM垃圾回收知识体系

    垃圾回收的区域 堆:Java 中绝大多数的对象都存放在堆中,是垃圾回收的重点 方法区:此中的 GC 效率较低,不是重点 由于虚拟机栈的生命周期和线程一致,因此不需要 GC 对象判活 在垃圾收集器对堆进 ...

  5. 垃圾回收机制GC知识再总结兼谈如何用好GC(转)

    作者:Jeff Wong 出处:http://jeffwongishandsome.cnblogs.com/ 本文版权归作者和博客园共有,欢迎围观转载.转载时请您务必在文章明显位置给出原文链接,谢谢您 ...

  6. Java GC(垃圾回收)机制知识总结

    目录 Java GC系列 Java关键术语 Java HotSpot 虚拟机 JVM体系结构 Java堆内存 启动Java垃圾回收 Java垃圾回收过程 垃圾回收中实例的终结 对象什么时候符合垃圾回收 ...

  7. JVM内存管理和JVM垃圾回收机制

    JVM内存管理和JVM垃圾回收机制(1) 这里向大家描述一下JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采 ...

  8. jvm - 垃圾回收

    jvm - 垃圾回收 注意 : 本系列文章为学习系列,部分内容会取自相关书籍或者网络资源,在文章中间和末尾处会有标注 垃圾回收的意义 它使得java程序员不再时时刻刻的关注内存管理方面的工作. 垃圾回 ...

  9. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

随机推荐

  1. Burst

    Unity Burst 用户指南 https://blog.csdn.net/alph258/article/details/83997917 Burst https://unity3d.com/cn ...

  2. LightOJ - 1214-Large Division(c++取模 + java的两种写法)

    Given two integers, a and b, you should check whether a is divisible by b or not. We know that an in ...

  3. MySQL索引凭什么能让查询效率提高这么多?

    点赞再看,养成习惯,微信搜一搜[三太子敖丙]关注这个喜欢写情怀的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系 ...

  4. 非构造函数方式创建DbContext实例的方法

    using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Design;using Microsoft.Entit ...

  5. [oracle/sql]写SQL从学生考试成绩三表中选出五门分综合超过720的尖子

    任务:有学生,科目,考分三张表,需要从中筛选出五门考分总和超过720的学生. 科目表最简单只有五条记录: CREATE TABLE tb_course ( id NUMBER not null pri ...

  6. go http请求流程分析

    前言 golang作为常驻进程, 请求第三方服务或者资源(http, mysql, redis等)完毕后, 需要手动关闭连接, 否则连接会一直存在; 连接池是用来管理连接的, 请求之前从连接池里获取连 ...

  7. 大量数据也不在话下,Spring Batch并行处理四种模式初探

    1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! Spring相关文章:Springboot-Cloud 前面写了一篇文章<通过例子讲解Spring Batch入门,优 ...

  8. 痞子衡嵌入式:IAR在线调试时设不同复位类型可能会导致i.MXRT下调试现象不一致(J-Link / CMSIS-DAP)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR在线调试时设不同复位类型可能会导致i.MXRT下调试现象不一致. 做Cortex-M内核MCU嵌入式软件开发,可用的集成开发环境( ...

  9. 第15课 - make的隐式规则(上)

    第15课 - make的隐式规则(上) 1. 问题 如果把同一个目标的命令拆分的写到不同地方,会发生什么? 执行make all 这个实验表明了:如果同一个目标的命令拆分的写到不同地方,那么 make ...

  10. [LeetCode]215. 数组中的第K个最大元素(堆)

    题目 在未排序的数组中找到第 k 个最大的元素.请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素. 示例 1: 输入: [3,2,1,5,6,4] 和 k = 2 输出 ...