0%

Java 中的内存回收机制

在计算机科学中,垃圾回收(英语:Garbage Collection,缩写为 GC)是指一种自动的存储器管理机制。当某个程序占用的一部分内存空间不再被这个程序访问时,这个程序会借助垃圾回收算法向操作系统归还这部分内存空间。垃圾回收器可以减轻程序员的负担,也减少程序中的错误。垃圾回收最早起源于 LISP 语言。当前许多语言如 Smalltalk、Java、C# 和 D 语言都支持垃圾回收器。

0x00 垃圾回收是什么

众所周知,Java 是一个不需要自己进行内存管理的语言。与 C++ 不同,JVM 会在空闲时间自动去释放无效对象所占用的内存空间。Java 中的对象都存放于堆中,如果堆中的对象没有及时的进行回收则会导致堆内存越来越大,直至堆内存被占满。而垃圾回收正是保证了有效的内存使用,释放无效对象的内存。这一些都无需程序员自己处理,JVM 已经帮我们做到了这一切。

0x01 如何判定对象无效

垃圾回收指的是回收掉无效的对象,那么什么是无效的对象呢?无效对象通常指的是不再存活的对象,更直白的讲是指不会再被其他地方使用的对象。这一类对象既然不会再被使用,JVM 就会释放掉它所占用的内存空间。那么,JVM 是如何判定对象无效呢?

引用计数法

引用计数法是一种简单高效的内存管理方式。这种方式会为每一个对象分配一个引用计数器,用来统计该对象被引用的次数,当这个计数器归零时也就意味着这个对象不再被任何地方引用。这种方式实现起来比较简单,它的弊端在于如果两个对象循环引用,它俩的计数器都不会归零,所以就永远不会被回收。目前主流的虚拟机并没有采用这种做法。

可达性分析

可达性分析是目前主流 JVM 对于内存管理的实现方式。其基本思想是从 GC Roots 开始向下遍历搜索引用对象,当所有的对象都搜索完毕,剩余的那些没有被搜索到的节点被认为是 不可达 的对象。 GC Roots 对象主要由下面四种类型组成:

  • 调用栈中引用的对象(本地变量表)
  • 方法区中静态引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中的对象

0x02 如何回收无效对象

JVM 的垃圾收集使用的是 分代收集算法。简单来讲就是根据对象的存活时间长短,将内存空间分为 新生代 老年代 元空间。不同声明周期的对象所需要使用到的回收算法不一样,如: 复制算法 标记-整理 标记-清除

复制算法

复制算法是一种简单粗暴的回收方式。它将内存划分为两大区域,只会在一块内存中分配的新对象。当需要进行内存回收时,直接将存活的对象复制到另一块内存中去,然后清除当前内存。这种做法的好处是不容易产生内存碎片,坏处是在一段时间内只能使用部分内存。总结来说,复制算法适合用于对象大量死亡的区域,因为这样只需要复制少部分存活对象到新的内存区域。JVM 中的新生代就非常符合这种场景,新生代会产生大量对象,但是每次 GC 后剩余的存活对象却并不多。
http://q8nnvv93v.bkt.clouddn.com/0a256e66f32cdc278f830a8380bc9f30.jpg

标记清除

标记清除算法会从将存活的对象进行标记,然后对剩余的无效对象进行清除回收。标记清除算法不需要对存活对象进行移动,而且只对无效对象做处理,所以这种算法适合存活的对象比较多的情况。标记清除算法的弊端也很明显,因为只是回收了无效对象,所以内存碎片会比较多。

标记整理

标记整理算法和标记清除在标记时采用的做法相同,不同点在于清除完无效对象后,会将所有存活对象向左端空闲内存进行移动并更新对象指针。毫无疑问,标记整理的成本更高,但是却解决了内存碎片的问题。
http://q8nnvv93v.bkt.clouddn.com/45bce1c3019c675dd8a4c513443e9f43.jpg