首页 > 其他分享 >谈谈 JVM 垃圾回收机制

谈谈 JVM 垃圾回收机制

时间:2024-05-15 12:43:05浏览次数:29  
标签:收集器 对象 算法 回收 谈谈 垃圾 JVM 内存

前言


垃圾回收需要思考三件事情,哪些内存需要回收?什么时候回收?如何回收?


一、哪些内存需要回收


JVM 的内存区域中,程序计数器、虚拟机栈和本地方法栈的生命周期是随线程而生,随线程而灭的。这几个区域的内存分配和回收都具有确定性不需要过多考虑回收问题,当方法或线程结束时,内存自然就跟着回收了。


Java 堆和方法区是具有不确定性的,比如一个方法根据不同的条件执行可能需要的内存是不同的。只有处于运行期才能知道需要创建哪些对象,创建多少对象,这部分的内存分配和回收是动态的,垃圾回收所关注的就是这部分内存


二、对象何时会“死亡”


Java 堆中存放了几乎所有的对象实例,垃圾回收器在进行回收前,需要判断哪些对象“存活”,哪些对象“死亡”,“死亡”的对象才会被回收。


1. 引用计数法

给对象中添加一个引用计数器

  • 每当有一个地方引用它,计数器就加 1;
  • 当引用失效,计数器就减 1;
  • 任何时候计数器为 0 的对象就是不可能再被使用的。

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。

2. 可达性分析算法


通过一系列被称为GC Roots的根对象作为起始节点集,从这些节点开始,通过引用关系向下搜寻,搜寻走过的路径称为“引用链”,如果某个对象到GC Roots没有任何“引用链”相连,就说明该对象不可达,即可以被回收。


如下图,Object 5、Object 6、Object 7 虽有关联,但是到GC Roots是不可达的,因此会被判定“死亡”。

image.png


Java 中固定可作为GC Root对象的有:

  • 虚拟机栈的栈帧中的本地变量表中引用的对象,如,参数、局部变量、临时变量等。
  • 方法区中类静态属性引用的对象,如,类中的静态变量。
  • 方法区中常量引用的对象。
  • 本地方法栈中 JNI(也就是native方法)引用的对象。
  • Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象、常驻异常、系统类加载器等。
  • 被同步锁持有的对象。

除了这些固定的还有一些临时性加入的,有兴趣的可以看下《深入理解 Java 虚拟机》。

上述两种方法都需要了解引用,详细介绍见Java引用

3. 方法区的回收

不要求虚拟机在方法区进行垃圾回收,在 Java 堆中,尤其是在新生代中,常规进行一次垃圾收集通常可以回收70%至99%的内存空间,相比之下,在方法区进行回收的“性价比”较低。该区域的垃圾回收主要是两个部分,废弃的常量和不再使用的类

3.1 废弃常量

假如在字符串常量池中曾存在字符串 "java",如果当前没有任何字符串对象引用该字符串常量的话,就说明常量 "java" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"java" 就会被系统清理出常量池了。

3.2 不再被使用的类

类需要同时满足下面 3 个条件才能算是 “不在被使用的类” :

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

三、垃圾回收算法

这里整理的均为“追踪式垃圾回收“,也成为”间接垃圾回收“。

1. 分代回收

Java 堆的区域划分如下图,被分为新生代、老年代、永久代或元空间,具体划分为五大块区域,不同的 GC 会针对不同的区域进行垃圾回收。

image.png

GC类型一般有以下几大类:

分类 说明
Minor GC 也称“Young GC”,只针对新生代进行的垃圾回收。
Major GC 也称“Old GC”,只针对老年代进行的垃圾回收
Mixed GC 针对新生代和部分老年代进行垃圾回收,部分垃圾收集器才支持。
Full GC 针对整个Java堆和方法区进行的垃圾回收,耗时最久的GC

2. 标记-清除算法

最早出现的垃圾回收算法,分为“标记”、“清除”两个阶段。可以标记存活的对象,也可以标记要回收的对象。
该算法有两个缺点:

  1. 是执行效率不稳定,随着对象的增多,标记效率会越来越低;
  2. 内存空间产生很多碎拼,浪费空间,分配大对象时可能需要重新触发垃圾回收。

标记-清除算法执行过程如下图:

image.png

3. 标记-复制算法

简称复制算法,为了解决标记-清除算法面对大量可回收对象执行效率低的问题,就是将内存分成两个区域,每次只使用其中一个区域,当该区域内存满了之后,会将还存活的对象复制到另一个区域,然后将原区域直接清理掉
该算法有两个缺点:

  1. 对象存活率过多时,会影响复制的效率;
  2. 一半内存不使用,浪费了大量空间。

标记-复制算法执行过程如下图:

image.png

由于 Java 的新生代对象存活率不高,所以一般针对新生代的垃圾回收使用标记-复制算法


如下图,将新生代分为内存较大的Eden,和两块内存较小的Survivor。每次分配内存只使用Eden和其中一个Survivor,我们假设第一次分配内存是EdenSurvivor 0

  1. 发生 GC 时,将使用的EdenSurvivor 0中存活的对象一次性复制到Survivor 1中,然后清理掉EdenSurvivor 0内存。
  2. 这时分配的内存就变成了EdenSurvivor 1,周而复始。

注意:Survivor不足以容纳轻 GC 之后的对象时,就需要依赖老年代来进行内存分配了

image.png

4. 标记-整理算法

标记完存活对象以后,让所有存活对象都向内存空间的一端移动,然后在清理掉边界以外的内存。

标记-整理算法执行过程如下图:

image.png

这种涉及到了对象的移动,如果移动存活对象,尤其是老年代这种每次回收都有大量对象存活的区域,会耗时很多,并且对象移动操作会全部暂停用户应用程序才能进行,这样会产生停顿时间。

这种停顿被称为Stop The World

5. 标记-清除-整理算法

先使用标记-清除算法进行垃圾回收,暂时容忍内存碎片的存在,直到碎片过多影响对象分配时,在进行标记-整理算法进行回收,获得规整空间。

四、经典垃圾收集器


下图展示了 7 种经典垃圾收集器,若两两出现互连情况,则表明两者它们可以搭配使用

  • 单线程收集器 Serial、Serial old
  • 并行收集器 Par New、Parallel Scavenge、Parallel old
  • 并发收集器 CMS、G1

image.png

1. Serial 收集器

最基本、历史最早的垃圾收集器了。单线程的收集器,收集垃圾时,必须暂停其他所有工作线程,也就是必有停顿,使用复制算法

2. Par New 收集器

多线程并行版本的 Serial 收集器,收集垃圾时,必须暂停其他所有工作线程,也就是必有停顿,使用复制算法

3. Parallel Scavenge 收集器

类似 Par New 收集器,但关注点是达到一个可控的吞叶量,使用复制算法


吞吐量公式:



吞吐量示例:

代码运行 95 秒 , 垃圾收集器运行 5 秒 , 那么吞吐量就是 $\frac{95}{95 + 5} = 0.95$

4. Serial old 收集器

Serial 收集器的老年代版本,单线程收集器,使用标记-整理算法。它主要有两大用途:

  1. 在JDK1.5及以前的版本中与 Parallel Scavenge 收集器搭配使用;
  2. 作为CMS收集器的后备方案。

5. Parallel old 收集器

Parallel Scavenge收集器的老年代版本。使用多线程和标记-整理算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。

6. CMS 收集器

是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。


整个过程分为5个步骤:初始标记→并发标记→重新标记→并发清理→并发重置。

7. G1 收集器

基于标记整理算法实现,运作流程主要包括以下:初始标→并发标记→最终标记→筛选回收,不会产生空间碎片,可以精确地控制停顿,可以支持用户设置期望停顿时间。


不追求一次性将 Java 堆清理干净,只要垃圾收集的速度赶得上对象分配的速度即可。



参考:

[1] 周志明. 深入理解 Java 虚拟机(第3版).

标签:收集器,对象,算法,回收,谈谈,垃圾,JVM,内存
From: https://www.cnblogs.com/fuxing/p/18193599

相关文章

  • 谈谈仿真云应用和重要组成技术
    云仿真技术正在各个领域展现出强大的应用潜力,它通过将计算和仿真资源移至云端,为用户提供高效、灵活和可靠的仿真环境。无论是建筑、医疗、娱乐还是教育培训,云仿真都为各行业带来了前所未有的机遇和挑战,本文将深入探讨云仿真的基本概念以及云仿真平台适合的应用场景有哪些,通过对仿......
  • jvm垃圾回收及内存分配
    目录0.垃圾收集器的相关JVM参数1.java垃圾回收器种类2.串行回收器3.并行回收器3.1ParNew回收器3.2ParallelGC回收器3.3ParallelOldGC回收器3.4CMS回收器CMS主要工作步骤CMS说明3.5G1回收器3.5.1新生代GC3.5.2G1的并发标记周期3.5.3混合回收3.5.4G1日志解析0.垃圾收......
  • Oracle:谈谈service_names和dbms_service的一点问题
     Oracle:谈谈service_names和dbms_service的一点问题 services_name在在19C中官方明确提到已经弃用了,只保留兼容,且在未来版本中可能不再支持该初始化参数。传送门。其次,无论是在12C或者11G,官方都表明了OracleRAC或者OracleRestart环境下,不通过直接修改services_name参数,而......
  • 垃圾回收机制
    垃圾回收机制【一】什么是垃圾回收机制垃圾回收机制(GC机制)是python自带的机制专门用来回收变量值所占的内存空间【二】在python中的垃圾#每次书写Python代码都会创建很多变量名和变量值#但是有很多变量名和变量值用过一次就不用了#--->被称之为垃圾--->不会主......
  • JVM自定义加类加载器
    在JVM类加载器分类中提及JVM自带的加载器无法满足实际业务需求时,可以自定义加载器。那一般什么情况下需要自定义加载器呢?隔离加载类:模块隔离——把类加载到不同的应用选项中,比如Tomcat类加载器。修改类加载方式:平台提供了三类加载器除必须加载的类加载器,可以根据实际情况......
  • 谈谈 Spring 的过滤器和拦截器
    前言我们在进行Web应用开发时,时常需要对请求进行拦截或处理,故Spring为我们提供了过滤器和拦截器来应对这种情况。那么两者之间有什么不同呢?本文将详细讲解两者的区别和对应的使用场景。(本文的代码实现首先是基于SpringBoot,Spring的实现方式仅简单描述)1.过滤器1.1.......
  • 2391. 收集垃圾的最少总时间
    传送锚点:https://leetcode.cn/problems/minimum-amount-of-time-to-collect-garbage/description/给你一个下标从0开始的字符串数组garbage,其中garbage[i]表示第i个房子的垃圾集合。garbage[i]只包含字符'M','P'和'G',但可能包含多个相同字符,每个字符分别表示一单位......
  • 最近在写一个网页,想谈谈数据表的关系
    一对多影片(一)--剧集(多)影片表idurltitle1url1title12url2title23url3title3剧集表idmovie_idurl11url121url233url341url4在上面两个表中,可见一个影片可以有多个剧集,在表的设计中应该在多的一方设置一的一方......
  • JVM类加载器ClassLoader源码剖析
    在JVM类加载器分类中通过ClassLoader获取了不同类型的类加载器,它是如此之重要那么ClassLoader究竟为何物呢?通过源码分析(以jdk17示例):调试跟踪ClassLoader:......
  • jvm调优参数
    目录1.java进程命令行启动2.java调优参数3.说明4.垃圾回收算法5.复制算法过程6.垃圾对象的定义7.引用和可触及性的强度8.STW1.java进程命令行启动java[-options]main_class_name[args...]2.java调优参数JVM的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可......