第9章 垃圾收集
在java虚拟机的堆里存放着正在运行的java程序所创建的所有对象。使用new、newarray、anewarray和multianewarray指令来创建对象,但没有明确的指令来释放它们。垃圾收集就是自动释放不再被程序所使用的对象的过程。
当一个对象不再被程序所引用时,它所使用的堆空间可以被回收,以便被手续的新对象使用。垃圾收集器必须能断定哪些对象是不再被引用的,并且能够把它们所占用的堆空间释放出来。在释放的过程中,垃圾收集器运行将要被释放的对象的终结方法(finalizer)。
垃圾检测通常通过建立一个根对象的集合,并且检查从这些根对象开始的可触及性来实现。如果正在执行的程序可以访问到的根对象与某个对象之间存在引用路径,那么这个对象就是可触及的。对于程序来说,根对象总是可以访问的,从这些根对象开始,任何无法被触及的对象被认为是垃圾,它们不再影响程序的未来运行。
区分活动对象和垃圾的两个基本方法时引用计数和跟踪。引用计数垃圾收集器通过为堆中的每一个对象保持一个计数来区分活动对象和垃圾对象,引用计数记录下了对那个对象的引用次数。跟踪垃圾收集器实际上追踪从根节点开始的引用图,在追踪中遇到的对象以某种方式打上标记,当追踪结束时,没有被打上标记的对象被判定为不可触及的,可以被当作垃圾收集。
引用计数垃圾收集器:引用计数是垃圾收集的早期策略。当一个对象被创建并且指向该对象的引用被分配给一个变量,这个对象的引用计数被置为1;当任何其他变量被赋值为堆这个对象的引用时,计数加1;当一个对象的引用超过了生存期或被设置一个新的值时,对象的引用计数减1。任何引用计数为0的对象都可以被当作垃圾收集。这种算法的缺点是:引用计数无法检测出循环引用(多个对象互相引用);每次引用计数的增加或者减少都带来额外的开销。
跟踪垃圾收集器:因为引用计数方法固有的缺陷,这种技术已经不为人所接受。现实所遇到的java虚拟机更可能在垃圾收集中使用追踪算法。跟踪收集器追踪从根节点开始的对象引用图。基本的追踪算法被称作“标记并清除”,在标记阶段,垃圾收集器遍历引用数,标记每一个遇到的对象;在清除阶段,未被标记的对象被释放了,使用的内存被返回给正在执行的程序;清除步骤必须包括对象的终结。标记并清除收集器通常使用两种策略对付堆碎块:压缩和拷贝。
压缩收集器:把活动的对象越过空闲区滑动到堆的一端。在这个过程中,堆的另一端出现一个大的连续空闲区,所有被移动的引用也被更新,指向新的位置。更新被移动的对象引用有时候通过一个间接对象引用层。不直接引用堆中的对象,对象的引用实际上指向一个对象句柄表;对象句柄才指向堆中对象的实际位置。当对象被移动了,只有这个句柄需要被更新为新位置。这种方法简化了消除碎块的工作,但每一次对象访问都带来性能损失。
拷贝收集器:把所有活动对象移动到一个新的区域。在拷贝的过程中,它们被紧挨着布置,所以可以消除原本他们在旧区域的空隙。原本的区域被认为都是空闲区。这种方法的好处是对象可以在从根对象开始遍历的过程中随着发现而被拷贝,不再有标记和清除的区分。对象被快速拷贝到新区域,同时转向指针仍然留在原来的位置。转向指针可以让垃圾收集器发现已经被转移的对象的引用,然后垃圾收集器可以把这些引用设置为转向指针的值,所以它们现在指向对象的新位置。拷贝收集器算法被称为“停止并拷贝”。这个方案中,堆被分为两个区域,任何时候都只使用其中的一个区域;对象在同一个区域中分配,直到这个区域被耗尽;此时,程序执行被中止,堆被遍历,遍历时遇到的活动对象被拷贝到另一个区域;当停止和拷贝过程结束时,程序恢复执行,内存将从新的堆区域中分配,直到它也被耗尽。这种方法的代价是,对于指定大小的堆来说需要两倍大小的内存。
按代收集的收集器:简单的停止拷贝收集器的缺点是,每一次收集时,所有的活动对象都必须被拷贝,它每次都把那些生命周期很长的对象来回拷贝,消耗大量的时间。按代收集的收集器通过把对象按照寿命来分组解决这个效率低下的问题,更多地收集那些短暂出现的年幼对象,而非寿命较长的对象。在这种方案中,堆被分成多个子堆,每个子堆为一代对象服务;最年幼的那一代进行最频繁的垃圾收集,如果一个最年幼的对象进过几次垃圾收集后仍然存活,那么它就被转移到另外一个代表更高寿命的子堆中去;每当对象在它所属的子堆中变得成熟(逃过多次垃圾收集)之后,它们就被转移到代表更高年龄的子堆中去。
自适应收集器:在某种情况下某些垃圾收集算法工作的更好,而另外一些收集算法在另外的情况下工作的更好。自适应算法监视堆中的情形,并且对应地调整为合适的垃圾收集技术。
火车算法:垃圾收集算法和明确释放对象比起来有一个潜在的缺点,即垃圾收集算法中程序员堆安排CPU时间进行内存回收缺乏控制。因为垃圾收集一般都会停止整个程序的运行来查找和收集垃圾对象,垃圾收集可能使得程序对事件响应迟钝,无法满足实时系统的要求。达到非破坏性垃圾收集的方法是使用渐进式的收集算法。渐进式垃圾收集器不会试图一次性发现并回收所有不可触及的对象,而是每次发现并回收一部分,因此理论上说每一次收集会持续更短的时间,如果每次可以保证(或者非常接近)不超过一个最大时间长度,就可以让java虚拟机适合实时环境;这样的收集器也可以消除用户可察觉的到的垃圾收集停顿。
通常渐进式垃圾收集器都是按代收集的收集器,大部分调用中都是收集堆的一部分。大部分对象都很快消亡,按代收集的收集器在年幼的子堆中比在年长的子堆中活动更频繁;除了最高寿的那个子堆(成熟对象空间)之外,每一个子堆都可以给定一个最大尺寸,按代收集的收集器可以大体上保证在一个最大时间值内渐进地收集所有的对象。成熟对象空间无法给定最大尺寸,因为,任何在其他年龄层子堆中不再适合的对象总要有个去处,除了成熟对象空间,它们没处可去。火车算法的目的是为了在成熟对象空间提供限定时间的渐进收集。
火车算法把成熟对象空间划分为固定长度的内存块,算法每次只会在一个块中单独执行。每一个块归属于一个集合,在一个集合内的块排了序,这些集合本身也排了序。火车算法中,块被称为车厢,集合被称为火车,成熟空间扮演火车站的角色。火车按照他们创建时的顺序分配号码,号码较小的火车总是更早出现的火车。在火车内部,车厢(块)总是被附加到火车的尾部,因此,较小数字表示更早出现的车厢。用这种命名方式表示成熟对象空间中所有的块的总体顺序。
火车算法每次执行的时候,只会对一个块(号码最低的块)执行垃圾收集。对象从更年轻的子堆提出来进入成熟对象空间,不管何时提出,它们都被附加到任何已经存在的火车中(最小号码火车除外),或者专门为容纳它们创建的一列新火车中。
车厢收集:每次火车算法被执行的时候,它要么收集最小数字火车中的最小数字车厢,要么收集整列最小数字火车。算法首先检查执行整列火车中任何车厢的引用,如果不存在任何来自火车以外的引用指向它内部包含的对象,那么整列火车都是垃圾,算法归还火车中所有车厢中的对象并返回。这步算法使得火车算法可以一次收集大型的,无法在一个块中容纳的循环数据结构。如果火车里并不都是垃圾,那么算法把注意力放到最小数字车厢上。算法首先把被车厢外部的车厢引用的对象转移到其他车厢去;当进行这个移动后,车厢里任何保留下来的对象都是没有引用的,可以被垃圾收集。算法归还最小数字车厢占据的空间并返回。
保证整列火车中没有循环的数据结构的关键是算法如何移动对象。如果正被收集的车厢中有一个对象被来自成熟空间以外的对象引用,这个对象被转移到正在被收集的火车之外的其他车厢去。如果对象被成熟对象空间的其他火车引用,对象就被转移到引用它的那列火车中去;然后转移过去的对象被扫描,查找对原车厢的引用,发现的任何被转移对象引用的对象都被转移到引用它的火车中去;新转移的对象也被扫描,这个过程不断重复,直到没有任何来自其他火车的引用指向正被收集的那节车厢。如果接收对象的车厢没有空间了,算法会创建新的车厢并附加到那列火车的尾部。一旦没有来自火车外的引用了,那么这节车厢剩余的外部引用都是来自于同一列火车的其他车厢;算法把这样的对象转移到最小数字火车的最后一个车厢去,然后扫描这些对象,查找对原被收集车厢的引用,任何新发现的被引用对象也都被转移到同一列列车的尾部,这个过程不断重复,直到没有任何形式的引用指向被收集的车厢。然后算法归还整个最小数字车厢占据的空间并且返回。
火车算法最重要的方面之一,就是它保证大型的循环数据会完全被收集,即使它们不能被放置在一个车厢中。因为对象被转移到引用它们的火车,相关的对象会变得集中。最后,成为垃圾的循环数据结构中的所有对象,不管有多大,会被放置到同一列火车中去,增大循环数据结构的大小只会增加最终组成同一列火车的车厢数。
记忆集合:为了促进收集过程,火车算法使用了记忆集合。一个记忆集合是一个数据结构,它包含了所有对一节车厢或者一列火车的外部引用。算法为成熟对象空间内每节车厢和每列火车都维护一个记忆集合。一个空的记忆集合显示车厢或者火车中的对象都不再被外部引用,它们是不可触及的,可以被垃圾收集。
相关推荐
深入理解 Java 虚拟机笔记 Java 虚拟机(JVM)是 Java 语言的运行环境,它负责解释和执行 Java 字节码。下面是 Java 虚拟机相关的知识点: 虚拟机内存结构 Java 虚拟机的内存结构主要包括以下几个部分: * 方法...
JVM的核心功能包括内存管理、垃圾收集、安全性和平台独立性。 #### 二、内存区域详解 ##### 1. 堆 (Heap) - **功能**: 存储所有由new关键字创建的对象和数组。 - **生命周期**: 对象的整个生命周期。 - **垃圾...
### 深入Java虚拟机JVM类加载学习笔记 #### 一、Classloader机制解析 在Java虚拟机(JVM)中,类加载器(ClassLoader)是负责将类的`.class`文件加载到内存中的重要组件。理解类加载器的工作原理对于深入掌握JVM以及...
JVM 不仅执行字节码,还负责垃圾收集、内存管理等任务,确保Java程序的高效运行。 与C++等编译型语言不同,Java 程序不直接生成操作系统可执行文件。Java 源代码先由javac编译器转化为字节码,然后由JVM 解释执行。...
《JVM:深入理解Java虚拟机》是一本深入解析Java虚拟机工作原理和技术细节的经典书籍。这份学习笔记将涵盖JVM的关键概念、架构以及它如何影响Java程序的性能。我们将探讨以下几个方面: 1. **JVM概述** Java虚拟机...
### 学习笔记之Java虚拟机详解 #### 运行时数据区域概览 Java虚拟机(JVM)运行时数据区域主要包括以下几部分:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区以及运行时常量池。 1. **程序计数器**: -...
主要的垃圾回收算法有标记-清除、复制、标记-整理和分代收集等。新生代、老年代的划分,以及各种GC策略如Serial、Parallel、CMS、G1等,都是为了更高效地进行垃圾回收。 5. **性能优化**:JVM提供了许多工具和参数...
不同的JVM实现采用不同的垃圾回收算法,常见的算法有标记-清除、标记-整理、复制、分代收集等。 Java虚拟机规范定义了JVM在执行Java程序时必须遵守的行为准则,它包括类文件格式、字节码指令集、执行引擎的行为等。...
本篇文章将深入探讨Java虚拟机中的垃圾收集器(GC)及其对内存管理的影响。 1. 垃圾收集器的由来与作用 垃圾收集器的引入主要是为了解决内存管理的问题。在没有GC的情况下,程序员需要手动释放不再使用的对象,这...
**JVM学习笔记(Java虚拟机)** Java虚拟机(JVM)是Java语言的核心组成部分,它是Java程序运行的平台,负责解释和执行字节码。深入理解JVM对于优化Java应用程序性能至关重要。本笔记将从以下几个方面详细介绍JVM:...
1. 引用计数法:早期的垃圾收集算法,通过计算对象的引用次数来判断是否需要回收。当引用计数为0时,对象被视为垃圾。但此方法无法处理循环引用的问题。 2. 可达性分析:现代Java垃圾回收主要采用可达性分析,通过...
它被进一步划分为新生代和老年代,以优化垃圾收集效率。新生代又细分为Eden空间、From Survivor空间和To Survivor空间。 - **新生代**: 主要用于存放新创建的对象。当对象存活一定周期后,会被转移到老年代。 - **...
8. **《深入理解JVM虚拟机笔记》**:深入探讨JVM的工作原理,如内存区域、类加载机制、垃圾回收算法、性能调优策略等。 9. **《高级MySQL笔记》**:高级MySQL学习可能涉及分区、视图、触发器、存储过程、复制与集群...
《深入理解Java虚拟机》是Java开发者们深入探讨Java运行机制的经典之作,作者周志明以其深入浅出的讲解方式,揭示了Java虚拟机(JVM)的工作原理。本资源包含该书第三版的源码分析及学习笔记,旨在帮助读者更透彻地...
4. **堆**:是Java对象的主要存储区域,垃圾收集器主要工作在这里。堆内存可通过-Xms和-Xmx参数设定最小和最大值,新生代和老年代可通过-Xmn、-XX:NewSize和-XX:MaxNewSize等参数调整。 5. **方法区/永久代**:存储...
4. **垃圾收集**:JVM如何自动管理内存,理解不同垃圾收集器如Serial、Parallel、CMS、G1等的工作机制,以及新生代和老年代的概念。 5. **类加载器**:系统类加载器、扩展类加载器和应用程序类加载器之间的双亲委派...
笔记可能涵盖了JVM内存模型(堆、栈、方法区、本地方法栈、程序计数器),垃圾收集(GC)机制,类加载过程,以及如何通过JVM参数进行性能调优。 张龙老师的笔记以实战和案例为导向,旨在帮助读者深入理解这些核心...
4. **垃圾收集器**:自动管理内存,通过标记-清除、复制、标记-整理、分代等算法回收不再使用的对象。 5. **内存模型**:定义了线程之间共享变量的访问规则,如volatile、synchronized和final关键字的实现。 ...
《JVM笔记(阳哥)》是一份深入探讨Java虚拟机(JVM)的资料,由阳哥精心整理。这份笔记涵盖了JVM的基础概念、内存管理、类加载机制、性能优化等多个方面,对于理解Java程序的运行机制以及提升开发效率具有重要的...
笔记可能涉及类加载机制、内存模型、垃圾收集算法以及如何进行性能调优。 通过学习这些笔记,开发者不仅可以掌握Java编程基础,还能深入了解其高级特性和最佳实践,从而提升编程技能和解决问题的能力。如果你正在...