【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
    @Override
    public void run() {
        //记录开始时间

        long last = System.currentTimeMillis();
        while(true){
            long now = System.currentTimeMillis();
            // 如果不是垃圾回收影响,这里每次都应该是输出100
            System.out.println(now - last);
            last = now;
            Thread.sleep(100);
        }
    }
}

/**
 * 创建对象线程
 */
class ObjectThread extends Thread{

    @SneakyThrows
    @Override
    public void run() {
        List<byte[]> bytes = new LinkedList<>();
        while(true){
            // 最多存放8g,然后删除强引用,垃圾回收时释放8g
            if(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.mfbz.cn/a/777634.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上万组风电,光伏,用户负荷数据分享

上万组风电&#xff0c;光伏&#xff0c;用户负荷数据分享 可用于风光负荷预测等研究 获取链接&#x1f517; https://pan.baidu.com/s/1izpymx6R3Y8JsFdx42rL0A 提取码&#xff1a;381i 获取链接&#x1f517; https://pan.baidu.com/s/1izpymx6R3Y8JsFdx42rL0A 提取…

【算法笔记自学】第 5 章 入门篇(3)——数学问题

5.1简单数学 #include <cstdio> #include <algorithm> using namespace std; bool cmp(int a,int b){return a>b; } void to_array(int n,int num[]){for(int i0;i<4;i){num[i]n%10;n /10;} } int to_number(int num[]){int sum0;for(int i0;i<4;i){sumsu…

计算组的妙用!!页面权限控制

需求描述&#xff1a; 某些特殊的场景下&#xff0c;针对某页看板&#xff0c;需要进行数据权限卡控&#xff0c;但是又不能对全部的数据进行RLS处理&#xff0c;这种情况下可以利用计算组来解决这个需求。 实际场景 事实表包含产品维度和销售维度 两个维度属于同一公司下面的…

搭建互联网医院实战:从源码到在线问诊APP的全流程开发

今天&#xff0c;笔者将讲述在线问诊APP的全流程开发&#xff0c;帮助开发者理解和掌握搭建互联网医院的核心技术和步骤。 一、需求分析与设计 需求分析包括明确目标用户、功能需求、性能需求等。设计阶段则包括系统架构设计、数据库设计和前后端界面设计等。 1.目标用户&…

柯桥职场英语学习商务英语口语生活英语培训生活口语学习

辣妹用英语怎么说&#xff1f; 辣妹在英语中通常被翻译为“hot girl”或“spicy girl”&#xff0c;但更常见和直接的是“hot chick”或简单地使用“hot”来形容。 举个例子: Shes a real hot girl with her trendy outfit and confident attitude. 她真是个辣妹&#xff0…

Ubuntu 20版本安装Redis教程

第一步 切换到root用户&#xff0c;使用su命令&#xff0c;进行切换。 输入&#xff1a; su - 第二步 使用apt命令来搜索redis的软件包&#xff0c;输入命令&#xff1a;apt search redis 第三步 选择需要的redis版本进行安装&#xff0c;本次选择默认版本&#xff0c;redis5.…

谷粒商城-记录创建工程和模块时遇到的两个问题

文章目录 一&#xff0c;Maven工程出现Gradle相关的信息1&#xff0c;问题描述2&#xff0c;解决办法 二&#xff0c;找不到maven插件1&#xff0c;问题描述2&#xff0c;解决方案 三&#xff0c;补充知识&#xff1a;Maven和Gradle 这篇记录几个在创建工程和模块后遇到的几个问…

代码随想录算法训练营第四十五天| 300.最长递增子序列、 674. 最长连续递增序列、 718. 最长重复子数组

300.最长递增子序列 题目链接&#xff1a;300.最长递增子序列 文档讲解&#xff1a;代码随想录 状态&#xff1a;不会&#xff0c;递推状态的时候只想着如何从dp[i-1]推导dp[i]&#xff0c;没想过可能需要枚举dp[0-i] 思路&#xff1a; 找出所有比自己小的数字的dp[j],在这些dp…

超过GPT-4V,国产开源多模态大模型来了!支持视频理解/超高分辨率图片理解/多轮对话...

扫码领取享50优惠&#xff01;随时可用&#xff0c;先到先得&#xff01; 大家好&#xff0c;开源多模态大模型真的是每天都在疯狂的涌现&#xff0c;今天分享一个国产大模型 InternLM-XComposer-2.5 中文名&#xff1a;浦语灵笔2.5 仅使用 7B LLM 后端就达到了 GPT-4V 级别的能…

全能PDF工具集 -- PDF Shaper Professional v14.3 特别版

软件简介 PDF Shaper是一款功能强大的PDF工具集&#xff0c;它提供了一系列用于处理PDF文档的工具。这款软件使用户能够轻松地转换、分割、合并、提取页面以及旋转和加密PDF文件。PDF Shaper的界面简洁直观&#xff0c;使得即使是新手用户也能快速上手。它支持广泛的功能&…

Okhttp hostnameVerifier详解

hostnameVerifier 方法简介核心原理参考资料 方法简介 本篇博文以Okhttp 4.6.0来解析hostnameVerfier的作用&#xff0c;顾名思义&#xff0c;该方法的主要作用就是鉴定hostnname的合法性。Okhttp在初始化的时候我们可以自己配置hostnameVerfier&#xff1a; new OkHttpClien…

奇迹MU 骷髅战士在哪

BOSS分布图介绍 我为大家带来各地区怪物分布图。在游戏前期&#xff0c;很多玩家可能会不知道该去哪里寻找怪物&#xff0c;也不知道哪些怪物值得打。如果选择了太强的怪物&#xff0c;弱小的玩家可能会无法抵御攻击。如果选择了低等级的boss&#xff0c;收益可能并不理想。所…

【数据库原理】课程笔记

数据库原理 一、数据库系统基础 数据模型的类型 概念数据模型&#xff1a; 概念数据模型也称概念模型或信息模型,是对现实世界中问题域内事务(特性)的描述,是以用户观点实现世界的模型(图形表示)。主要用于描述事物的概念化结构,使数据库的设计人员在设计初期,避开计算机系统及…

基于大象机器人UltraArm P340机械臂和传送带,实现教育场景中的自动化分拣系统!

引言 今天我们将展示一个高度自动化的模拟场景&#xff0c;展示多个机械臂与传送带协同工作的高效分拣系统。在这个场景中&#xff0c;机械臂通过视觉识别技术对物体进行分类&#xff0c;并通过精确的机械操作将它们放置在指定的位置。这一系统不仅提高了分拣的速度和准确性&am…

Go语言--复合类型之指针与数组

分类 指针 指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置。Go 语言对指针的支持介于 Java 语言和 C/C语言之间,它既没有想 Java 语言那样取消了代码对指针的直接操作的能力,也避免了 C/C语言中由于对指针的滥用而造成的安全和…

【紫外线发光器件小结】 UV-B LED 308nm

之前有介绍光的波长和频率计算。 波长小于390nm,频率高于770太赫兹的电磁波忙&#xff0c;或者光。基本有一段就叫做紫外线。 紫外线有分为UV-A/B/C;三小段&#xff1b; 如下图&#xff1a; 高压汞灯与UV LED的光谱&#xff1b;黑色线汞灯&#xff0c;蓝色LED

通信协议:常见的芯片内通信协议

相关阅读 通信协议https://blog.csdn.net/weixin_45791458/category_12452508.html?spm1001.2014.3001.5482 本文将简单介绍一些常见的芯片间通信协议&#xff0c;但不会涉及到协议的具体细节。 一、AMBA&#xff08;Advanced Microcontroller Bus Architecture&#xff09;…

(七)[重制]C++命名空间与标准模板库(STL)

​ 引言 在专栏C教程的第六篇C中的结构体与联合体中&#xff0c;介绍了C中的结构体和联合体&#xff0c;包括它们的定义、初始化、内存布局和对齐&#xff0c;以及作为函数参数和返回值的应用。在专栏C教程的第七篇中&#xff0c;我们将深入了解C中的命名空间&#xff08;nam…

C++(Qt)-GIS开发-简易瓦片地图下载器

Qt-GIS开发-简易瓦片地图下载器 文章目录 Qt-GIS开发-简易瓦片地图下载器1、概述2、安装openssl3、实现效果4、主要代码4.1 算法函数4.2 瓦片地图下载url拼接4.3 多线程下载 5、源码地址6、参考 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;GIS开发 …

连锁门店如何快速联网

随着新零售业态的发展&#xff0c;连锁门店的运营模式逐渐转为数字化运营&#xff0c;新增了诸如收银PoS、扫码枪、摄像头等数字化终端。这些数字化的业务应用都需要依托稳定可靠的网络才能正常运转&#xff0c;在这样的背景下&#xff0c;连锁门店对网络连接的需求显得尤为关键…