Hello,大家好,我是Steafan,在经历了惊心动魄的阿里电面之后,我又回来了。

今天我们来谈谈Java主流虚拟机-HotSpot的GC实现机制,本篇文章默认使用HotSpot虚拟机进行介绍,如果没有特殊说明,其都为HotSpot虚拟机中的特性。

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围城的“高墙”,墙外面的人想进去,墙里面的人却想出来。

说起垃圾收集,大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史比Java久远,1960年诞生与MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。关于Garbage Collection的历史这里就不多说了,因为这是一篇技术博客而不是来将历史的,如果对GC的发展历史感兴趣可以自行百度。

一、GC实现机制-我们为什么要去了解GC和内存分配?

说道这个问题,我有一个简单的回答:在真实工作中的项目中,时不时的会发生内存溢出、内存泄露的问题,这也是不可避免的Bug,这些潜在的Bug在某些时候会影响到项目的正常运行,如果你的项目没有合理的进行业务内存分配,将会直接影响到的项目的并发处理,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节,而了解了GC实现机制则是我们一切监控和调节的前提。

二、GC实现机制-Java虚拟机将会在什么地方进行垃圾回收?

说起垃圾回收的场所,了解过JVM(Java Virtual Machine Model)内存模型的朋友应该会很清楚,堆是Java虚拟机进行垃圾回收的主要场所,其次要场所是方法区。

三、GC实现机制-Java虚拟机具体实现流程

我们都知道在Java虚拟机中进行垃圾回收的场所有两个,一个是堆,一个是方法区。在堆中存储了Java程序运行时的所有对象信息,而垃圾回收其实就是对那些“死亡的”对象进行其所侵占的内存的释放,让后续对象再能分配到内存,从而完成程序运行的需要。关于何种对象为死亡对象,在下一部分将做详细介绍。Java虚拟机将堆内存进行了“分块处理”,从广义上讲,在堆中进行垃圾回收分为新生代(Young Generation)和老生代(Old Generation);从细微之处来看,为了提高Java虚拟机进行垃圾回收的效率,又将新生代分成了三个独立的区域(这里的独立区域只是一个相对的概念,并不是说分成三个区域以后就不再互相联合工作了),分别为:Eden区(Eden Region)、From Survivor区(Form Survivor Region)以及To Survivor(To Survivor Region),而Eden区分配的内存较大,其他两个区较小,每次使用Eden和其中一块Survivor。Java虚拟机在进行垃圾回收时,将Eden和Survivor中还存活着的对象进行一次性地复制到另一块Survivor空间上,直到其两个区域中对象被回收完成,当Survivor空间不够用时,需要依赖其他老年代的内存进行分配担保。当另外一块Survivor中没有足够的空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老生代,在老生代中不仅存放着这一种类型的对象,还存放着大对象(需要很多连续的内存的对象),当Java程序运行时,如果遇到大对象将会被直接存放到老生代中,长期存活的对象也会直接进入老年代。如果老生代的空间也被占满,当来自新生代的对象再次请求进入老生代时就会报OutOfMemory异常。新生代中的垃圾回收频率高,且回收的速度也较快。就GC回收机制而言,JVM内存模型中的方法区更被人们倾向的称为永久代(Perm Generation),保存在永久代中的对象一般不会被回收。其永久代进行垃圾回收的频率就较低,速度也较慢。永久代的垃圾收集主要回收废弃常量和无用类。以String常量abc为例,当我们声明了此常量,那么它就会被放到运行时常量池中,如果在常量池中没有任何对象对abc进行引用,那么abc这个常量就算是废弃常量而被回收;判断一个类是否“无用”,则需同时满足三个条件:

(1)、该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;

(2)、加载该类的ClassLoader已经被回收

(3)、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述3个条件的无用类进行回收,这里说的是可以回收而不是必然回收。

大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;同理,当老年代中没有足够的内存空间来存放对象时,虚拟机会发起一次Major GC/Full GC。只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full CG。

虚拟机通过一个对象年龄计数器来判定哪些对象放在新生代,哪些对象应该放在老生代。如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将该对象的年龄设为1。对象每在Survivor中熬过一次Minor GC,年龄就增加1岁,当他的年龄增加到最大值15时,就将会被晋升到老年代中。虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中所有相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

四、GC实现机制-Java虚拟机如何实现垃圾回收机制

(1)、引用计数算法(Reference Counting)

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的,这就是引用计数算法的核心。客观来讲,引用计数算法实现简单,判定效率也很高,在大部分情况下都是一个不错的算法。但是Java虚拟机并没有采用这个算法来判断何种对象为死亡对象,因为它很难解决对象之间相互循环引用的问题。

public class ReferenceCountingGC{public Object object = null;private static final int OenM = 1024 * 1024;private byte[] bigSize = new byte[2 * OneM];public static void testCG(){ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.object = null;objB.object = null;System.gc();
}
}

在上述代码段中,objA与objB互相循环引用,没有结束循环的判断条件,运行结果显示Full GC,就说明当Java虚拟机并不是使用引用计数算法来判断对象是否存活的。

(2)、可达性分析算法(Reachability Analysis)

这是Java虚拟机采用的判定对象是否存活的算法。通过一系列的称为“GC Roots"的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为饮用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。可作为GC Roots的对象包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象。本地方法栈JNI引用的对象。

在上图可以看到GC Roots左边的对象都有引用链相关联,所以他们不是死亡对象,而在GCRoots右边有几个零散的对象没有引用链相关联,所以他们就会别Java虚拟机判定为死亡对象而被回收。

五、GC实现机制-何为死亡对象?

Java虚拟机在进行死亡对象判定时,会经历两个过程。如果对象在进行可达性分析后没有与GC Roots相关联的引用链,则该对象会被JVM进行第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,如果当前对象没有覆盖该方法,或者finalize方法已经被JVM调用过都会被虚拟机判定为“没有必要执行”。如果该对象被判定为没有必要执行,那么该对象将会被放置在一个叫做F-Queue的队列当中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它,在执行过程中JVM可能不会等待该线程执行完毕,因为如果一个对象在finalize方法中执行缓慢,或者发生死循环,将很有可能导致F-Queue队列中其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。如果在finalize方法中该对象重新与引用链上的任何一个对象建立了关联,即该对象连上了任何一个对象的引用链,例如this关键字,那么该对象就会逃脱垃圾回收系统;如果该对象在finalize方法中没有与任何一个对象进行关联操作,那么该对象会被虚拟机进行第二次标记,该对象就会被垃圾回收系统回收。值得注意的是finaliza方法JVM系统只会自动调用一次,如果对象面临下一次回收,它的finalize方法不会被再次执行。

六、再探GC实现机制-垃圾收集算法

(1)、标记-清楚算法(Mark-Sweep)

用在老生代中, 先对对象进行标记,然后清楚。标记过程就是第五部分提到的标记过程。值得注意的是,使用该算法清楚过后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

.      (2)、复制算法(Copying)

用在新生代中,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另外一块上,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。

七、空间分配担保策略-GC过程中的内存担保机制

当出现大量对象在Minor GC后仍然存活的情况,就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。与生活中的银行贷款类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会存活下来在实际完后才能内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。如果出现担保失败,就只好重新发起一次Full GC来进行内存的分配。

深入理解Java-GC机制相关推荐

  1. 老年代的更新机制_如何理解Java GC机制

    Java GC机制 简单来说就干了3件事 确定哪些内存需要回收,确定什么时候需要执行GC,如何执行GC 确定哪些内存需要回收 垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还&quo ...

  2. java gc机制新区域旧屋_Java 内存回收机制——GC机制-Go语言中文社区

    一.Java GC 概念说明 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾 ...

  3. 98. 我说说你对Java GC机制的理解?

    我说说你对Java GC机制的理解? 写在前面 JVM的运行数据区 1.方法区 2.堆( Heap) 3.栈( Stack) 4.PC寄存器 5.本地方法栈 初识GC Marking Normal D ...

  4. java事件处理模型_从零开始理解JAVA事件处理机制(3)

    我们连续写了两小节的教师-学生的例子,必然觉得无聊死了,这样的例子我们就是玩上100遍,还是不知道该怎么写真实的代码.那从本节开始,我们开始往真实代码上面去靠拢. 事件最容易理解的例子是鼠标事件:我们 ...

  5. 从零开始理解JAVA事件处理机制(3)

    我们连续写了两小节的教师-学生的例子,必然觉得无聊死了,这样的例子我们就是玩上100遍,还是不知道该怎么写真实的代码.那从本节开始,我们开始往真实代码上面去靠拢. 事件最容易理解的例子是鼠标事件:我们 ...

  6. 理解Java ClassLoader机制 |用Java说话,人气战胜时间!Come On

    理解Java ClassLoader机制 |用Java说话,人气战胜时间!Come On 我在参加一个比赛. 欢迎大家都来我的网站参观一下. http://home.fego.cn/members/l ...

  7. 秒懂系列,深入理解Java反射机制

    文章目录 深入理解Java反射机制 一.Java反射概述 二.Java创建对象思想传递 三.类对象 3.1 类对象和类的对象 3.2 三种获取类对象的方法 四.Class对象的常用方法 4.1 获取成 ...

  8. 理解Java反射机制

    理解Java反射机制 1. 概述 2. 反射原理 3. 反射的优缺点 4. 反射的用途 5. 反射相关的类 5.1 Constructor 5.2 Field 5.3 Method 5.4 Class ...

  9. 给我说说你对Java GC机制的理解?

    写在前面 使用Java快一年时间了,从最早大学时候对Java的憎恶,到逐渐接受,到工作中体会到了Java开发的各种便捷与福利,这确实是一门不错的开发语言.不仅是 Intellij开发Java程序的爽快 ...

  10. 【98期】面试官:给我说说你对Java GC机制的理解?

    程序员的成长之路 互联网/程序员/技术/资料共享 关注 阅读本文大概需要 5.5 分钟. 来自:windblog.cn/java/2019/03/27/java-gc-learning/ 写在前面 使 ...

最新文章

  1. Spring Cloud - Feign调用问题
  2. Python培训教程分享:Python中选择结构是什么
  3. java开发培训中消息中间件的优势有哪些
  4. Windows Server 2008 R2 如何启动内核调试
  5. Leetcode 202. 快乐数 解题思路及C++实现
  6. 你写的代码一点都不 Pythonic
  7. Java黑皮书课后题第2章:2.18(打印表格)编写程序,显示下面的表格,将浮点数值类型转化为整数
  8. 物联网网关linux带串口,物联网网关|串口转HTTP GET协议
  9. sql developer Oracle 数据库 用户对象下表及表结构的导入导出
  10. android dalvik虚拟机 大小段问题
  11. 基于Arduino的循迹小车
  12. 关于安卓设备更改和烧录IMEI的图文教程
  13. 本特利3500软件组态指导
  14. 【ENVI】shp文件裁剪需要注意的问题
  15. PowerDesign安装教程
  16. 美联储印钞,中国物价为何上涨?
  17. Allegro中Change铜皮至其他层
  18. VxWorks6.8串口示例
  19. PHP 编写“九九乘法表”
  20. Excel 文字转拼音

热门文章

  1. 计算机加域后数据库无法登录,[MDT] 解决因加域客户端 Windows 登录身份引发的无法打开登录所请求的数据库故障...
  2. Python获取屏幕坐标,自动发送信息
  3. hadoop 常用命令
  4. 【Rust日报】2021-10-06 [Rust游戏] - 自走棋
  5. 【微波技术与电路】02 有界空间的微波
  6. 一千本免费电子书(建议长期保存)转的-用迅雷下载
  7. ALFA深度学习外观检测自学习人工智能软件
  8. 16天记住7000考研单词(784个句子版本) 实际单词4369
  9. 俄罗斯的程序员工资高吗?
  10. 强化学习必知二要素:计算效率和样本效率