动力节点旗下在线教育品牌  |  咨询热线:400-8080-105 学Java全栈,上蛙课网
首页 > 文章

Java中的垃圾回收机制及其原理

07-01 17:43 263浏览
举报 T字号
  • 大字
  • 中字
  • 小字

‘垃圾’这个词大家都不会陌生,在生活中我们会制造出各种各样的垃圾,垃圾是指那些失去使用价值、 无法利用的废弃物品。同样的在java程序中也会产生各种各样的垃圾,需要我们去处理和回收。这就涉及到了java中的垃圾回收机制。

对于JVM来说,垃圾就是指那些在堆中存在的,已经“死亡”的对象。而对于“死亡”的定义,我们可以简单的将其理解为“不可能再被任何途径使用的对象”。现实生活中垃圾回收是实现减量、提质、增效的必然选择,是改善人居环境、促进城市精细化管理、保障可持续发展的重要举措。同样的,垃圾回收机制对于在java程序中为代码的运行创造一个良好的运行环境是不可或缺的。

垃圾回收(Garbage Collection,GC),顾名思义就是释放程序中垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。

Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?为了解决这一难题,在1960年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时 Java 还没有出世呢!所以实际上 GC 并不是Java的专利,GC的历史远远大于Java的历史。但不可否认的是GC在java中得到了更好地运用和发挥。

接下来我们迎来的问题是如何判断一个对象是否存活?即垃圾判断算法。下面给大家介绍java中主要的垃圾判断算法。

一、引用计数法

在这种算法中,假设堆中每个对象(不是引用)都有一个引用计数器。当一个对象被创建并且初始化赋值后,该对象的计数器的值就设置为 1,每当有一个地方引用它时,计数器的值就加 1,例如将对象 b 赋值给对象 a,那么 b 被引用,则将 b 引用对象的计数器累加 1。

反之,当引用失效时,例如一个对象的某个引用超过了生命周期(出作用域后)或者被设置为一个新值时,则之前被引用的对象的计数器的值就减 1。而那些引用计数为 0 的对象,就可以称之为垃圾,可以被收集。

特别地,当一个对象被当做垃圾收集时,它引用的任何对象的计数器的值都减 1。

引用计数法实现起来比较简单,对程序不被长时间打断的实时环境比较有利。但需要额外的空间来存储计数器,难以检测出对象之间的循环引用。

二、可达性分析法

可达性分析法也被称之为根搜索法,可达性是指,如果一个对象会被至少一个在程序中的变量通过直接或间接的方式被其他可达的对象引用,则称该对象就是可达的。更准确的说,一个对象只有满足下述两个条件之一,就会被判断为可达的:

对象是属于根集中的对象

对象被一个可达的对象引用

在这里,我们引出了一个专有名词,即根集,其是指正在执行的 Java 程序可以访问的引用变量(注意,不是对象)的集合,程序可以使用引用变量访问对象的属性和调用对象的方法。在 JVM 中,会将以下对象标记为根集中的对象,具体包括:

虚拟机栈(栈帧中的本地变量表)中引用的对象

1.方法区中的常量引用的对象

2.方法区中的类静态属性引用的对象

3.本地方法栈中 JNI(Native 方法)的引用对象

4.活跃线程(已启动且未停止的 Java 线程)

根集中的对象称之为GC Roots,也就是根对象。可达性分析法的基本思路是:将一系列的根对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,如果一个对象到根对象没有任何引用链相连,那么这个对象就不是可达的,也称之为不可达对象。

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,这里我们讨论几种常见的垃圾收集算法的核心思想。

一、标记—清除算法

标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。清理掉的垃圾就变成未使用的内存区域,等待被再次使用。

这种算法的逻辑再清晰不过了,并且也很好操作,但它存在一个很大的问题,那就是内存碎片。等我们回收完之后,内存就会切成了很多段。我们知道开辟内存空间时,需要的是连续的内存区域,这时候我们需要一个连续的的内存区域,但其中有几个内存片段是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。

二、复制算法

复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。

  • 标记—整理算法

标记整理算法(Mark-Compact)标记过程仍然与标记—清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。标记—整理算法一方面在标记-清除算法上做了升级,解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。看起来很美好,但从上图可以看到,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。

四、分代收集算法

分代收集(Generational Collector)算法的将堆内存划分为新生代、老年代和永久代。新生代又被进一步划分为 Eden 和 Survivor 区,其中 Survivor 由 FromSpace(Survivor0)和 ToSpace(Survivor1)组成。所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。分代收集,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,可以将不同生命周期的对象分代,不同的代采取不同的回收算法进行垃圾回收,以便提高回收效率。

垃圾回收(GC)线程与应用线程保持相对独立,当系统需要执行垃圾回收任务时,先停止工作线程,然后命令 GC 线程工作。以串行模式工作的收集器,称为Serial Collector,即串行收集器;与之相对的是以并行模式工作的收集器,称为Paraller Collector,即并行收集器。

在看完本文后,相信大家对java的垃圾回收机制有了一定的了解。正是凭借着这种垃圾回收机制,java JVM才能保持高效运行,才能完全释放JVM的虚拟内存。可以说java的垃圾回收机制和现实世界的环卫工人一样,扮演着环境清洁者的身份,为创造一个良好的环境而无私奉献着。

0人推荐
共同学习,写下你的评论
0条评论
上善若水
程序员上善若水

16篇文章贡献85593字

作者相关文章更多>

推荐相关文章更多>

Java数据结构

HelloWorld10-31 08:24

浅谈MySQL中SQL优化的常用方法

军哥08-12 23:29

五分钟读懂UML类图

江湖人称小李白12-10 10:41

MyBatis开发框架的四大核心

IT逐梦者08-17 21:43

一次搞定continue,break和return

HelloWorld11-06 11:19

发评论

举报

0/150

取消