JVM笔记 —— 垃圾回收(GC)详解

news2024/12/22 19:50:35

一、垃圾回收的分类

针对HotSpot JVM的实现,它里面的GC其实准确分类只有两大种:

  • Partial GC:部分收集模式

    • Young GC:只收集年轻代的GC
    • Old GC:只收集老年代的GC。只有CMS中有这个模式。
    • Mixed GC:收集整个年轻代以及部分老年代的GC。只有G1有这个模式
  • Full GC:收集整个堆和方法区。

堆是垃圾回收的主要区域,方法区很少会被回收。
本文所讨论的均指HotSpot JVM

二、死亡对象判断方法

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

1. 引用计数法

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

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

但是引用计数法很难解决对象之间循环引用的问题,因此目前主流的虚拟机中并没有选择这个算法。

在这里插入图片描述

2. 根可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

下图中的 Object 6 ~ Object 10 之间虽有引用关系,但它们到 GC Roots 不可达,因此为需要被回收的对象。
在这里插入图片描述

哪些对象可以作为 GC Roots 呢?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 类静态常量引用的对象
  • 常量池中被引用的对象
  • 所有被同步锁持有的对象

对象被回收前如果该对象重写了finaize()方法则需先执行此方法后才能被回收。Object 类中的 finalize 方法一直被认为是一个糟糕的设计,影响了 Java 语言的安全和 GC 的性能,JDK9 版本及后续版本中各个类中的 finalize 方法会被逐渐弃用移除。
参考:Java基础知识点之finalize方法详解

三、引用类型分类

无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。

Java中将引用分为了强引用、软引用、弱引用、虚引用四种。非强引用通常用来指向某些只需要暂时缓存的数据。

1. 强引用

引用变量默认就是强引用,以下其它三种将引用通过特殊包装的才能形成其它引用。
强引用的对象在GC Roots可达时不会被回收。

2. 软引用 SoftReference

软引用是一种相对强引用弱化了一些的引用,用java.lang.ref.SoftReference实现,可以让对象豁免一些垃圾收集。在可达时,当系统内存充足的时不会被回收,系统内存不足时则会被回收。

public class SoftReferenceDemo { 
     public static void main(String[] args) { 
         Object a = new Object(); 
         SoftReference<Object> softReference = new SoftReference<>(a);//软引用
         //a和软引用指向同一个对象
         System.out.println(a);//java.lang.Object@4554617c
         System.out.println(softReference.get());//java.lang.Object@4554617c 10 
         //内存够用,软引用不会被回收
         a==null;
         System.gc();//内存够用不会自动gc,手动唤醒gc
         System.out.println(softReference.get());//java.lang.Object@4554617c 16 
         //内存不够用时
         try {            //配置Xms和Xmx为5MB
             byte[] bytes = new byte[1024*1024*30];//设置30MB超内存
         } catch (Throwable e){e.printStackTrace();}
           finally {
             System.out.println(softReference.get());//null,被回收
 		 } 
 	 }
 }

软引用的一个应用场景:一个应用需要读取大量的本地图片,如果每次读取都从硬盘读取会严重影响性能,如果一次性全部加载到内存,内存可能会溢出。可以使用软引用解决这个问题,使用一个HashMap来保存图片路径和图片对象管理的软引用之间的映射关系,内存不足时,JVM会自动回收缓存图片对象的占用空间,有效地避免了OOM问题。

//Map<图片路径,图片对象软引用>,在系统内存不足时value所指向的对象会被回收
Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>

3. 弱引用 WeakReference

弱引用需要用java.lang.ref.WeakReference实现,它比软引用的生存期更短,对于弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否够,都会回收该对象的占用内存。

Object a = new Object(); 
WeakReference<Object> softReference = new WeakReference<>(a);//弱引用

Map中还有一个WeakHashMap,WeakHashMap就是一种弱引用的map,内部的key为弱引用,在GC时如果key指向的对象不存在其它强引用的情况下会被回收掉,而对于value的回收会在下一次操作map时回收掉,所以WeakHashMap适合缓存处理。

4. 虚引用 PhantomReference

虚引用要通过java.lang.ref.PhantomReference类来实现,虚引用不会决定对象的生命周期,如果一个对象只有虚引用,就相当于没有引用,在任何时候都可能会被垃圾回收器回收。它不能单独使用也不能访问对象,虚引用必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供一种确保对象被finalize以后,做某些事情的机制。


因此总结如下,在一次GC中,用于可达性分析的GC Roots本身不会被回收,GC Roots引用链不可达对象的必然会被回收,而在引用链可达的情况中:

  • 强引用的对象不会被回收
  • 仅有软引用的对象在内存不足时会被回收
  • 仅有弱引用或虚引用的对象必然会被回收

在这里插入图片描述

参考:JVM中如何理解强引用、软引用、弱引用、虚引用?
阿里面试:说说强引用、软引用、弱引用、虚引用吧

四、什么是内存泄漏

严格来说,如果某些对象在程序中不会再被用到了,但是这些对象又无法被垃圾收集器回收(GC Roots以及其引用链可达的强引用对象),那么这些对象所占用的内存就处于平白浪费的状态了,这就的内存泄漏。如果这种情况可以累积,随着内存泄漏的增多,就会导致严重的性能问题甚至OOM。

宽泛地说,实际情况中很多时候一些不太好的实践会导致对象的生命周期变得过长,比如不合理地进入了老年代,在老年代中堆积,等到Full GC时才能被回收,这种情况也可以叫“内存泄漏”。

在实际场景中可大概分为以下几种情况:

1. 类变量中引用短期对象

类变量在垃圾回收时被作为GC Roots,而类变量的生命周期一般和JVM程序一致,只有方法区中的对应类被回收才有可能被回收。如果在类变量对象中引用很多短期内使用的对象,那么由于在GC Roots下被强引用,这些短期对象都得不到回收,就造成了内存泄漏。如下一个静态list的例子:

public class test {
    static List list = new ArrayList<>();
    //如果object只是短期内需要使用的对象,那么如果这个方法一直被调用,就会造成内存泄漏
    public void oomTest() {
        Object object = new Object();
        list.add(object);
    }
}

2. 各类连接泄漏

例如数据库连接。如果在获取数据库连接后没有正确的归还或关闭,导致每次访问都创建一个未关闭的连接,就导致了内存泄漏。

案例:多线程访问数据库导致内存泄露的优化过程
数据库连接池内存泄漏问题的分析和解决方案

3. 改变哈希集合关键字的hash值

当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。

因为String是不可变类型,我们可以放心地把String 存入HashSet,或者把String当做HashMap的key值。

4. 缓存泄漏

内存泄漏的一个常见来源是缓存,一旦你把对象引用放入到缓存中,就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。
对于这个问题,可以使用WeakHashMap(弱引用)代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。

参考:https://blog.csdn.net/weixin_43899792/article/details/124304136

五、垃圾收集算法

分代收集理论

现代虚拟机的垃圾收集器大多都遵循了分代收集理论,这个理论建立在三个经验假说之上:

  • 弱分代假说:绝大多数对象都是朝生夕灭的。
  • 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
  • 跨代引用假说:跨代引用相对于同代引用来说仅占极少数。依据这条假说,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构,这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。

以下简单讲解下三种垃圾回收方法论

1. 标记 - 清除算法

最早出现也是最基础的垃圾收集算法是标记 - 清除算法,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收清除掉所有被标记的对象。也可以反过来。

缺点:
执行效率不稳定,如果堆中有大量对象需要回收就必须进行大量的标记与清除动作。
会导致内存空间碎片化,创建对象时需要耗费资源寻找合适的空闲空间,并且创建较大对象时可能找不到足够的连续内存。

2. 标记 - 复制算法

标记 - 复制算法将要回收的空间分为几个区域,注意不要被标记复制的名字忽悠了,标记清除算法中分为标记和清除两个阶段,但是标记复制算法并没有标记阶段,为什么呢?首先要明确判断对象是否存活的核心思想是用根可达算法找出存活对象,由于标记清除算法需要回收垃圾对象,所以需要对存活对象进行标记,然后清除不可用对象。而复制算法是要复制存活对象到另一块区域,所以在根可达算法发现存活对象后是直接复制到另一块区域,即在根可达分析过程中就已经完成了筛选(复制),待复制完成后,直接清理掉另一块区域即可,所以不需要挨个标记然后去清除。

优点:执行效率较高,且不会产生内存碎片
缺点:内存空间不能充分利用,需要保留一块区域用于下一次垃圾回收时复制存活对象。

Hotspot JVM的堆空间中年轻代的垃圾回收就是基于这种算法,年轻代划分为一个Eden区和两个Survivor区,内存比例默认为8:1:1,一个Survivor区保留,其余可使用,垃圾回收时只需要将通过根可达分析确认存活的对象复制到保留的Survivor区。但是不是每次都只有10%的对象存活,因此需要老年代做分配担保,如果Survivor区空间不足,则将一部分对象直接晋升老年代。

3. 标记 - 整理算法

标记 - 复制算法除了不能充分利用空间,且对象存活率高时需要进行较多的复制操作,因此在存活率较高的老年代就不能采用这种算法。
针对老年代存活率高的特性,有人提出了标记 - 整理算法,就是在标记 - 清除算法的基础上,将剩余存活对象向一端进行整理。

六、经典垃圾收集器

如果说收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。《Java虚拟机规范》中对垃圾收集器应该如何实现并没有做出任何规定,因此不同的厂商、不同版本的虚拟机所包含的垃圾收集器都可能会有很大差别,不同的虚拟机一般也都会提供各种参数供用户根据自己的应用特点和要求组合出各个内存分代所使用的收集器。

1. Serial / Serial Old / ParNew 收集器

Serial收集器:单线程收集器,并且在进行垃圾收集工作时必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。采用标记复制算法。
Serial Old 收集器:Serial收集器的老年代版本,采用标记整理算法。
在这里插入图片描述
ParNew收集器:Serial收集器的多线程版本,除了使用多线程进行垃圾回收外其他与Serial一样
在这里插入图片描述

2. Parallel Scavenge / Parallel Old 收集器

Java8的默认垃圾收集器组合

Parallel Scavenge收集器:也是使用标记-复制算法的多线程收集器,关注点是吞吐量(高效率的利用 CPU),提供了很多参数供用户找到最合适的停顿时间或最大吞吐量。
Parallel Old 收集器:Parallel Scavenge的老年代版本。使用多线程和“标记-整理”算法

在这里插入图片描述

3. CMS收集器

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

4. G1 收集器

Java9后的默认垃圾收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.。

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。

在这里插入图片描述

参考:JavaGuide

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

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

相关文章

mybatis的基本使用和理解

mybatis的基本使用和理解 Lombok的使用(使用注解的方式将实体类中的get、set、构造函数代替&#xff09; Lombok是一个Java库&#xff0c;能自动插入编辑器并且构建工具&#xff0c;简化Java开发。通过添加注解的方式&#xff0c;不需要为类编写getter或equals方法&#xff0…

Vue3 + TS4.8踩坑之配置husky问题env: sh\r: No such file or directory

一、基本情况&#xff1a; 硬件环境&#xff1a;MacOS 10.14.6 背景&#xff1a;用vue3官方npm init vuelatest初始化创建的vue3 ts4.8项目。 二、问题和解决方案&#xff1a; 问题1&#xff1a;git commit的时候提示&#xff1a;env: sh\r: No such file or directory 原…

0121 进程管理

1.在Linux中&#xff0c;每个执行的程序都称为一个进程。每一个进程都分配一个ID号&#xff08;pid进程号&#xff09; 2.每个进程都可能以两种方式存在&#xff0c;前台和后台。前台进程即用户当前屏幕上可进行的操作&#xff0c;后台进程即实际操作&#xff0c;由于屏幕上无…

Java中源文件声明规则,以及java包,import关键字的使用

Java中源文件声明规则&#xff0c;以及java包&#xff0c;import关键字的使用。 1. 源文件声明规则 当在一个源文件中定义多个类&#xff0c;并且还有import语句和package语句时&#xff0c;要特别注意以下规则&#xff1a; a. 一个源文件中只能有一个 public 类。 b. 一个源…

LC电路是如何产生振荡的

电容和电感是两个储能元件&#xff0c;当电源给电容充电完成后&#xff0c; 将开关切到电感&#xff0c;电电感两端的电压是一个正弦波&#xff0c;正弦波频率是: 这时我们称电感和电容产生了振荡。 当然由于电感和电容都是有损耗的&#xff0c;所以这种振荡会慢慢的衰减&…

【文献查找使用】

目录 知识框架No.1 中文文献一、查找、二、下载、三、引用、页码四、什么是DOI呢&#xff1f;&#xff1f;&#xff1f; No.2 外文文献一、查找二、下载三、引用、页码 No.3 如何在不下载的情况下进行正确引用呢&#xff1f;&#xff1f;一、谷歌学术进行查询二、上sci-hub网站…

使用python的cartopy库读取shapefile文件 .shp文件是乱码

文章目录 问题解决方法 问题 使用python的cartopy库读取shapefile文件即.shp文件乱码 我在使用python的cartopy库读取shapefile文件时出现了乱码 record的.attributes的[‘name’]都是乱码 from cartopy.io import shapereader shp_pathr/home/mw/project/北京市1.shp#文件路…

【Linux 裸机篇(六)】I.MX6U 主频和时钟配置

目录 一、时钟系统详解1. 系统时钟来源2. 7路 PLL 时钟源2.1 ARM_PLL (PLL1)2.2 528_PLL (PLL2)2.3 USB1_PLL (PLL3)2.4 USB2_PLL (PLL7)2.5 ENET_PLL (PLL6)2.6 VIDEO_PLL (PLL5)2.7 AUDIO_PLL (PLL4) 3. 时钟树简介4. 内核时钟设置5. PFD 时钟设置6. AHB、 IPG 和 PERCLK 根时…

SQLite数据库简单小入门学习(一)

目录 一、认识数据库&#xff08;一&#xff09;数据库简介&#xff08;二&#xff09;数据库类型 二、SQLite数据库&#xff08;一&#xff09;SQLite简介&#xff08;二&#xff09;学习所需工具&#xff08;1&#xff09;scott.db&#xff08;2&#xff09;SQLiteSpy.exe &a…

【分布式理论】聊一下 ACID、BASE、CAP、FLP

分布式理论基础 今天我们来聊一下分布式相关基础理论基础&#xff0c;上一篇文章中&#xff0c;我描述了一下分布式系统的纲&#xff0c;但是想要入手学习分布式系统设计&#xff0c;其实需要先从基本理论开始。而知名的ACID、BASE、CAP、FLP都是相关的理论基础。 ACID ACID…

STM32平衡小车 TB6612电机驱动学习

TB6612FNG简介 单片机引脚的电流一般只有几十个毫安&#xff0c;无法驱动电机&#xff0c;因此一般是通过单片机控制电机驱动芯片进而控制电机。TB6612是比较常用的电机驱动芯片之一。 TB6612FNG可以同时控制两个电机&#xff0c;工作电流1.2A&#xff0c;最大电流3.2A。 VM电…

C++ [STL-简介]

本文已收录至《C语言和高级数据结构》专栏&#xff01; 作者&#xff1a;ARMCSKGT ​​​​​​​​ 文章目录 前言正文简介关于STL各种版本 STL组件容器迭代器配接器(适配器)算法仿函数空间配置器 STL的重要性学习STL的意义如何学习STL STL的缺陷 最后 前言 STL(standard tem…

Django+Vue的一个用户数据分析展示

文章目录 Git地址、项目所需文件总体效果展示一、项目环境所需二、Django代码解析2.1、执行文件2.2、注册app01 Git地址、项目所需文件 SQL数据文件和用户需求文件 提取码 1111 Git克隆地址 zip下载 其中第一个连接是数据文件&#xff0c;后两个连接选一个即可 总体效果展示 …

从idea向GitLab上传代码图文详解

这里写目录标题 一 新建一个idea工程二 点击左下角Version Control三 上传到GitLab四 给IDEA发链接五 回gitlab上校验六 去gitlab上把代码拖回来 在安装完gitlab插件&#xff0c;辛苦的配置完gitlab环境后&#xff0c;向gitlab中提交代码变成了首要任务 一 新建一个idea工程 二…

非常量引用只能绑定到左值

问题分析 代码情况&#xff1a; // ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream> #include <vector> using namespace std; vector<int> fun1() {vector<int> ver1;for (int i…

HCIP之路---vlan实验

1、pc2/4/5/6同一网段&#xff0c;pc1/3与2/4/5/6不在同一网段 设置1/3 --- 192.168.1.0/24 2/4/5/6 --- 192.168.2.0/24 sw1上配置 [SW1-GigabitEthernet0/0/3]dis this # interface GigabitEthernet0/0/3 port link-type access port default vlan 2 # return [SW1-Giga…

C++类的模拟实现

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了简单模拟实现string类 C类的模拟实现 文章目录 C类的…

Progress ThemeBuilder crack

Progress ThemeBuilder crack 自定义输入将覆盖自定义日期输入和下拉列表。 Fluent主题中的图表不应用系列颜色。 撤消重做操作会导致重复以前编辑过的变量。 拆分器折叠的拆分条模板错误。 ThemeBuilder是一个多功能工具&#xff0c;可以帮助您创建视觉样式&#xff0c;并将其…

数据分析入门之:如何快速安装使用Jupyter Notebook?

人生苦短&#xff0c;我用python 今天来给大家介绍一下关于Jupyter Notebook的用法 关于它的组成部分就先不在这里详细解说啦~ 毕竟我可太懂你们啦~ 文章太长就会吃灰的~ python 安装包资料:点击此处跳转文末名片获取 一、什么是Jupyter Notebook&#xff1f; 1. 简介 Jupy…

Atom 1.13版本带来的哪些改变?

Atom是GitHub基于Electron的开源文本编辑器&#xff0c;它的1.13版本 为用户和开发人员增加了许多新的特性和改进&#xff0c;包括一个基准工具&#xff0c;一个“重新打开项目”菜单选项和API&#xff0c;以及一个自定义按钮解析器&#xff0c;它可以把Chrome键盘事件映射为At…