java 的垃圾回收也就是我们常说的 gc ( garbage collector )。这是一个老生常谈的问题。之所以这么经常被提及,是因为它对 java 性能有很大影响。
在 c/c++ 中考虑到效率,内存的分配和销毁是由程序员来控制的。程序员使用内存必须手动分配,在使用结束后,在手动释放掉已经不用的内存空间。如果忘记释放内存空间,那么这块内存区域就是一直被占用的状态。其他急需内存的程序将无法使用这块内存,导致内存泄漏。
java 中为了减轻程序员的负担,同时为了防止程序员忘记释放内存导致内存泄漏,内存的回收交由垃圾回收器(gc)管理。程序员可以专注于核心业务的编写,不受繁琐的手动分配和释放的打扰。
gc 所做的工作就是,将不再被使用的对象或者不再被引用的对象,所占用的内存空间进行回收。
为了理解 java 的垃圾回收,先来了解一下 java 内存区域
java 的内存区域粗略包括有5个部分
方法区(Method Area)
虚拟机栈(VM Stack)
本地方法栈(Native Method Stack)
堆(Heap)
程序计数器(Program Counter Register)
java 内存区域.png
程序计数器
程序计数器(pc),是 jvm 的一个抽象概念,和 cpu 中的 pc 概念相似。用来记录当前执行的字节码文件的所在行。用来控制程序的执行顺序,进行流程控制。jvm 中的每个线程都拥有一个 pc ,来记录各自执行到的指令位置。
方法区
方法区在 Hotspot JVM 中也称为 permanent generation。与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。 对于习惯在HotSpot虚拟机上开发、部署程序的开发者来说,很多人都更愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。
虚拟机栈
和 pc 一样,虚拟栈也是线程私有的。生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈
本地方法栈和虚拟机栈相似,但是它是用来记录 Native 方法的。也就是 java 调用的非 java 方法,比如 c/c++
堆
堆是 jvm 内存类中的主要部分,被所有线程共享。所有对象和数组都在这个堆上分配,因此堆是 gc 管理的主要区域。为了有效地实现垃圾回收,堆又被细分为新生代(yong generation)和老年代(old generation),新生代又可以再细致地分为 Eden Space 和 Survivor Space。在 Oracle 的 Hopspot JVM 的描述中,也将方法区称为永久代(permanent generation)。
Hotspot 中 gc
JVM 只是 java 虚拟机概念或者称它为标准。就像 rfc 文档和具体网络协议实现的之间关系。因此,JVM 有很多的实现,比如我们最熟悉的两种实现
本文讨论的垃圾回收机制以 HotSpot 为例
HotSpot 中的垃圾回收,主要涉及到 heap ,也就是堆区。
HotsSpot 使用一种叫世代回收(Generational Garbage Collection)的方法来实现垃圾回收,本文的后面将介绍为什么要分代
为了实现世代回收,HotSpot 把 heap 分为多个区域
分别为新生代和老年代,对应于垃圾回收的不同过程,使用不同的回收算法
注:
1、有两个垃圾收集过程,minor garbage collection、marjor garbage collection。
2、在进行垃圾回收的时候,会停止所有在运行的线程被称作 " stop world event "
Hotspot 中的垃圾回收有下面几个步骤:
1、新的对象被分配到 eden,两个 survivor 分区在开始状态都是空的
2、eden 空间分配满时,就开始执行第一次的 minor garbage collection。将还在被使用的对象移动到 S0,同时删除 eden 中,不再被使用的对象。标记对象 age 为 1。
3、发生下一次的 minor GC 时,将 eden 区中未被使用的对象删除,并将正在使用的对象移动到 survivor 区。需要注意,这个时候把 eden 对象放到 S1,还把 S0 中还被使用的对象移动到 S1。然后,把 age 加 1。
4、再进行下一次 minor GC 时,将 eden 区中未被使用的对象删除,并移动到 S0 区,同时 S1 移动到 S0, age 加 1
5、当对象的 age 达到某个阈值(通常情况为 8),就进行 marjor GC 。把 age 为 8 且还在使用的对象,从 yong generation 移动到 old generation
6、过程总览
为什么要分代?
根据经验分析得到对象特性:
刚创建的对象容易被回收
经过几次回收后还保留的对象,被回收的几率会降低
基于这种分析,可以将堆分为新生代和老生代,针对不同的阶段的特点,采用不同的回收算法来提高回收效率。这就时Hotspot JVM GC 的思想
当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。在老生代中,几乎所有的对象都是经过几次垃圾回收后依然得以幸存的。因此,可以认为这些对象在一段时期内,甚至在应用程序的整个生命周期中,将是常驻内存的。如果依然使用复制算法回收老生代,将需要复制大量对象。再加上老生代的回收性价比也要低于新生代,因此这种做法也是不可取的。根据分代的思想,可以对老年代的回收使用与新生代不同的标记-压缩算法,以提高垃圾回收效率。(后续讨论算法)
总结
上面简述了 Hotspot JVM 的 GC,但是没有涉及具体的垃圾回收算法。大概的垃圾回收算法有,引用计数法、标记-清除算法、复制算法、标记-压缩法、增量算法*。浅析 java 垃圾回收(二)—— 回收算法继续回收算法
参考