前言

文章来源: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. MySQL InnoDB 实现高并发原理

    MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...

  2. nyoj 599-奋斗的小蜗牛 (double ceil(); (temp - 1) / 5)

    599-奋斗的小蜗牛 内存限制:64MB 时间限制:1000ms 特判: No 通过数:0 提交数:96 难度:1 题目描述: 传说中能站在金字塔顶的只有两种动物,一种是鹰,一种是蜗牛.一只小蜗牛听了 ...

  3. Vue2.x与bootsrap-table动态添加元素和绑定事件无效

    一.问题:     最近在使用vue与bootstrap-table结合生成表格时,按以前的经验----每列数据可用formatter:function(value,row,index){}进行一些其 ...

  4. 手把手带你一键部署 Kubernetes + KubeSphere 至 Linux

    本文介绍一个最快安装 Kubernetes 和体验 KubeSphere 核心功能的方式,all-in-one 模式可一键安装 Kubernetes v1.15.5 和 KubeSphere 至一台 ...

  5. 作业要求20191107-6 beta week 2/2 Scrum立会报告+燃尽图 05

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/9958 一.小组情况 队名:扛把子 组长:孙晓宇 组员:宋晓丽 梁梦瑶 韩 ...

  6. 浅谈.NET中的反射

    一.概述  1.通过反射可以提供类型信息,从而使得我们开发人员在运行时能够利用这些信息构造和使用对象 2.反射机制允许程序在执行过程中动态地添加各种功能 二.运行时类型标识 1.运行时类型标志(RTT ...

  7. 《程序人生》系列-害敖丙差点被开除的P0事故

    你知道的越多,你不知道的越多 点赞再看,养成习惯 GitHub https://github.com/JavaFamily上已经收录有一线大厂面试点脑图.个人联系方式和技术交流群,欢迎Star和指教 ...

  8. k8s 上使用 StatefulSet 部署 zookeeper 集群

    目录 StatefulSet 部署 zookeeper 集群 创建pv StatefulSet 测试 StatefulSet 部署 zookeeper 集群 参考 k8s官网zookeeper集群的部 ...

  9. 利用scatter()绘制颜色映射的二次方曲线

    程序如下: import matplotlib.pyplot as plt x_value = list(range(1, 1001)) y_value = [x**2 for x in x_valu ...

  10. ant 自动化构建环境。

    ant  一个自动化构建的工具 可以帮你实现自动化打包和发布到web环境上. 项目目录如下: 项目一:common-framework 项目二:estun-platform-web 其中项目一是项目二 ...