Java学习笔记之垃圾回收(GC)

基本概念

Java语言的一大特点就是可以进行自动垃圾回收处理,而无需开发人员过于关注系统资源管理,而在C++语言中,开发人员需要通过malloc/freenew/delete等函数来显式的分配和释放内存,这对开发人员提出了比较高的要求,容易造成内存访问错误和内存泄露等问题。当然,java也有可能出现内存泄漏现象,但是发生的概率很小。
Java垃圾回收器负责完成三件任务:

  • 分配内存
  • 确保被引用的对象的内存不被错误回收
  • 回收不再被引用的对象的内存空间

很显然,Java垃圾回收与Java内存有很大的关联,在讲解垃圾回收之前有必要介绍Java虚拟机(JVM)体系结构。

JVM体系结构

JVM是一个抽象的计算机结构。JVM针对特定于操作系统并且可以将Java指令翻译成底层系统的指令并执行。同时,JVM确保了Java的平台无关性。
在JVM体系结构中,与垃圾回收相关的两个主要组件有堆内存和垃圾回收器。
堆内存是内存数据区,用来保存运行时的对象实例。垃圾回收器也是在这里操作的。
值得注意的是,每种JVM可能采用不同的方法实现垃圾回收机制。这里主要介绍HotSpot JVM的垃圾回收原则。JVM体系结构图如下所示:

Java垃圾回收过程

在运行时,Java的实例对象被存放在堆内存区域。当一个对象不再被引用时,满足回收的条件,就会被移出堆内存且内存空间会被回收。
Java堆内存(Heap Memory)结构图如下所示:

堆内存主要分为以下两个部分:

  • 新生代(Young Generation)
    • Eden空间
    • Survivor S0空间
    • Survivor S1空间
  • 老年代(Old Generation)

注意:在Java SE8特性中,永久代(Permanent Generation)被划分为非堆内存区(Non Heap Memory)。

说明:

  1. Eden空间是内存分配的地方,它是一块连续的空间内存区域。由于不需要进行可用内存块的查找,故其内存分配速度非常快。任何实例对象都是通过Eden空间进入运行时内存区域的。
  2. 为什么会有两个Survivor空间?这两个Survivor空间是新生代与老年代的缓冲区域。当第一次触发Minor GC后,Eden中存活的对象将被移动到S0中,此时Eden为空。当第二次触发Minor GC后,S0和Eden中存活的对象被移动到S1,此时S0和Eden被清空。若再次触发Minor GC后,S1和Eden中存活的对象被移动到S0,此时S1和Eden被清空。总之,轮流移动,S0和S1在移动时至少有一个是空闲的。
  3. 何时新生代中存活的对象会移至老年代?每次触发Minor GC,计数器会统计触发的次数。默认情况下,如果触发的次数超过16次,就将新生代中存活的对象移至老年代中。
  4. 就Java垃圾回收过程,老年代是实例生命周期的最后阶段。Major GC扫描老年代的垃圾回收过程,如果实例不在引用,那么就会被标记回收,否则会继续留在老年代。
  5. 除了堆内存区域用来存放存活(living)的数据,JVM 还需要尤其是类描述、元数据等更多信息。所以这些信息统一被存放在命名为Permanent generation(永久/常驻代)的区域。

内存碎片:一旦实例对象从堆中被删除,其位置就会空余出来用于未来实例的分配。而这些空出的空间将会使整个内存区域碎片化。为了实例的快速分配,需要进行碎片整理。基于垃圾回收器的不同选择,回收的内存区域要么被不停地被整理,要么在一个单独的GC进程中完成。

待续…

参考资料:
1.JVM 垃圾回收器工作原理及使用实例介绍
2.How Garbage Collection works in Java
3.Java深度历险(四)——Java垃圾回收机制与引用类型
4.Java GC系列
5.JVM 垃圾回收算法
6.Java 堆内存(Heap)