深入浅出 JVM GC(1)
# 前言
初级 Java 程序员步入中级程序员的有一个无法绕过的阶段------GC(Garbage Collection)。作为 Java 程序员,说实话,很幸福,不用像 C 程序员那样,时刻关心着内存,就像网上有句名言------生活从来都不容易,只不过是有人替你负重前行!是的,GC 在替我们做这些脏活累活,GC 像让我们把精力都放在业务上,而不用每时每刻都在想着内存。现在,GC 也是每个语言的标准配置了。不然谁会去使用这个语言呢?
然而,作为一个合格的程序员,对底层的好奇是进步的动力,如果一个程序员失去了好奇心,那就可以说他在程序员这条道路上就结束了。
难道我们不好奇 GC 到底是怎么做的吗?接下来,我们就分析 GC 做了哪些事情。
实际上,GC 主要做3件事情:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
说到底,GC 就是做这3件事情,如果你能解决这3个问题,那么你也可以实现一个 GC。
那我们就一个一个问题来看看。
1. 哪些内存需要回收
还记得我们之前分享的关于 JVM 运行时数据区吗?有堆,有栈,有方法区(永久代),还有直接内存,还有 PC 寄存器。其中,GC 的主要战场就是堆,当然,方法区也是需要 GC 的。但重点还是堆。
我们知道,堆中内存是共享的,基本所有的对象都是在堆中创建。当一个对象不需要使用了,理论上我们就需要释放他所占用的内存。
问题来了,如何分辨一个对象不需要使用了呢?答案是:不可能被任何途径使用的对象。也就是说他没有了任何引用。我们知道,引用在栈中,实例在堆中,当一个实例没有了指向他的引用,我们认为,这个实例就需要清除并释放他所占用的内存了。
那么 GC 是如何实现的呢?一般而言有2种方法:
- 引用计数法(有缺陷,无法解决循环引用问题,JVM 没有采用)
- 可达性分析(解决了引用计数的缺陷,被 JVM 采用)
什么是引用计数法呢?
给对象中添加一个引用计数器,每当有一个地方引用他是,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能在被使用的。
虽然乍看这个算法简单,效率也高,但有一个问题这个算法无法解决,就是循环引用。试想一下:A 对象引用了 B,B 对象也引用了 A,但 A 和 B 都不被别的地方使用,也就是说,实际上这两个对象是垃圾对象,但是由于他们互相持有引用,导致他们的引用计数器都不为0,因此系统无法判断是垃圾,也无法回收他们。
所以,在现在的 JVM 中,是没有使用这个算法的。我们知道就行。
引用计数法不行,那就再说说可达性分析算法。
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,所有所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连(也就是对象不可达)时,则证明此对象是不可用的。如下图所示,obj5 , obj6, obj7 虽然互相有关联,但是他们到 GC Roots 是不可达的,所以他们将会判定为是可回收的对象。

那么哪些对象可以作为 GC Roots 对象呢?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI (即 native 方法)引用的对象。
2. 什么时候回收?
注意:即使是在可达性分析算法中不可达的对象,也并非是"非死不可的",这时候他们实际上是处于 “缓刑” 阶段。因为要真正宣告一个对象的死亡,至少需要经历两次标记过程:
> 如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。注意:当对象没有覆盖 finalize 方法,或者 finalize 方法已经被虚拟机调用过,虚拟机将这两种情况都视为 “没有必要执行”。也就是说,finalize 方法只会被执行一次。
如果这个对象被判定为有必要执行 finalize 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在稍后由一个虚拟机自动建立的,低优先级的 Finalizer 线程去执行它。注意:如果一个对象在 finalize 方法中运行缓慢,将会导致队列后的其他对象永远等待,严重时将会导致系统崩溃。
finalize 方法是对象逃脱死亡命运的最后一道关卡。稍后 GC 将对队列中的对象进行第二次规模的标记,如果对象要在 finalize 中 “拯救” 自己,只需要将自己关联到引用上即可,通常是 this。如果这个对象关联上了引用,那么在第二次标记的时候他将被移除出 “即将回收” 的集合;如果对象这时候还没有逃脱,那基本上就是真的被回收了。
这里需要注意的一点就是:一个对象如果重写了 finalize 方法,那么这个方法最多只会被执行一次。
建议:如非必要,不要重写该方法。可以使用 try-finally 代替,此方式更好,更及时。同时注意:在 Mysql 的 JDBC 驱动中,com.mysql.jdbc.ConnectionImpl 就实现了 finalize 方法,作用是:当一个 JDBC Connection 被回收时,需要进行连接的关闭,如果开发人员忘记了关闭,则在 finalize 方法中进行关闭。但是,由于其调用的不确定性,这不能单独作为可靠的资源回收手段。
到这里,我们知道了什么时候进行回收:如果一个对象重写了 finalize 方法且这个方法没有被 JVM 调用过,那么这个对象会被放入一个队列等待被一个低优先级的线程执行 finalize 方法,如果在这个方法中对象不能自救,则这个对象在第二次标记过程中就会被标记死亡,等待 GC 回收。
3. 如何回收?
如何回收,这个问题非常的大,涉及到各种垃圾回收算法,各种垃圾收集器。限于本篇的篇幅,楼主将不会在这篇文章里深入探讨,这里只会列出一些大纲,这些大纲将是后面文章的摘要,我们将在后面的文章中深入探讨如何回收。
那么,有哪些摘要呢?
3.1 垃圾回收算法
- 标记清除算法
- 复制算法
- 标记整理算法
- 分代收集算法(堆如何分代)
这些算法是 GC 的基础,所有 GC 的实现都是基于这些算法来清除无用对象,然后释放内存空间。我们将会在后面的文章一个一个讲解。
3.2 有哪些垃圾收集器
- Serial 串行收集器(只适用于堆内存256m 一下的 JVM )
- ParNew 并行收集器(Serial 收集器的多线程版本)
- Parallel Scavenge (PS 收集器,该收集器以吞吐量为主要目的,是1.8的默认 GC)
- CMS 收集器(该收集器全称 Concurrent Mark Sweep,是一种关注最短停顿时间的垃圾收集器)
- G1 收集器(JDK 9 的默认 GC)
3.3 有哪些GC
- Young GC(又称 YGC,minor GC,年轻代 GC)
- Old GC (老年代 GC,只有 CMS 才会单独回收 Old 区)
- Full GC(又称 major GC)
- Mixed GC(混合 GC,G1 收集器独有)
好,以上就是如何回收的大纲,我们将在后面的文章中慢慢讲解。
总结
这篇文章主要总结了什么是 GC ,以及 GC 的作用,GC 主要做了3件事情,哪些内存需要回收,什么时候回收,如何回收。我们知道了 GC 通过可达性分析知道了哪些内存需要回收,那什么时候回收呢?执行 finalize 方法后如果还没有复活,将被回收。第三个问题:如何回收呢?这个问题是一个大课题,我们只是列出了一些大纲,比如有哪些垃圾收集器,有哪些垃圾算法,有哪些 GC 过程。这些细节我们将在后面慢慢讲解,逐步深入。
深入浅出 JVM GC(1)的更多相关文章
- 深入浅出 JVM GC(3)
# 前言 在 深入浅出 JVM GC(2) 中,我们介绍了一些 GC 算法,GC 名词,同时也留下了一个问题,就是每个 GC 收集器的具体作用.有哪些 GC 收集器呢? Serial 串行收集器(只适 ...
- 深入浅出 JVM GC(2)
# 前言 在 深入浅出 JVM GC(1) 中,限于上篇文章的篇幅,我们留下了一个问题 : 如何回收? 这篇文章将重点讲述这个问题. 在上篇文章中,我们也列出了一些大纲,今天我们就按照那个大纲来逐个讲 ...
- 深入浅出 JVM GC(4)常用 GC 参数介绍
# 前言 从前面的3篇文章中,我们分析了5个垃圾收集器,还有一些 GC 的算法,那么,在 GC 调优中,我们肯定会先判断哪里出现的问题,然后再根据出现的问题进行调优,而调优的手段就是 JVM 提供给我 ...
- Java性能优化之JVM GC(垃圾回收机制)
Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.st ...
- 如何避免后台IO高负载造成的长时间JVM GC停顿(转)
译者著:其实本文的中心意思非常简单,没有耐心的读者建议直接拉到最后看结论部分,有兴趣的读者可以详细阅读一下. 原文发表于Linkedin Engineering,作者 Zhenyun Zhuang是L ...
- 【转载】Java性能优化之JVM GC(垃圾回收机制)
文章来源:https://zhuanlan.zhihu.com/p/25539690 Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我 ...
- JVM GC机制
垃圾收集主要是针对堆和方法区进行. 回收机制: 现在的JVM基本都使用分代回收机制,把堆中内存区域分为新生代,老年代. 新生代: Eden(80%) Survivor0(10%) Survivor1( ...
- JVM 自带性能监测调优工具 (jstack、jstat)及 JVM GC 调优
1. jstack:占用最多资源(CPU 内存)的Java代码 https://www.cnblogs.com/chengJAVA/p/5821218.html https://blog.csdn.n ...
- 理解JVM GC
理解JVM GC对于我们把控Java应用有很大的帮助.下面我从运维角度,把网上的JVM相关的资料整理如下,以加深对JVM GC的理解.如有错误的地方,请看官指正. JVM内存使用分类 JVM的内存分区 ...
随机推荐
- Python Day 14 迭代器、for循环原理、枚举、生成器
阅读内容 内容回顾 带参装饰器和wraps用法 迭代器知识引入 可迭代对象 迭代器对象 for循环迭代器 枚举对象 生成器 ##内容回顾 函数的嵌套定义:在函数内部定义另一 ...
- lua的table转为excel表格的方法
项目中需要用到转表工具,由于没有直接的转表工具,而且嵌套的table(table里面嵌套了多层表格与数组).无奈之下,只好采用折衷的方法,先将table表格转为json数据,再用在线转表工具将json ...
- [Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creator【s
问题:执行创建函数的sql文件报错如下: [Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA ...
- Altera 在线资源使用
Altera 在线资源使用 Altera 在线资源使用 1 1.Altera中文版 2 2.建立myaltera账户 获取官网信息与支持 2 3系统化的设计资源 2 3.1.设计实例 2 3.2.参考 ...
- Scala数组小结
1.定长数组 定长数组:指长度不可变的数组Array. 第一种方式: 先声明一个数组,后初始化该数组: scala> val array = new Array[Double](5) array ...
- 编译与解释(java)
计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序. 翻译的方式有两种,一个是编译,一个是解释.两种方式只是翻译的时间不同 计算机不 ...
- flask_SQLALchemy之多表查询
1. join 查询 假设这样一个业务场景,知道一个邮箱地址,要查询这个地址所属的用户,第一个办法是用连接多个 filter() 来查询. for u, a in session.query(User ...
- 深圳scala-meetup-20180902(2)- Future vs Task and ReaderMonad依赖注入
在对上一次3月份的scala-meetup里我曾分享了关于Future在函数组合中的问题及如何用Monix.Task来替代.具体分析可以查阅这篇博文.在上篇示范里我们使用了Future来实现某种non ...
- 使用cygwin中的awk工具进行mysql binlog日志查看[利刃篇]
linux工具确实强悍,然而作为没有linux机器使用权以及开发没有使用linux进行的人,有时想用一些命令确实不方便,所以,才去试着用用cygwin,一款在windows平台上运行的类UNIX模拟环 ...
- Java的组合排列问题
从4个人中选2个人参加活动,一共有6种选法. 从n个人中选m个人参加活动,一共有多少种选法?C(m/n)=C((m-1)/(n-1))+C(m/(n-1))数学算法 public class Main ...