JVM GC 算法原理概述

对于JVM的垃圾收集(GC),这是一个作为Java开发者必须了解的内容,那么,我们需要去了解哪些内容呢,其实,GC主要是解决下面的三个问题:

  • 哪些内存需要回收?

  • 什么时候回收?

  • 如何回收?

回答了这三个问题,也就对于GC算法的原理有了最基本的了解。

1 如何判定哪些内存需要回收


  在Java虚拟机的堆中会存放着很多的对象,那么,我们需要回收垃圾的时候,是通过什么算法来判断哪些垃圾的生命周期已到,需要回收呢?接下来的几种算法将帮助你解决这几个问题。

引用计数算法

先讲讲第一个算法:引用计数算法

  其实,这个算法的思想非常的简单,一句话就是:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是不可能再被使用的。

  这些简单的算法现在是否还被大量的使用呢,其实,现在用的已经不多,没有被使用的最主要的原因是他有一个很大的缺点很难解决对象之间循环引用的问题

  循环引用:当A有B的引用,B又有A的引用的时候,这个时候,即使A和B对象都为null,这个时候,引用计数算法也不会将他们进行垃圾回收。

public class Test_02 {public static void main(String[] args) {Instance instanceA = new Instance();Instance instanceB = new Instance();instanceA.instance = instanceB;instanceB.instance = instanceA;instanceA = null;instanceB = null;System.gc();Scanner scanner = new Scanner(System.in);scanner.next();}
}class Instance{public Object instance = null;
}

如果使用的是引用计数算法,这是不能被回收的,当然,现在的JVM是可以被回收的。

可达性分析算法

  这个算法的思想也是很简单的,这里有一个概念叫做可达性分析,如果知道图的数据结构,这里可以把每一个对象当做图中的一个节点,我们把一个节点叫做GC Roots,如果一个节点到GC Roots没有任何的相连的路径,那么就说明这个节点不可达,也就是这个节点可以被回收。

上面图中,虽然obj7、8、9相互引用,但是到GC Roots不可达,所以,这种对象也是会被当做垃圾收集的。

在Java中,可以作为GC Roots的对象包括以下几种:

  • 虚拟机栈(栈帧中的局部变量表,Local Variable Table)中引用的对象。

  • 方法区中类静态属性引用的对象。

  • 方法区中常量引用的对象。

  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

2 什么时候回收


  在可达性分析算法中不可达的对象,也不是一定会死亡的,它们暂时都处于“缓刑”阶段,要真正宣告一个对象“死亡”,至少要经历两次标记过程。

step1:判断有没有必要执行finalize()方法
  • 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行`finalize()`方法

另外,有两种情况都视为“没有必要执行”:

  • 对象没有覆盖finaliza()方法。

  • finalize()方法已经被虚拟机调用过。

step2:如何执行

  如果这个对象被判定为有必要执行finalize()方法,那么此对象将会放置在一个叫做 F-Queue 的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。

step3:执行死亡还是逃脱死亡

首先,我们需要知道,finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue 队列中的对象进行第二次小规模的标记。

  • 逃脱死亡:对象想在finalize()方法中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可,例如把自己(this关键字)赋值给某个类变量或者对象的成员变量,这样在第二次标记时它将被移出“即将回收”的集合。

  • 执行死亡:对象没有执行逃脱死亡,那就是死亡了。

3 如何回收


  如何回收其实就是利用哪些算法进行回收,垃圾收集算法这里讲几种大家平时也是看到的比较的算法,分别为:标记-清除算法复制算法标记-整理算法分代回收算法

  这部分的内容其实在网上的文章比较多了,而且,基本上的差别不大,所以,从网上的文章选取下来,当做一个小的总结,大家可以参考这篇文章算是一个比较全的总结:GC算法与内存分配策略。

标记-清除(Mark-Sweep)算法

  标记-清除(Mark-Sweep) 算法是最基础的垃圾收集算法,后续的收集算法都是基于它的思路并对其不足进行改进而得到的。顾名思义,算法分成“标记”、“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记过程在前一节讲述对象标记判定时已经讲过了。

标记-清除算法的不足主要有以下两点:

  • 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不触发另一次垃圾收集动作。

  • 效率问题,因为内存碎片的存在,操作会变得更加费时,因为查找下一个可用空闲块已不再是一个简单操作。

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

复制(Copying)算法

  为了解决标记-清除算法的效率问题,一种称为“复制”(Copying)的收集算法出现了,思想为:它将可用内存按容量分成大小相等的两块,每次只使用其中的一块。当这一块内存用完,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。

  这样做使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,代价可能过高了。复制算法的执行过程如下图所示:

标记-整理(Mark-Compact)算法

  复制算法在对象存活率较高时要进行较多的复制操作,效率将会变低。更关键的是:如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用复制算法

  根据老年代的特点,标记-整理(Mark-Compact)算法被提出来,主要思想为:此算法的标记过程与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。 具体示意图如下所示:

分代收集(Generational Collection)算法

  当前商业虚拟机的垃圾收集都采用分代收集(Generational Collection)算法,此算法相较于前几种没有什么新的特征,主要思想为:根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法:

  • 新生代 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

  • 老年代 在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清除标记-整理算法来进行回收。

4 总结

这里用思维导图做一个小的总结。

参考

  堆外内存的回收机制分析 https://www.jianshu.com/p/35cf0f348275 

  java调用本地方法--jni简介 https://blog.csdn.net/w1992wishes/article/details/80283403 

  咱们从头到尾说一次 Java 垃圾回收 https://mp.weixin.qq.com/s/pR7U1OTwsNSg5fRyWafucA 

  深入理解 Java 虚拟机 

  Java Hotspot G1 GC的一些关键技术 Java Hotspot G1 GC的一些关键技术 - 美团技术团队

附:关于GC Roots的理解


所谓“GC roots”,或者说tracing GC的“根集合”,就是一组必须活跃的引用
例如说,这些引用可能包括:

  • 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
  • VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。
  • JNI handles,包括global handles和local handles
  • (看情况)所有当前被加载的Java类
  • (看情况)Java类的引用类型静态变量
  • (看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)
  • (看情况)String常量池(StringTable)里的引用

注意,是一组必须活跃的引用,不是对象。

  Tracing GC的根本思路就是:给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,其余对象(也就是没有被遍历到的)就自然被判定为死亡。注意再注意:tracing GC的本质是通过找出所有活对象来把其余空间认定为“无用”,而不是找出所有死掉的对象并回收它们占用的空间。
GC roots这组引用是tracing GC的起点。要实现语义正确的tracing GC,就必须要能完整枚举出所有的GC roots,否则就可能会漏扫描应该存活的对象,导致GC错误回收了这些被漏扫的活对象。

  这就像任何递归定义的关系一样,如果只定义了递推项而不定义初始项的话,关系就无法成立——无从开始;而如果初始项定义漏了内容的话,递推出去也会漏内容。

那么分代有什么好处?

  对传统的、基本的GC实现来说,由于它们在GC的整个工作过程中都要“stop-the-world”,如果能想办法缩短GC一次工作的时间长度就是件重要的事情。如果说收集整个GC堆耗时太长,那不如只收集其中的一部分?
  于是就有好几种不同的划分(partition)GC堆的方式来实现部分收集,而分代式GC就是这其中的一个思路。

  这个思路所基于的基本假设大家都很熟悉了:weak generational hypothesis——大部分对象的生命期很短(die young),而没有die young的对象则很可能会存活很长时间(live long)。

  这是对过往的很多应用行为分析之后得出的一个假设。基于这个假设,如果让新创建的对象都在young gen里创建,然后频繁收集young gen,则大部分垃圾都能在young GC中被收集掉。由于young gen的大小配置通常只占整个GC堆的较小部分,而且较高的对象死亡率(或者说较低的对象存活率)让它非常适合使用copying算法来收集,这样就不但能降低单次GC的时间长度,还可以提高GC的工作效率。

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

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

相关文章

ORB-SLAM3的Local Mapping线程详解

Local Mapping线程是ORB-SLAM3的三大线程之一。 文章目录 一、Local Mapping线程的主要流程二、Local Mapping线程主要作用1、插入关键帧2、剔除地图点 一、Local Mapping线程的主要流程 关键帧是通过根据经验去添加一些约束给帧,然后输出关键帧,如果能…

Netty组件基础

Netty入门简介 netty是一个异步、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。 Netty优势 Netty解决了TCP传输问题,如黏包、半包问题,解决了epoll空轮询导致CPU100%的问题。并且Netty对API进行增强&#xf…

matlab设置colorbar标题的两种方式

%% 第一种 figure; A rand(3,4,3); A1 A(:,:,1); A2 A(:,:,2); A3 A(:,:,3); contourf(A1,A2,A3,30); colormap(jet);colorbar; my_handlecolorbar; my_handle.Label.String depth/km; my_handle.Label.FontSize 15;%% 第二种 figure; A rand(3,4,3); A1 A(:,:,1); A2 …

学习笔记14——Springboot以及SSMP项目

SpringBoot Springboot项目 IDEA2023只能创建jdk17和21的springboot项目解决 - 嘿嘿- - 博客园 (cnblogs.com)解决IntelliJ IDEA2022.03创建包时,包结构不自动分级显示的问题_idea建包不分级-CSDN博客IDEA调出maven项目窗口_idea maven窗口-CSDN博客 相比于spring的…

【温故而知新】HTML回流和重绘

概念 HTML回流和重绘是浏览器渲染页面时的两个重要过程。 回流(reflow)指的是浏览器在渲染页面时,根据页面的结构和样式计算元素的布局和位置。当页面布局或元素位置发生变化时,浏览器需要重新计算元素的布局,这个过…

理解SpringMVC的工作流程

组件 前置控制器 DispatcherServlet。 映射控制器 HandlerMapping。 处理器 Controller。 模型和视图 ModelAndView。 视图解析器 ViewResolver。 工作流程 spring mvc 先将请求发送给 DispatcherServlet。 DispatcherServlet 查询一个或多个 HandlerMapping,找到…

开源分布式搜索引擎ElasticSearch结合内网穿透远程连接

文章目录 前言1. Windows 安装 Cpolar2. 创建Elasticsearch公网连接地址3. 远程连接Elasticsearch4. 设置固定二级子域名 前言 简单几步,结合Cpolar 内网穿透工具实现Java 远程连接操作本地分布式搜索和数据分析引擎Elasticsearch。 Cpolar内网穿透提供了更高的安全性和隐私保…

re模块(正则)

【 一 】 re模块概述 在线测试工具 正则表达式在线测试 - 站长工具 随着正则表达式越来越普遍,Python 内置库 re 模块也支持对正则表达式使用 Python 提供了re模块可以支持正则表示表达式使用,re模块提供了9个常量、12个函数 使用方法: re…

1.1.0 IGP高级特性之BFD

双向转发检测技术 BFD(Bidirectional Forwarding Detection,双向转发检测) 提供了一个通用的、标准化的、介质无关和协议无关的快速故障检测机制,用于快速检测、监控网络中链路或者IP路由的转发连通状态。【每个厂家都支持的】 B…

6款AI商品海报创作神器,让设计创意无限!

曾经,为了展现物品的美好,我们煞费苦心地设计造型,捕捉那一刹的灵感;为了制作商品的海报,我们四处寻找那些能触动心灵的素材;为了拍摄产品的完美瞬间,我们不断调整角度,期待光与影的…

引用jquery.js的html5基础页面模板

本专栏是汇集了一些HTML常常被遗忘的知识,这里算是温故而知新,往往这些零碎的知识点,在你开发中能起到炸惊效果。我们每个人都没有过目不忘,过久不忘的本事,就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

【JavaEE初阶二】 Thread类及常见方法

1. 线程的创建 主要来简单学习一下以下几种方法: 1.1 继承 Thread 类 具体代码见前面的一章节,主体的步骤有以下几部分; 1、继承 Thread 来创建一个自定义线程类MyThread class MyThread2 extends Thread{//重写run方法Overridepublic void …