JVM(九):垃圾回收算法

在本文中,我们将从概念模型的角度探讨 JVM 是如何回收对象,包括 JVM 是如何判断一个对象已经死亡,什么时候在哪里进行了垃圾回收,垃圾回收有几种核心算法,每个算法优劣是什么等。

为何需要GC

Java 中的一个核心技术就是自动垃圾回收,该技术使得程序员可以不用像写 C++ 一样手动分配和释放内存,那么为何还需要我们去学习垃圾回收呢。这里就要说到两个概念了。

  • 内存泄露:有已经不再使用的对象仍然占用着内存;
  • 内存溢出:已经没有足够的空间可以让 JVM 分配内存给对象了。

大量的内存泄露会引发内存溢出,但内存溢出不一定是内存泄露引起的,也可能是因为总共的内存空间就不够大,而需要分配的对象太大导致。

学习垃圾回收的背后逻辑,可以让我们在程序发生内存溢出的时候,快速高效地排查出问题进行解决。并且学习了 GC 的细节,也有助于我们调节 JVM 的一些运行参数,让系统达到更高的并发量。

对象的死亡

如果要销毁一个对象,那么就需要确定该对象已经死亡,只有这样才能够将该对象所占的内存空间进行释放。那么 JVM 是如何判断一个对象已经死亡了呢。

引用计数法

引用计数法实现十分简单,就是给每一个对象增加一个计数器,每当有一个地方对其进行了引用就 +1,当引用失效时就 -1,如果计数器的值为 0,则代表该对象已经不再被使用了,可以对其回收了。

这种方式的最大优点就是实现简单,判定效率高。但其有一个致命的缺点就是 循环引用问题。当两个对象互相引用,但其实他们已经没有任何其他用处了。此时因为彼此间还存在引用,就会发生循环引用,使用引用计数法就无法对其进行回收。

可达性分析算法

正是因为引用计数法那个致命的缺点,因此主流的实现都是通过 可达性分析 来判断对象能否进行销毁。其核心思想是 通过一系列称为 "GC Roots" 的节点来作为起始点,从这些节点开始搜索,这个搜索的轨迹被称为 "引用链",如果一个对象没有包含在任何一个引用链中,那么就判断该对象是无效的。

概念中说到是通过 GC Roots 来作为起始点,那么哪些对象可以作为GC Roots呢。

  • 虚拟机栈中引用的对象;
  • 本地方法栈中引用的对象;
  • 方法区中静态属性引用的对象;
  • 方法区中常量引用的对象。

引用的区分

在判断对象能否被销毁的时候,都使用到了 引用 这个词语,说的是如果有被引用的,那么就不销毁,如果没有引用则将其进行销毁,这种分别方式非黑即白,太过强硬,因此 JDK1.2 之后对引用的含义进行了扩充,实现了多级回收的效果。即在内存不紧张的时候,有一些对象是可以进行保留的,但如果内存紧张的时候,就需要对其进行回收。

  • 强引用:我们平常在编程使用的引用都是这种,普遍存在的引用,只要是这个,就不会被回收;
  • 软引用:有用但非必需。在内存很紧张快要溢出的时候,就会回收这些对象,如果回收后还没有空余空间才会报内存溢出。这种引用通常用来实现内存敏感的缓存;
  • 弱引用:比软引用更弱一些,只能活到下一次垃圾回收前。其实主要回收的就是这些内存;
  • 虚引用:虚引用不对对象生存时间造成影响,也无法通过虚引用获得对象实例。其存在的价值就是在对象收到回收的时候,能够让系统做一些事。应用场景为跟踪对象被 GC 的活动,因为其被回收的时候系统会受到一条系统通知。

方法区的回收

前面说的都是对象的回收,即对堆内存的回收,但其实在方法区内也是有垃圾回收的。在方法区内回收的内容主要是 废弃常量无用的类

其中废弃常量很好理解。就是常量池中的一个常量已经没有任何对象引用它了,即其已经没有价值了,那么就会将其移出常量池,回收其空间。

而对无用的类进行回收又是怎么理解的呢。首先我们需要判断什么是无用的类。一个类是无用的,需要满足以下3点:

  • 该类的实例都已经被回收了,即堆中没有该类的实例对象
  • 加载该类的 ClassLoader 已经被回收
  • 无法通过反射访问该类,即该类对应的 java.lang.Class 对象没有被调用

只有满足以上3点的类才可以被回收,但其是否回收取决于 JVM 启动时的参数控制。JVM 可以在启动时设置不对类进行回收。

回收算法

上面我们已经明白了什么对象是可以回收的,那么我们该如何针对这些对象进行回收呢。回收前后内存空间又是如何布局的呢。下面就让我们来看一下几个主流的 GC 算法。

标记-清除算法

标记-清除算法是最简单,最基本的算法。其本质就如同其名字一样,分为2个步骤,首先标记出所有需要清除的对象,然后在回收阶段,统一清除即可。

但其拥有两个严重的缺点。一个是标记和清除阶段都不快,效率很低;另一个是其只是单纯的将无用的对象清除,很容易造成大量的内存碎片,如果内存碎片太多,那么在分配大对象的时候,就很容易造成内存不够的情况。因此针对这些情况,就出现了几个改进的优良版本。

标记-整理算法

标记-整理算法解决的是内存碎片的问题,在标记阶段还是采取一样的解决方式,但在下个阶段并不是直接清除掉无用对象,而是先将有用的对象移到内存的一边,然后直接回收掉分界线一边的对象,这样就可以腾出许多规整的空间。

复制算法

标记-整理算法只是解决了内存碎片的问题,但是效率问题还是一个痛点,因此就有人提出了复制算法。其将内存空间分为 2 部分,每一次只使用其中一块,当这一块的空间用完了,就将存活的对象复制到另一边去,然后将使用过的空间直接清理掉即可。这种算法十分的高效,也解决了内存碎片的问题。

但其将可用空间简单的划分为了50-50,代价十分的高昂。不过经过研究表明,新生代对象大部分都是朝生夕死的,因此不需要按照 1:1 的比例来划分空间。商用的虚拟机 HotSpot 就是默认将内存划分为 8:1:1,即一块Eden区,两个Survivor区,在进行分配时,将 Eden 和一个 Survivor 直接复制到另一个 Survivor 即可,这样解决了复制算法空闲空间太大的问题,又提高了 GC 的效率。

但是也正是因为这样的划分,Survivor 的内存空间是比较小的,因此需要有一个其他内存进行分配担保,确保大对象也能够进行内存分配,这就老年代存在的价值之一。当另一块 Survivor 没有足够空间放置对象时,将会直接将对象分配至老年代。而老年代采取的 GC 算法为标记-整理算法。

分代算法

经过上面 3 种算法的分析,想必大家也想到了,分代算法其实并不是一个新算法,其只是根据前面算法的优劣将内存空间进行了划分,对每个不同的空间采取不同的算法,以便根据各个不同的年代采取不同的,最适合的算法。

在 Java8 之前,方法区称为永久代,也如同堆空间一样被 GC 进行管理,但在 Java8 之后,这种实现方式被MetaSpace 取代,采用直接内存的方式来进行内存分配管理.

Hotspot 将内存划分为新生代和老年代。新生代因为大部分的对象都是快节奏的,因此采用复制算法来处理。而老年代因为对象存活率高,且已经没有额外空间对齐进行分配担保了,因此采用标记-清理或标记-整理算法进行处理。

总结

在本文中,我们介绍了什么是垃圾回收,如何判断对象应该进行回收了,以及回收逻辑的几个不同抽象模型。在后面的文章,我们将对算法的具体实现进行探讨,了解当前业内主流的虚拟机实现,看看在实际生产情况下,不同的 垃圾收集器 的具体实现方式.

文章在公众号 “iceWang" 第一手更新,有兴趣的朋友可以关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯!

本系列文章主要借鉴自《深入分析 JavaWeb 技术内幕》和《深入理解 Java 虚拟机-JVM高级特性与最佳实践》。

JVM(九):垃圾回收算法的更多相关文章

  1. jvm详情——3、JVM基本垃圾回收算法回收策略

    JVM基本垃圾回收算法回收策略 引用计数(Reference Counting):比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只用收集计数为0的 ...

  2. JVM G1垃圾回收算法简要介绍

    JVM G1垃圾回收算法简要介绍 G1的特点 能够像CMS垃圾回收算法一样并发操作应用线程(潜台词:多核) 无需太长时间即可压缩空闲内存空间(潜台词:不会引起太多的GC停顿时间) 尽可能地让GC时长可 ...

  3. JVM常见垃圾回收算法

    jdk1.7.0_79 众所周知,Java是一门不用程序员手动管理内存的语言,全靠JVM自动管理内存,既然是自动管理,那必然有一个垃圾内存的回收机制或者回收算法.本文将介绍几种常见的垃圾回收(下文简称 ...

  4. jvm的垃圾回收算法

    一.对象存活判断判断对象是否存活一般有两种方式:1.引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收.此方法简单,无法解决对象相互循环引用的问题.2 ...

  5. 深入理解JVM一垃圾回收算法

    我们都知道java语言与C语言最大的区别就是内存自动回收,那么JVM是怎么控制内存回收的,这篇文章将介绍JVM垃圾回收的几种算法,从而了解内存回收的基本原理. 一.stop the world 在介绍 ...

  6. jvm学习-垃圾回收算法(三)

     垃圾回收算法  引用计数法 比较古老的一种垃圾回收算法.在java的GC并没有采用 增加一个引用 引用+1 减少一个引用引用减一 每次清除引用为0的的对象 缺点:不能回收循环引用的垃圾对象 标记清除 ...

  7. 深入探究JVM之垃圾回收算法实现细节

    @ 目录 前言 垃圾回收算法实现细节 根节点枚举 安全点 安全区域 记忆集和卡表 写屏障 并发的可达性分析 低延迟GC Shenandoah ZGC 总结 前言 本篇紧接上文,主要讲解垃圾回收算法的实 ...

  8. JVM中垃圾回收算法

    GC 算法与种类 GC的概念 Garbage Collection 垃圾收集1960年 List 使用了GCJava中,GC的对象是堆空间和永久区 引用计数法 老牌垃圾回收算法通过引用计算来回收垃圾使 ...

  9. 深入理解JVM(五) -- 垃圾回收算法

    上篇文章我们了解到哪些内存区域和哪些对象可以被回收,这篇文章我们就来了解一下具体的垃圾回收算法的思路,不讨论具体的实现. 一 最基础算法 标记-清除(Mark-Swap) 为什么说他是最基础的算法,因 ...

随机推荐

  1. Win10《芒果TV》更新v3.8.40青春版:优化推送策略、新增缓存清理

    芒果TV暑期重磅活动-青春芒果节拉开帷幕,炫酷的3D视觉大秀.王牌IP互动体验馆.众星云集的青春炙燥夜晚会.神秘的芒果吉祥物发布,Win10版<芒果TV>全平台同步更新青春版v3.8.40 ...

  2. 零元学Expression Blend 4 - Chapter 30 8个Expression Blend4的快捷

    原文:零元学Expression Blend 4 - Chapter 30 8个Expression Blend4的快捷 我针对工作区跟视窗的快捷键整理了八个Expression Blend4的快捷, ...

  3. Jquery 插件开发公开属性顺序的影响.

    如下代码拷贝能正常运行. (function ($) { $.fn.DemoPlugin = function (options) { var opts; opts = $.extend({}, $. ...

  4. 获得QQ聊天输入框中的内容

    // 首先得到输入框的句柄.通过spy++这类工具分析,聊天窗体的类名为“#32770”// 但当前系统里不只一个类名为“#32770”的窗体,这就需要全体遍历一次.// 类名为“#32770”标题含 ...

  5. 京东sdk商家上架接口调用问题总结

    前言: 最近在做商家发布产品,调用京东sdk,发现问题很多,而且还是在我同事的帮助下完成的,摸索中,菜鸟还请高手门多多提携才好,入正题 首先是引用jd的sdk啦,京东sdk中发布商品需要调用一个 36 ...

  6. Spectre小计

    今天安装了vs2017,而后又安装了wdk,随便写了一个控制台测试程序,居然报错.网上也查了一圈,也没有得到解决.报错内容如下: MSB8038:已启用Spectre缓解,但找不到Spectre缓解库 ...

  7. XML转义字符 如"&"

    解析数据 XML 解析器通常情况下会处理XML文档中的所有文本. 当XML元素被解析的时候,XML元素内部的文本也会被解析,例如: <message>Hello Word!</mes ...

  8. Spark学习之路(十六)—— Spark Streaming 整合 Kafka

    一.版本说明 Spark针对Kafka的不同版本,提供了两套整合方案:spark-streaming-kafka-0-8和spark-streaming-kafka-0-10,其主要区别如下:   s ...

  9. Hive学习之路(一)—— Hive 简介及核心概念

    一.简介 Hive是一个构建在Hadoop之上的数据仓库,它可以将结构化的数据文件映射成表,并提供类SQL查询功能,用于查询的SQL语句会被转化为MapReduce作业,然后提交到Hadoop上运行. ...

  10. Java NIO 学习笔记(三)----Selector

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...