前言

文章来源:https://studyidea.cn/

GC 中文直译垃圾回收,是一种回收内存空间避免内存泄漏的机制。当 JVM 内存紧张,通过执行 GC 有效回收内存,转而分配给新对象从而实现内存的再利用。 JVM GC 机制虽然无需开发主动参与,减轻不少工作量,但是某些情况下,自动 GC 将会导致系统性能下降,响应变慢,所以这就需要我们提前了解掌握 GC 机制。当面对这种情况时,才能从容不迫的解决问题。另外 GC 机制也是 Java 面试高频考题,了解掌握 GC 是一项必备技能。

学习 GC ,首先我们解决三个问题:

  • 什么是垃圾
  • 在哪里回收垃圾
  • 怎么回收垃圾

什么是垃圾

我们先来看一段简单的代码。

上面代码通过将字符串对象转化成字节数组,然后写入本地文件。方法一旦开始执行,就将会在分配一定内存给新建的对象,然后将引用告诉了strbytes 变量。等到方法执行完毕,方法内部局部变量紧接将就会被销毁。但是这样仅仅销毁了局部变量,却没有带走内存上这些实际的对象。这类不再起作用,没有被引用的对象,将其归类为垃圾。

在偌大的内存上存活着无数对象,GC 之前需要准确将这些对象标记出来,分为存活对象与垃圾对象。这个过程一旦少标记,那就只能等待下次 GC标记,再回收,这样将会影响 GC 效率。另外决不能错标记,将正常存活对象标记为垃圾。一旦回收正常存活的对象,可能就会引起程序各种崩溃。

目前有两种算法可以用来标记:

  • 引用计数法
  • 可达性分析法

引用计数法

引用计数法通过在对象头分配一个字段,用来存储该对象引用计数。一旦该对象被其他对象引用,计数加 1。如果这个引用失效,计数减 1。当引用计数值为 0 时,代表这个对象已不再被引用,可以被回收。

如上图所示,当 str 引用堆中对象时,计数值增加为 1。当 str 变为 null 时,既不再引用该对象,计数值减 1。此时该对象就可以被 GC 回收。

引用计数法只需要判断计数值,所以实现比较简单,这个过程也比较高效。但是存在一个很严重的问题,无法解决对象循环引用问题。

从上图可以看到, a,b 不再引用堆中对象,导致计数减一。此时两个对象内部还存在互相引用,计数值不为 0,此时 GC 没办法回收该对象。

可达性分析法

这个算法首先需要按照规则查找当前活跃的引用,将其称为 GC Roots。接着将 GC Roots 作为根节点出发,遍历对象引用关系图,将可以遍历(可达)的对象标记为存活,其余对象当做无用对象。

注意这里是是引用,而不是对象。

从上图可以看到,绿色对象虽然存在循环引用,但是由于这些对象不能被 GC Roots 遍历到,所以将会被回收。

可以被当做GC Roots 活跃引用包括但不限于以下引用:

  • 方法中局部变量
  • 静态变量,常量
  • JNI handles
  • ....

在哪里回收垃圾

还记得刚开始接触 Java 时,只知道堆栈,对象实例分配在堆中,方法中局部变量位于栈中。实际上 JVM 内存区域划分更加细致,分为:

  • 方法区
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

如图所示,我们将内存划分为线程私有与线程共享的区域。方法区与堆都是线程共享的区域,这两部分占用 JVM 大部分内存,剩下三个小弟将会跟线程绑定,随着线程消亡,自动将会被 JVM 回收。

堆应该是大家最熟悉的一块区域,几乎所有对象实例都将会在此出生,通常也是虚拟机上占用内存最大一块区域,简直就是 JVM 内存中的大哥大。堆内存内部也不是简简单单一块而已,目前将会根据分代算法,将堆分代,不同对象位于不同区域。这一点我们下文再详细了解。

方法区

方法区将会保存已被虚拟上加载的类信息、常量,静态变量,字节码等信息,堆上的对象正式通过方法区这些信息,才能正确创建出来。

虚拟机栈栈由一系列栈帧组成,每个栈帧其实代表一个方法,栈帧中将会保存一个方法的局部变量表,方法出入口信息,操作栈等。每当调用一个方法,就将会把这个栈帧压入栈中,执行结束,出栈。

本地方法栈与虚拟机栈比较类似,最大区别在于,虚拟机栈执行的 Java 方法,而本地方法栈将会用来执行 Native 方法服务。下面方法就会在本地方法栈中执行。

public static native void arraycopy(Object src,  int  srcPos,
Object dest, int destPos,
int length);

程序计数器

程序计算器可以说是这几块区域占用最小的一部分,但是功能却十分重要。Java 源代码通过编译变成字节码,然后被 JVM 载入运行之后,将会变成一条条指令,而程序计数器的工作就是告诉当前线程下一条需要执行指令。这样即使发生了线程切换,等待恢复的时候,当前线程依然知道接下去要执行的指令。

怎么回收

目前主流 GC 算法主要分为三种:

  • 标记-清除算法
  • 复制算法
  • 标记-整理算法

标记-清除算法

这是一个最为基础也是最容易实现的算法,主要实现步骤分为两步:标记,清除。

  • 标记:通过上述 GC Roots 标记出可达对象。
  • 清除:清理未标记对象

ps:这个图着实难画啊。。。。

可以看到经过这个算法回收之后,虽然堆空间被清理出来,但是也产生很多空间碎片。这就会导致一个新对象根据堆剩余容量计算,看起来是可以分配,但是实际分配过程,由于没有连续内存,导致虚拟机感知到内存不足,又不得不提前再次触发 GC

可能这里你就会有疑惑,为什么对象需要分配一块连续的内存?

这里引用一下 R 神 @RednaxelaFX 答案。

另外这个算法还有一个不足:标记与清除效率比较低。这就竟会导致 GC 占用时间过长,影响正常程序使用。

复制算法

为了解决上述效率问题,诞生复制算法。这个算法将可用内存分为两块,每次只使用其中一块,当这一块内存使用完毕,触发 GC ,将会把存活的对象依次复制到另外一块上,然后再把已使用过的内存一次性清理。

这个算法每次只需要操作一半内存,GC 回收之后也不存在任何空间碎片,新对象内存分配时只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是这个算法闲置一半内存空间,空间利用效率不高。

PS:复制算法以空间换时间,两者不可兼得

另外对象存活率也会影响复制算法效率。如果对象大部分都是朝生夕死,只需要移动少量存活对象,就能腾出大部分空间。反而如果对象存活率高,这就需要进行较多的复制操作,回收之后也并没有多余内存,这就可能导致频繁触发 GC

针对这种存活时间长的对象,就需要使用标记-整理算法。

标记-整理算法

标记-整理算法可以说是标记-清除算法的改进版,改进了清除导致的空间碎片问题。这个算法分为两步:

  • 标记:也是通过 GC Roots 标记存活对象。
  • 整理:将存活对象往一端移动,按照内存地址一次排序,然后将末端边界之外内存直接清理。

虽然标记-整理算法解决了标记-清除算法空间碎片问题,也完整利用整个内存空间,但是这个算法问题效率并不高。相较于标记-清除算法,标记-整理算法多增加整理这一步,所以该算法效率还低于标记-清除算法。

分代收集算法

从上面三种 GC 算法可以看到,并没有一种空间与时间效率都是比较完美的算法,所以只能做的是综合利用各种算法特点将其作用到不用的内存区域。

目前商业虚拟机根据对象存活周期不同划分内存区域,一般分为新生代,老年代。新对象一般情况都会优先分配在新生代,新生代对象若存活时间大于一定阈值之后,将会移到至老年代。新生代的对象都是短命鬼,老年代的对象都是长寿先生。

新生代每次 GC 之后都可以回收大批量对象,所以比较适合复制算法,只需要付出少量复制存活对象的成本。这里内存划分并没有按照 1:1 划分,默认将会按照 8:1:1 划分成 Eden 与两块 Survivor空间。每次使用 Eden 与一块Survivor空间,这样我们只是闲置 10% 内存空间。不过我们每次回收并不能保证存活对象小于 10%,在这种情况下就需要依靠老年代的内存分配担保。当Survivor空间并不能保存剩余存活对象,就将这些对象通过分配担保进制移动至老年代。

老年代中对象存活率将会特别高,且没有额外空间进行分配担保,所以并不适合复制算法,所以需要使用标记-清除或标记-整理算法。

随便聊聊

最近又到一年一次大考的时候,不得不又拿起周志明『深入 Java 虚拟机』重新学习。还记得第一次翻看这本书的时候,大半内容看不懂,看完也很快就忘了。然后过了一段时间,又重新拿起此书,这次比上次好,也已经能看小大半了。最近跟一些小伙伴聊天,发现他们都是看这本书学习 JVM ,不得不说这本书真是一本神书。最近『深入 Java 虚拟机』第三版即将上架开售,有需要的小伙伴可以考虑入手了。

好了 ,GC 机制就就总结到这里,下一篇我们来聊聊 JVM 常用 GC 回收器。

帮助链接

GC Roots

Java虚拟机详解04----GC算法和种类

深入 Java 虚拟机

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn

直击面试,聊聊 GC 机制的更多相关文章

  1. 面试7 GC机制中如何判断一个对象是否任在使用

    GC 通过在使用的根引用遍历所有引用的对象实例,当一个对象不能被遍历时,将被视为不能被使用.

  2. 聊聊面试中常问的GC机制

    GC 中文直译垃圾回收,是一种回收内存空间避免内存泄漏的机制.当 JVM 内存紧张,通过执行 GC 有效回收内存,转而分配给新对象从而实现内存的再利用. JVM GC 机制虽然无需开发主动参与,减轻不 ...

  3. 013 GC机制

    本文转自:https://www.cnblogs.com/shudonghe/p/3457990.html 最近还是在找工作,在面试某移动互联网公司之前认为自己对Java的GC机制已经相当了解,其他面 ...

  4. Java 内存区域和GC机制分析

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

  5. 从C#垃圾回收(GC)机制中挖掘性能优化方案

    GC,Garbage Collect,中文意思就是垃圾回收,指的是系统中的内存的分配和回收管理.其对系统性能的影响是不可小觑的.今天就来说一下关于GC优化的东西,这里并不着重说概念和理论,主要说一些实 ...

  6. 浅谈你感兴趣的 C# GC 机制底层

    本文内容是学习CLR.via C#的21章后个人整理,有不足之处欢迎指导. 昨天是1024,coder的节日,我为自己coder之路定下一句准则--保持学习,保持自信,保持谦逊,保持分享,越走越远. ...

  7. GC基本算法及C++GC机制

    前言 垃圾收集器是一种动态存储分配器,它自动释放程序不再需要的已分配的块,这些块也称为垃圾.在程序员看来,垃圾就是不再被引用的对象.自动回收垃圾的过程则称为垃圾收集(garbage collectio ...

  8. Java 内存区域和GC机制

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

  9. Java系列笔记(3) - Java 内存区域和GC机制

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

随机推荐

  1. 斐波那契数列的实现(C语言)

    int fibonacci(int positon){ if(position==1||position==2){ return 1; } return fibonacci(position-1)+f ...

  2. Netty学习篇⑤--编、解码

    前言 学习Netty也有一段时间了,Netty作为一个高性能的异步框架,很多RPC框架也运用到了Netty中的知识,在rpc框架中丰富的数据协议及编解码可以让使用者更加青睐: Netty支持丰富的编解 ...

  3. java property 配置文件管理工具框架,避免写入 property 乱序

    property property 是 java 实现的 property 框架. 特点 优雅地进行属性文件的读取和更新 写入属性文件后属性不乱序 灵活定义编码信息 使用 OO 的方式操作 prope ...

  4. /proc/cpuinfo文件解读(超易理解)

    在linux系统中,提供了/proc目录下文件,显示系统的软硬件信息.如果想了解系统中CPU的提供商和相关配置信息,则可以查/proc/cpuinfo.但是此文件输出项较多,不易理解.例如我们想获取, ...

  5. supervisor 安装配置详解

    一.安装 源码安装 先下载最新的supervisor安装包:https://pypi.python.org/pypi/supervisor , 如: (python3命令为 pip install g ...

  6. day20191009jdbc学习笔记

    周三Wednesday JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Ja ...

  7. ##* %%* linux变量处理

    链接来自他们分享,,,, 如有侵权,请联系本人删除,本人将立即删除.停止分享. https://blog.csdn.net/fengzijinliang/article/details/4252021 ...

  8. 英语口语考试资料Volunteers

    Being a volunteer is great!        There are lots of volunteers around us now. And they don’t do it ...

  9. HTML 创建按钮实现跳转链接

    1.使用 form<form method="get" action="/page2"> <button type="submit& ...

  10. 设置更改root密码、连接mysql、mysql常用命令

    6月19日任务 13.1 设置更改root密码13.2 连接mysql13.3 mysql常用命令 13.1 设置更改root密码 使用场景:例如长时间不用忘记了mysql的root密码,那么就需要去 ...