浅谈垃圾回收算法

1,530 阅读4分钟

首先给大家讲一个梗,为什么Java程序员越来越多,而C++程序员越来越少呢?—— 那还不是因为不用管理内存啊

在C++中,自己new的对象,在不用的时候需要手动释放。而在Java里,你可以释放你的双手,将这项工作交给JVM自己管理。在JVM里面,这种机制叫做垃圾回收(Garbage Collection)机制。

ps:本文不涉及垃圾收集器,主要是讲垃圾回收的思路。

Java内存管理

Java的变量一共存储在三个地方、方法区、堆、栈

方法区

主要存放常量,静态常量、常量池,在程序编译的时候,这块内存就已经被分配出来了,伴随程序的一生。

是JVM管理的内存最大的一块,存放所有的实例对象,也是垃圾收集器管理的主要区域。也称为“GC堆”

当方法被执行的时候,方法内部的局部变量就会被存储在栈里,当方法执行完毕的时候,刚刚存储的局部变量会被自动释放。

回收流程

  1. 首先会判断对象是否存活
  2. 利用回收算法对对象进行回收

如何判断对象是否存活?

前面说到垃圾收集器管理的是堆,在回收之前,通常需要判断这个对象是否是存活的。

引用计数法

遍历对象,当这个对象被其他对象引用的时候计数器就加1,当引用失效的时候,计数器减1。当对象的计数器为0时,则表示该对象没有被引用。这种方法效率很高,但是无法解决互相引用的问题。因此主流JVM都未采用这种方法。

可达性分析

这一算法的思想是从GC Roots作为起始点,从这些节点往下搜索,搜索的路径叫做引用链,当一个对象没有任何引用链可以到GC ROOT的时候,则证明该对象是不可用的。一般想要宣告一个对象死亡,至少要经历两次标记,即标记-筛选-标记。

image

垃圾回收算法

标记-清除

过程
  1. 标记出所有需要回收的对象
  2. 清除所有被标记的对象

不足:

  1. 清除和标记两个效率都不高。
  2. 会产生大量的内存碎片,会导致在分配较大对象的时候,如果找不到足够的空间会再次清除。

image

复制算法

过程:

将可用内存分为两块,每次只使用其中的一块。一块内存使用完的时候,将存活下来的对象复制到另一块中,再清除这一块内存。这样就没什么内存碎片可言了。

优点:

效率高,解决了内存碎片的问题。

缺点:只能使用一半的空间。

image
不过现代虚拟机几乎都不是1:1分配空间,因为大部分都对象的生命周期都不长。例如HotSpot虚拟机,默认的Eden和Survivor的大小比例是8:1。

标记 — 整理算法

复制-收集算法,在对象存活率高的情况下,要进行多次复制,效率比较低。所以说为此提出了“标记-整理”算法。

过程:

标记过程同上,然后让所有存活的对象移动到另一端,然后清理掉边界外的内存。

image

分代收集算法

分代收集算法比较常见,一般把堆分为新生代和老年代,这样可以根据各个年代的特点采用适当的算法。

  • 新生代:新生代会有大批对象死去,少量对象存活。适合复制算法。
  • 老年代:老年代中对象存活率较高,没有额外 空间来分配担保。所以说适合采用标记-清除 或 标记-整理算法。
    ps:空间分配担保是用来检测老年代最大可用的连续空间是否大于新生代所有活着的对象的总空间。

引用类型

在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。

强引用(Strong reference)

就是指在程序代码之中普遍存在的,例如new出来的对象,只要强引用还在,该对象就不会被回收。

软引用(Soft Reference)

用来描述一些还有用但并非必须的对象。在内存即将溢出之前,垃圾收集器会将这些对象进行回收。

弱引用(Weak Reference)

用户描述非必须对象的。无论当前内存是否足够,随时都有可能被回收。

虚引用(Phantom Reference)

虚引用不会影响该对象都生命周期,只会在该对象被回收的适合获得一个系统通知。