作业 - 第 9 周
一、简述 JVM 垃圾回收原理
1、运行时数据区
JVM 在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚机机进程的启动而一直存在,有的区域则是依赖用户线程的启动和结束而建立和销毁。JVM 所管理的内存将会包括以下几个运行时数据区域,如下图所示。
1.1 程序计数器
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行字节码的行号指示器。在 JVM 的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序流程的指示器,分支、循环、跳转、异常处理、线程恢复等基本功能都需要依赖这个计数器来完成。
每条线程都需要一个程序计数器,保证各条线程之间的计数器互不影响,独立存储,是线程私有的内存。如果一个线程正在执行 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行本地方法,则这个计数器为空。
1.2 Java 虚拟机栈
与程序计数器一样,Java 虚拟机栈是私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行时,JVM 都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法执被调用直至执行完毕的过程,就对应着一个栈桢在虚拟机栈中从入栈到出栈的过程。
1.3 本地方法栈
本地方法栈与虚拟机栈做发挥的作用是非常类似的,其区别只是虚拟机栈为虚拟机执行的是 Java 方法(也就是字节码)服务,而本地方法栈为虚拟机使用到的本地方法服务。
1.4 Java 堆
对于 Java 应用程序来说,Java 堆是虚拟机所管理内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存唯一的目的就是存放对象实例,Java 世界里几乎所有对象实例都在这里分配内存。Java 堆是垃圾回收器管理的内存区域。
1.5 方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
2、垃圾收集器
2.1 概述
垃圾收集需要完成三件事情:
哪些内存需要回收?
什么时候回收?
如何回收?
上节介绍了内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法 3 个区域随线程而生,随线程而灭,栈中的栈桢随着方法的进入和退出而有条不稳地执行着出栈和入栈操作。每个栈桢中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和和回收都具备确定性,在这几个区域内就不需要过多地考虑如何回收的问题,当方法或线程结束时,内存自然就跟随着回收了。
而 Java 堆和方法区这两个区域则有着很明显的不确定性:一个接口的多个实现需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有在运行期,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理。
2.2 对象已死?
在堆里存放着 Java 世界中几乎所有的对象实例,垃圾收集器在对堆进行回收之前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”了。
2.2.1 引用计数算法
引用计数算法判断对象是否存活是这样的:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
2.2.2 可达性分析算法
这个算法的基本思路是通过一系列被称为“GC Roots"的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径成为引用链,如果某个对象到 GC Roots 间没有任何引用链相连,或者用图论的话说就是从 GC Roots 到对象是不可达市,证明此对象不可能再被使用。
在 Java 技术体系里面,固定可作为 GC Roots 的对象包括以下几种:
在虚拟机栈中引用的对象
在方法区中静态属性引用的对象
在方法区中常量引用的对象
在本地方法栈中 JNI 引用的对象
Java 虚拟机内部的引用
所有被同步锁持有的对象
反映 Java 虚拟机内部情况的 JMXBean、JVMTI 注册的回调、本地代理缓存等。
2.2.3 引用类型
强引用,指程序代码之中普遍存在的引用赋值。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉引用的对象。
软引用,是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行二次回收。
弱引用,也是用来描述那些非必须对象,但它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
虚引用,也被称为“幽灵引用”或“幻影引用”它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。
2.2.4 生存还是死亡?
即使在可达性分析算法中判定为不可达对象,也不是“非死不可”的,这个时候它们还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalize()方法。如果这个对象被判定确有必要执行 finalize()方法,那么该对象将会被放置在一个名为 F-Queue 的队列之中,并在稍后由一条由虚拟机创建的低优先级的 Finalize 线程去执行它们的 finalize()方法。稍后收集器会对 F-Queue 中的对象进行第二次小规模的标记。如果对象这个时候还没逃脱,那它基本上就要被回收了。
3、垃圾收集算法
3.1 分代收集理论
分代收集理论是建立在两个分代假说之上:
弱分代假说:绝大多数对象都是朝生夕灭的。
强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
3.2 收集算法
标记-清除算法
标记-复制算法
标记-整理算法
二、性能优化-总结
如下图:
评论