写点什么

Java 垃圾回收

用户头像
dongge
关注
发布于: 2020 年 08 月 04 日
Java 垃圾回收

要想说清楚 Java 中的垃圾回收,必须先了解一下 Java 内存模型。

Java 内存模型


在 Java 中,JVM 内存模型主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈。


程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。


JVM 在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。在加载类的时候,JVM 会先加载 class 文件,而在 class 文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。


Java 虚拟机栈是线程私有的内存空间,它和 Java 线程一起创建。当创建一个线程时,会在虚拟机栈中申请一个线程栈,用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息,并参与方法的调用和返回。每一个方法的调用都伴随着栈帧的入栈操作,方法的返回则是栈帧的出栈操作。


本地方法栈跟 Java 虚拟机栈的功能类似,Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。但本地方法并不是用 Java 实现的,而是由 C 语言实现的。



堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,最后 Survivor 由 From Survivor 和 To Survivor 组成。



垃圾回收


垃圾回收,顾名思义,便是将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配。在 Java 虚拟机的语境下,垃圾指的是不再使用的对象所占据的堆空间。这里便涉及了一个关键的问题:如何辨别一个对象不再使用?


垃圾回收是通过可达性分析算法进行垃圾对象的识别,具体过程是:从线程栈帧中的局部变量,或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没有被标记的对象就是可回收的垃圾对象了。所以你可以看出来,可达性分析算法其实是一个引用标记算法。


可达性分析算法的实质在于将一系列 GC Roots 作为初始的存活对象合集(live set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。


GC Roots 包括(但不限于)如下几种:

  • Java 方法栈桢中的局部变量;

  • 已加载类的静态变量;

  • JNI handles;

  • 已启动且未停止的 Java 线程。


标记以后,JVM 就会对垃圾对象占用的内存进行回收,回收主要有三种方法。



第一种方式是清理:将垃圾对象占据的内存清理掉,其实 JVM 并不会真的将这些垃圾内存进行清理,而是将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象。但这样做有一个很明显的缺陷,由于垃圾对象是散落在内存空间各处的,所以标记出来的空闲空间也是不连续的,当应用程序创建一个数组需要申请一段连续的大内存空间时,即使堆空间中有足够的空闲空间,也无法为应用程序分配内存。



第二种方式是压缩:从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间。



第三种方法是复制:将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。JVM 将这两个空间分别命名为 from 区域和 to 区域。当对象从 from 区域复制到 to 区域后,两个区域交换名称引用,继续在 from 区域创建对象,直到 from 区域满。



JVM 在具体进行垃圾回收的时候,会进行分代回收。



JVM 中,具体执行垃圾回收的垃圾回收器有四种。

第一种是 Serial 串行垃圾回收器,这是 JVM 早期的垃圾回收器,只有一个线程执行垃圾回收。

第二种是 Parallel 并行垃圾回收器,它启动多线程执行垃圾回收。如果 JVM 运行在多核 CPU 上,那么显然并行垃圾回收要比串行垃圾回收效率高。在串行和并行垃圾回收过程中,当垃圾回收线程工作的时候,必须要停止用户线程的工作,否则可能会导致对象的引用标记错乱,因此垃圾回收过程也被称为 stop the world,在用户视角看来,所有的程序都不再执行,整个世界都停止了。

第三种 CMS 并发垃圾回收器,在垃圾回收的某些阶段,垃圾回收线程和用户线程可以并发运行,因此对用户线程的影响较小。Web 应用这类对用户响应时间比较敏感的场景,适用 CMS 垃圾回收器。

最后一种是 G1 垃圾回收器,它将整个堆空间分成多个子区域,然后在这些子区域上各自独立进行垃圾回收,在回收过程中垃圾回收线程和用户线程也是并发运行。G1 综合了以前几种垃圾回收器的优势,适用于各种场景,是未来主要的垃圾回收器。



总结

Java 为了实现自动化内存管理和高效并发,设计了 Java 内存模型。垃圾回收是对内存模型中堆空间中的不再使用的对象的回收,垃圾回收基于引用标记算法的可达性分析实现对 GC root 的引用判断。在实际的垃圾回收中,堆空间使用分代的方法对内存空间分而治之。使用不同垃圾回收算法可以应对不同的场景。通过 JVM 参数可以实现对堆空间的调优。

用户头像

dongge

关注

还未添加个人签名 2017.10.19 加入

还未添加个人简介

评论

发布
暂无评论
Java 垃圾回收