【JVM基础篇】Java的四种垃圾回收算法介绍

文章目录

  • 垃圾回收算法
    • 垃圾回收算法的历史和分类
    • 垃圾回收算法的评价标准
    • 标记清除算法
      • 优缺点
    • 复制算法
      • 优缺点
    • 标记整理算法(标记压缩算法)
      • 优缺点
    • 分代垃圾回收算法(常用)
      • JVM参数设置
      • 使用Arthas查看内存分区
      • 垃圾回收执行流程
      • 分代GC算法内存为什么分年轻代、老年代
    • 文章说明

垃圾回收算法

Java是如何实现垃圾回收的呢?简单来说,垃圾回收算法要做的有两件事:

  1. 找到内存中存活的对象
  2. 释放不再存活对象的内存,使得程序能再次利用这部分空间

在这里插入图片描述

垃圾回收算法的历史和分类

  • 1960年John McCarthy发布了第一个GC算法:标记-清除算法。
  • 1963年Marvin L. Minsky 发布了复制算法。

本质上后续所有的垃圾回收算法,都是在上述两种算法的基础上优化而来。

在这里插入图片描述

垃圾回收算法的评价标准

Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为Stop The World简称STW,如果STW时间过长(系统假死)则会影响用户的使用。

如下图,用户代码执行和垃圾回收执行让用户线程停止执行(STW)是交替执行的。

在这里插入图片描述

交替执行过程可以通过如下代码验证:

package chapter04.gc;import lombok.SneakyThrows;import java.util.LinkedList;
import java.util.List;/*** STW测试*/
public class StopWorldTest {public static void main(String[] args) {new PrintThread().start();new ObjectThread().start();}
}/*** 打印线程*/
class PrintThread extends Thread{@SneakyThrows@Overridepublic void run() {//记录开始时间long last = System.currentTimeMillis();while(true){long now = System.currentTimeMillis();// 如果不是垃圾回收影响,这里每次都应该是输出100System.out.println(now - last);last = now;Thread.sleep(100);}}
}/*** 创建对象线程*/
class ObjectThread extends Thread{@SneakyThrows@Overridepublic void run() {List<byte[]> bytes = new LinkedList<>();while(true){// 最多存放8g,然后删除强引用,垃圾回收时释放8gif(bytes.size() >= 80){// 清空集合,强引用去除,垃圾回收器就会去回收对象bytes.clear();}bytes.add(new byte[1024 * 1024 * 100]);Thread.sleep(10);}}
}

代码运行之前,设置如下JVM参数

在这里插入图片描述

在这里插入图片描述

所以判断GC算法是否优秀,可以从三个方面来考虑

  1. 吞吐量

吞吐量指的是 CPU 用于执行用户代码的时间与 CPU 总执行时间的比值,即吞吐量 = 执行用户代码时间 /(执行用户代码时间 + GC时间)。吞吐量数值越高,垃圾回收的效率就越高

在这里插入图片描述

  1. 最大暂停时间

最大暂停时间指的是所有在垃圾回收过程中的STW时间最大值。比如如下的图中,黄色部分的STW就是最大暂停时间,显而易见上面的图比下面的图拥有更少的最大暂停时间。最大暂停时间越短,用户使用系统时受到的影响就越短。

在这里插入图片描述

  1. 堆使用效率

不同垃圾回收算法,对堆内存的使用方式是不同的。比如标记清除算法,可以使用完整的堆内存。而复制算法会将堆内存一分为二,每次只能使用一半内存。从堆使用效率上来说,标记清除算法要优于复制算法。

在这里插入图片描述

上述三种评价标准:堆使用效率、吞吐量,以及最大暂停时间不可兼得

一般来说,堆内存越大,回收对象就越多,最大暂停时间就越长。想要减少最大暂停时间,就要减少堆内存,少量多次,因为每次清理有一些准备工作,因此垃圾回收总时间会上升,吞吐量会降低。

没有一个垃圾回收算法能兼顾上述三点评价标准,所以不同的垃圾回收算法它的侧重点是不同的,适用于不同的应用场景(即垃圾回收算法没有好与坏,只有是否适合

  • 秒杀场景,购买只有很少的时间,最大暂停时间越短越好
  • 有的场景,程序就在后台处理数据,暂停时间长一点无所谓,目标是吞吐量高一点

标记清除算法

标记清除算法的核心思想分为两个阶段:

  1. 标记阶段,将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出所有存活对象。
  2. 清除阶段,从内存中删除没有被标记也就是非存活对象

第一个阶段,从GC Root对象开始扫描,将对象A、B、C在引用链上的对象标记出来:

在这里插入图片描述

第二个阶段,将没有标记的对象清理掉,所以对象D就被清理掉了。

在这里插入图片描述

优缺点

优点:实现简单,只需要在第一阶段给每个对象维护标志位(在引用链上,标记为1),第二阶段删除标记值为0的对象即可。

缺点

  1. 碎片化问题:由于内存是连续的,所以在对象被删除之后,内存中会出现很多细小的可用内存单元。如果我们需要的是一个比较大的空间,很有可能这些内存单元的大小过小无法进行分配。如下图,红色部分已经被清理掉了,总共回收了9个字节,但是每个都是一个小碎片,无法为5个字节的对象分配空间。

在这里插入图片描述

  1. 分配速度慢。由于内存碎片的存在,需要维护一个空闲链表,极有可能发生每次需要遍历到链表的最后才能获得合适的内存空间。我们需要用一个链表来维护,哪些空间可以分配对象,很有可能需要遍历这个链表到最后,才能发现这块空间足够我们去创建一个对象。如下图,遍历到最后才发现有足够的空间分配3个字节的对象了。如果链表很长,遍历也会花费较长的时间。

在这里插入图片描述

复制算法

复制算法的核心思想是:

  1. 准备两块空间From空间和To空间,每次在对象分配阶段,只能使用其中一块空间(From空间)。

对象A首先分配在From空间:

在这里插入图片描述

  1. 在垃圾回收GC阶段,将From中的存活对象复制到To空间。

在垃圾回收阶段,如果对象A存活,就将其复制到To空间。然后将From空间直接清空。

在这里插入图片描述

  1. 将两块空间的From和To名字互换,下次依然在From空间上创建对象。

在这里插入图片描述

完整的复制算法的例子:

1、将堆内存分割成两块From空间 To空间,对象分配阶段,创建对象。

在这里插入图片描述

2、GC阶段开始,将GC Root搬运到To空间

在这里插入图片描述

3、将GC Root关联的对象,搬运到To空间

在这里插入图片描述

4、清理From空间,并把名称互换

在这里插入图片描述

优缺点

优点

  • 吞吐量高,复制算法只需要遍历一次存活对象复制到To空间即可,比标记-整理算法少了一次遍历的过程,因而性能较好;但是性能不如标记-清除算法,因为标记清除算法不需要进行对象的移动
  • 不会发生碎片化,复制算法在复制之后就会将对象按顺序放入To空间中,所以对象以外的区域都是可用空间,不存在碎片化内存空间。

缺点

  • 内存使用效率低,每次只能让一半的内存空间来给创建对象使用。

标记整理算法(标记压缩算法)

标记整理算法是对标记清理算法中容易产生内存碎片问题的一种解决方案

核心思想分为两个阶段:

  1. 标记阶段,将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出所有存活对象。
  2. 整理阶段,将存活对象移动到堆的一端。清理掉存活对象的内存空间。

在这里插入图片描述

优缺点

优点:

  • 内存使用效率高,整个堆内存都可以使用,不像复制算法只能使用半个堆内存
  • 不会发生碎片化,在整理阶段可以将对象往内存的一侧进行移动,剩下的空间都是可以分配对象的有效空间

缺点:

  • 整理阶段的效率不高,需要遍历多次对象,还需要移动对象。整理算法有很多种,比如Lisp2整理算法需要对整个堆中的对象搜索3次,整体性能不佳。可以通过Two-Finger、表格算法、ImmixGC等高效的整理算法优化此阶段的性能。

分代垃圾回收算法(常用)

现代优秀的垃圾回收算法,会将上述描述的垃圾回收算法组合进行使用,其中应用最广的就是分代垃圾回收算法(Generational GC)。分代垃圾回收将整个内存区域划分为两块大区:年轻代、老年代:

在这里插入图片描述

  • Eden区:对象刚被创建出来的时候放到的地方
  • 幸存者区-S0、幸存者区-S1:用来实现复制算法

可以通过arthas来验证下内存划分的情况:

  • 在JDK8中,添加-XX:+UseSerialGC参数使用分代回收的垃圾回收器,运行程序。
  • 在arthas中使用memory命令查看内存,显示出三个区域的内存情况。

在这里插入图片描述

  • Eden + survivor 这两块区域组成了年轻代。
  • tenured_gen指的是晋升区域,其实就是老年代。

JVM参数设置

可以设置的虚拟机参数如下

参数名参数含义示例
-Xms设置堆的最小和初始大小,必须是1024倍数且大于1MB比如初始大小6MB的写法: -Xms6291456 -Xms6144k -Xms6m
-Xmx设置最大堆的大小,必须是1024倍数且大于2MB比如最大堆80 MB的写法: -Xmx83886080 -Xmx81920k -Xmx80m
-Xmn新生代的大小新生代256 MB的写法: -Xmn256m -Xmn262144k -Xmn268435456
-XX:SurvivorRatio伊甸园区和幸存区的比例,默认为8:如新生代有1g内存,则伊甸园区800MB,S0和S1各100MB比例调整为4的写法:-XX:SurvivorRatio=4
-XX:+PrintGCDetailsverbose:gc打印GC日志

老年代大小不需要设置,因为新生代设置完之后,老年代的大小就确定了(总的堆内存-新生代内存)

:如果使用其他版本的JDK,或者使用其他回收器,上面的部分参数可能就不会生效

使用Arthas查看内存分区

代码:

package chapter04.gc;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** 垃圾回收器案例1*/
//-XX:+UseSerialGC  -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3  -XX:+PrintGCDetails
public class GcDemo0 {public static void main(String[] args) throws IOException {List<Object> list = new ArrayList<>();int count = 0;while (true){System.in.read();System.out.println(++count);//每次添加1m的数据list.add(new byte[1024 * 1024 * 1]);}}
}

使用arthas的memory展示出来的效果:

在这里插入图片描述

heap展示的是可用堆。

垃圾回收执行流程

1、分代回收时,创建出来的对象,首先会被放入Eden伊甸园区。

在这里插入图片描述

2、随着对象在Eden区越来越多,如果Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,称为Minor GC或者Young GC。Minor GC会把需要eden中和From需要回收的对象回收,把没有回收的对象放入To区(算法使用的是复制算法)。Minor GC结束之后**,**Eden区会被清空,后面创建的对象又可以放到Eden区。

在这里插入图片描述

3、接下来,S0会变成To区,S1变成From区。当eden区满时再往里放入对象,依然会发生Minor GC。

在这里插入图片描述

此时会回收eden区和S1(from)中的对象,并把eden和from区中存活的对象放入S0。

注意:每次Minor GC中都会为对象记录他的年龄,初始值为0,每次GC完加1。

在这里插入图片描述

4、如果Minor GC后对象的年龄达到阈值(最大15,默认值和垃圾回收器有关),对象就会被晋升至老年代

在这里插入图片描述

在这里插入图片描述

5、当老年代中空间不足,无法放入新的对象时,先尝试minor gc(为啥?**因为young满了之后,部分对象年龄没有到15,也被放在了老年区,**minor gc可以清理young区来放新对象)。如果空间还是不足,就会触发Full GC(停顿时间较长),Full GC会对整个堆进行垃圾回收。如果Full GC依然无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。

在这里插入图片描述

下图中的程序为什么会出现OutOfMemory?

在这里插入图片描述

从上图可以看到,Full GC无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。

【测试代码】

//-XX:+UseSerialGC  -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3  -XX:+PrintGCDetails
public class GcDemo0 {public static void main(String[] args) throws IOException {List<Object> list = new ArrayList<>();int count = 0;while (true){System.in.read();System.out.println(++count);//每次添加1m的数据list.add(new byte[1024 * 1024 * 1]);}}
}

结果如下:

在这里插入图片描述

老年代已经满了,而且垃圾回收无法回收掉对象,如果还想往里面放就发生了OutOfMemoryError

在这里插入图片描述

分代GC算法内存为什么分年轻代、老年代

为什么分代GC算法要把堆分成年轻代和老年代?首先我们要知道堆内存中对象的特性:

  • 系统中的大部分对象,都是创建出来之后很快就不再使用可以被回收,比如用户获取订单数据,订单数据返回给用户之后就可以释放了。
  • 老年代中会存放长期存活的对象,比如Spring的大部分bean对象,在程序启动之后就不会被回收了。
  • 在虚拟机的默认设置中,新生代大小要远小于老年代的大小。

分代GC算法将堆分成年轻代和老年代主要原因有

  • 可以通过调整年轻代和老年代的比例来适应不同类型的应用程序,提高内存的利用率和性能。
  • 新生代和老年代使用不同的垃圾回收算法,新生代一般选择复制算法;老年代可以选择标记-清除标记-整理算法,由程序员来选择灵活度较高。
  • 分代的设计中允许只回收新生代(minor gc),如果能满足对象分配的要求就不需要对整个堆进行回收(full gc),STW时间就会减少。(尽可能做minor gc,少做full gc,尽量降低垃圾回收对程序运行的影响)

文章说明

该文章是本人学习 黑马程序员 的学习笔记,文章中大部分内容来源于 黑马程序员 的视频黑马程序员JVM虚拟机入门到实战全套视频教程,java大厂面试必会的jvm一套搞定(丰富的实战案例及最热面试题),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对 黑马程序员 的优质课程表示感谢。

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

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

相关文章

【SpringBoot】IDEA查看spring bean的依赖关系

前因&#xff1a;在研究springcloud config组件时&#xff0c;我发现config-server包下的EnvironmentController可以响应客户端的请求&#xff0c;但EnvironmentController并不在启动类所在的包路径下&#xff0c;所以我推测它是作为某个Bean方法在生效&#xff0c;寻找bean的依…

DAY1: 实习前期准备

文章目录 VS Code安装的插件C/CCMakeGitHub CopilotRemote-SSH收获 VS Code 下载链接&#xff1a;https://code.visualstudio.com 安装的插件 C/C 是什么&#xff1a;C/C IntelliSense, debugging, and code browsing. 为什么&#xff1a;初步了解如何在VS Code里使用C输出…

关于小爱同学自定义指令执行

1.前言 之前买了小爱同学音响&#xff0c;一直想让其让我的生活变得更智能&#xff0c;编写一些程序来完成一些自动化任务&#xff0c;但是经过搜索发现&#xff0c;官方开发者平台不能用了&#xff0c;寻找api阶段浪费了我很长时间。最后在github 开源项目发现了俩个比较关键…

13.SQL注入-宽字节

SQL注入-宽字节 含义&#xff1a; MySQL是用的PHP语言&#xff0c;然后PHP有addslashes()等函数&#xff0c;这类函数会自动过滤 ’ ‘’ null 等这些敏感字符&#xff0c;将它们转义成’ ‘’ \null&#xff1b;然后宽字节字符集比如GBK它会自动把两个字节的字符识别为一个汉…

内容营销专家刘鑫炜:网站排名需考虑哪些SEO优化技巧?

网站排名的SEO优化技巧包括&#xff1a; 1. 关键词研究&#xff1a;了解目标受众的搜索关键词&#xff0c;将这些关键词合理地应用在网站的标题、描述、正文和标签中&#xff0c;有助于提高网站排名。 2. 内容优化&#xff1a;创建高质量、有价值的内容&#xff0c;可以吸引搜…

Qt源码解析之QObject

省去大部分virtual和public方法后&#xff0c;Qobject主要剩下以下成员&#xff1a; //qobject.h class Q_CORE_EXPORT Qobject{Q_OBJECTQ_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)Q_DECLARE_PRIVATE(QObject) public:Q_I…

STM32-OC输出比较和PWM

本内容基于江协科技STM32视频内容&#xff0c;整理而得。 文章目录 1. OC输出比较和PWM1.1 OC输出比较1.2 PWM&#xff08;脉冲宽度调制&#xff09;1.3 输出比较通道&#xff08;高级&#xff09;1.4 输出比较通道&#xff08;通用&#xff09;1.5 输出比较模式1.6 PWM基本结…

MATLAB常用语句总结7

MATLAB总结7&#xff1a;常见错误归纳 本篇专门用于记录一些应试技巧 文章目录 MATLAB总结7&#xff1a;常见错误归纳前言一、一些小定义和小技巧二、蒙塔卡罗求解方法1.函数的定义2.函数引用3.代码量较少的蒙塔卡罗 三、函数引用与多变量四、矩阵引用五、非线性函数&#xff…

14-39 剑和诗人13 - 顶级大模型测试分析和建议

​​​​​ 随着对高级语言功能的需求不断飙升&#xff0c;市场上涌现出大量语言模型&#xff0c;每种模型都拥有独特的优势和功能。然而&#xff0c;驾驭这个错综复杂的生态系统可能是一项艰巨的任务&#xff0c;开发人员和研究人员经常面临选择最适合其特定需求的模型的挑战。…

哈弗架构和冯诺伊曼架构

文章目录 1. 计算机体系结构 2. 哈弗架构&#xff08;Harvard Architecture&#xff09; 3. 改进的哈弗架构 4. 冯诺伊曼架构&#xff08;Von Neumann Architecture&#xff09; 5. 结构对比 1. 计算机体系结构 计算机体系结构是指计算机系统的组织和实现方式&#xff0c…

Python | Leetcode Python题解之第220题存在重复元素III

题目&#xff1a; 题解&#xff1a; class Solution(object):def containsNearbyAlmostDuplicate(self, nums, k, t):from sortedcontainers import SortedSetst SortedSet()left, right 0, 0res 0while right < len(nums):if right - left > k:st.remove(nums[left]…

68.WEB渗透测试-信息收集- WAF、框架组件识别(8)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;67.WEB渗透测试-信息收集- WAF、框架组件识别&#xff08;7&#xff09; 右边这些是waf的…

Mean teacher are better role models-论文笔记

论文笔记 资料 1.代码地址 2.论文地址 https://arxiv.org/pdf/1703.01780 3.数据集地址 CIFAR-10 https://www.cs.utoronto.ca/~kriz/cifar.html 论文摘要的翻译 最近提出的Temporal Ensembling方法在几个半监督学习基准中取得了最先进的结果。它维护每个训练样本的标签…

PCIe驱动开发(1)— 开发环境搭建

PCIe驱动开发&#xff08;1&#xff09;— 开发环境搭建 一、前言 二、Ubuntu安装 参考: VMware下Ubuntu18.04虚拟机的安装 三、QEMU安装 下载网站&#xff1a; https://download.qemu.org 下载文件&#xff1a;qemu-4.1.0-rc5.tar.xz 使用如下命令解压&#xff1a; tar …

clickhouse高可用可拓展部署

clickhouse高可用&可拓展部署 1.部署架构 1.1高可用架构 1.2硬件资源 部署服务 节点名称 节点ip 核数 内存 磁盘 zookeeper zk-01 / 4c 8G 100G zk-02 / 4c 8G 100G zk-03 / 4c 8G 100G clikehouse ck-01 / 32c 128G 2T ck-02 / 32c 128G 2T ck-03 / 32c 128G 2T ck-04 /…

java wait, notify, notifyAll三个方法

wait(), notify(), 和 notifyAll() 是 Java 中用于线程间通信和同步的方法&#xff0c;它们都是 Object 类中的方法&#xff0c;而非 Thread 类的方法。这些方法通常与 synchronized 关键字一起使用&#xff0c;用于实现线程之间的协作和互斥访问共享资源。 关于生产者-消…

PsQuerySystemDllInfo逆向

typedef struct _SYSTEM_DLL_ENTRY {ULONG64 type;UNICODE_STRING FullName;PVOID ImageBase;PWCHAR BaseName;PWCHAR StaticUnicodeBuffer; }SYSTEM_DLL_ENTRY, * PSYSTEM_DLL_ENTRY; 返回值为上面的结构体指针 验证 type: fullname inagebase: pwchar basename PWCHAR …

Windows 11文件资源管理器选项卡的4个高级用法,肯定有你喜欢的

作为一个每天使用文件资源管理器来管理我的工作流程的人,选项卡帮助我为处于不同完成阶段的工作创建了不同的文件夹。以下是我使用选项卡提高工作效率的最佳技巧。 打开和关闭选项卡 假设你的计算机上安装了Windows 11的最新更新,请按Ctrl+E打开文件资源管理器。在我发现“…

可验证算法在招投标领域的专家“盲抽”中的标段识别码加密应用研究

摘要 在招投标过程中&#xff0c;标段&#xff08;包&#xff09;识别码的安全性至关重要。本文提出了一种基于可验证算法的标段识别码加密方法&#xff0c;以确保其在专家“盲抽”过程中的保密性和可信性。通过对不同表的标段识别码进行全量加密&#xff0c;并通过匹配验证其…

烟草企业如何在数字化转型中实现从“传统”到“智能”的跨越?

在数字化浪潮的席卷下&#xff0c;各行各业都在经历着深刻的变革。作为国民经济的重要组成部分&#xff0c;烟草行业正处于高质量发展的重要阶段&#xff0c;加快信息系统国产化升级&#xff0c;对于提升行业竞争力、强化信息安全保障具有重要战略意义。 达梦数据积极助力烟草行…