java垃圾收集机制
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
四、总结··································································· 16
1
Java 垃圾收集机器-Garbage Collector
Java 垃圾收集机器-Garbage Collector
[摘 要]:本文详细讨论了 Java 垃圾收集机制的基本原理,并结合 Java 语言特 性, 介绍利用 Java 垃圾收集机制提高程序的健壮性和性能的基本方法。 [关键词]:Garbage collector,Heap, Reference, 对象,JVM
4
Java 垃圾收集机器-Garbage Collector
发现这个问题,就得通过后续过程才能找到。 但是在有的情况下将使得“复制式垃圾收集器”显得效率极为低下,比如随 着程序进入了稳定状态之后,它几乎不产生或产生很少的垃圾。尽管如此,这时 的一个“复制式垃圾收集器”仍会将所有内存从一处复制到另一处,这显得非常 浪费。更糟的是:程序中的对象不仅不死亡,而且一个个都很庞大,对两个大的 内存堆管理也将成为不必要的消耗。
2百度文库
Java 垃圾收集机器-Garbage Collector
数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上 和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些 Java 数据 要保存在堆栈里——特别是对象的引用(也可称为对象的引用变量) ,但 Java 中的对象不会放在其中。 (2) 堆(Heap)。一种常规用途的内存池(也在 RAM 区域) ,其中保存了 Java 对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不 必知道要从堆里分配多少存储空间, 也不必知道存储的数据要在堆里停留多长的 时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需 用 new 命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保 存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时 会花掉更长的时间!这是导致 Java 性能不佳的因素之一。 SUN 的 JVM 使用分代方式(Generation)管理 堆 空 间 , “代”分配给新旧对象 的内存池。这些对象的不断积累会导致一个的内存状态,从而推动垃圾收集的开 始。如图说明了 SUN 的 JVM 中堆空间粗略的划分。
二、垃圾收集的基本原理:
2.1 JVM 中内存的划分
垃圾收集器对 Java 程序员来说,基本上是透明的,但是只有了解 GC 的工作 原理、如何优化 GC 的性能、如何与 GC 进行有限的交互,才能提高整个应用程序 的性能、全面提升内存的管理效率,为了说明其工作方式,我们首先看看内存中 几种常用的存放数据的地方: (1) 堆栈(Stack) : 位 于常规 RAM(随机访问存储器)区域,但可通过它的 “堆栈指针”获得处理器的直接支持。堆栈指针若向下移,会创建新的内存;若 向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次 于 CPU 的寄存器。创建程序时,Java 编译器必须准确地知道堆栈内保存的所有
12
3.2.1 软引用········································································ 13 3.2.2 弱引用········································································ 14 3.2.3 虚引用········································································· 15
2、基于 Tracing 算法、 Compacting 算法的 “标记和清除 (make-and- sweep)”方式:对于常规性的应用,标记和清除显得非常慢,但一旦知道自己 不产生垃圾,或者只产生很少的垃圾,它的速度就会非常快。标记和清除(make -and-sweep)采用的逻辑:基于 Tracing 算法从堆栈和静态存储区域开始,并 跟踪所有引用,寻找活动对象。然而,每次发现一个活动对象的时候,就会设置 一个标记(一个位或多个位) ,为那个对象作上“记号”。但此时尚不收集那个 对象。只有在标记过程结束,清除过程才正式开始。在清除过程中,不复存活的 对象会被释放然而,不会进行任何形式的复制(这时的堆中被使用的空间呈现不 连续的状态) 。所以假若收集器决定使这个断续的内存堆密集(compact),它将 使用 Compacting 算法重新整理他所找到的对象:垃圾收集器将所有的对象移置 堆的一端。堆的另一端就变成了一个相邻的空闲内存区。收集器会对它移动的所 有对象的所有引用进行更新,这样这些引用能够在新的位置识别同样的对象。为 了 简 化 对 象 引 用 的 更 新 , compacting 算法 增 加 了 间 接 的 一 层 ( level of indirection) 。间接层通过句柄(handle)和句柄表实现。在这种情况下,对象 引用总是指向句柄表中同样的句柄入口。反过来,句柄入口包含了句柄的实际引 用。当 compacting 标记和清除垃圾收集器移动对象时,收集器在句柄表中只修 改实际的对象引用对句柄入口的所有引用没有受到影响。 虽然使用句柄表简化了
3
Java 垃圾收集机器-Garbage Collector
new 的时候,存储分配机制必须进行某种形式的搜索,使新对象能够利用已经存 在的空洞,否则就会很快用光堆的存储空间。之所以内存堆的分配会在 C++里对 性能造成如此重大的性能影响,对可用内存的搜索正是一个重要的原因。所以创 建基于堆栈的对象要快得多。 而在 Java 中的内存堆(Heap)更像一条传送带:每次分配了一个新对象后, “Heap 指针”都会朝前移动,这意味着对象存储空间的分配可以达到非常快的 速度。因为“Heap 指针”只是单纯的往前移动至未经分配的区域,所以它与 C++ 的堆栈分配方式几乎是不相上下的(当然,在数据记录上会多花一些开销,但要 比搜索存储空间快多了) 。但是如果只是按那种方式分配,最终就要求进行大量 的内存页面交换(这对性能的发挥会产生巨大干扰) ,而且终究会用光内存,出 现内存分页错误 (page fault)。所以 Java 引入了“垃圾收集器”。它在收集“垃 圾”的同时,也负责重新紧密排列(compact)堆里的所有对象,消除内存空洞, 将“堆指针”移至尽可能靠近传送带开头的地方,远离发生(内存)分页错误的 地点。 垃圾收集器会重新安排所有东西, 使其成为一个高速、 无限自由的堆模型 , 同时游刃有余地分配存储空间。但是垃圾收集时的代价是非常高昂的,这也是导 致 Java 性能不佳的因素之一。
如图所 示 , 年 轻的 一 代 包 括年 轻的 对 象空 间( eden )和 两 个存 活 (survivor)空间(SS#1 和 SS#2) 。新对象被分配到 eden 中,那些存活较久的 对象则会从年轻的一代转移到老一代中。图中的 Perm 段叫做永久代(permanent generation) ,它保存了 JVM 的类和方法对象。 (3) 静态存储(Static)。这儿的“静态”(Static)是指“位于固定位置” (尽管也在 RAM 里)或是有且仅有一份。程序运行期间,静态存储的数据将随时 等候调用。可用 static 关键字指出一个对象的特定元素是静态的。但 Java 对象 本身永远都不会置入静态存储空间。
Java 垃圾收集机器-Garbage Collector
一、引言:·································································· 2 二、垃圾收集的基本原理:············································· 2 2.1 JVM 中内存的划分··············································· 2 2.2 对象在内存中的分配············································· 3 2.3 垃圾收集的基本原理与算法····································4 三、与垃圾收集器交互··················································· 7 3.1 FINALIZE ()方法·················································· 7
3.1.1 Finalize()用处之一:观察·································· 8 3.1.2 Finalize()用处之二:再生································ 11
3.2
JAVA.LANG.REF 包··················································
一、引言:
在面向对象的程序设计中,我们肯定是在不停的产生对象,而产生的每个对 象为了生存,都需要动用资源,尤其是内存资源。当对象不需要内存,就该加以 清除,是资源可被释放、可再被使用。在程序设计的环境中:典型的情况是我们 首先产生对象,比如使用 new 关键字来创建对象,同时返回该对象的一个引用 (C++中该引用可能是个指针) ,之后我们通过该引用使用这个对象,当我们不再 使用该对象, 或是该对象失去对它的所有引用时, 它就应该被摧毁。 在 C,C++ 或 其它语言中,程序员负责取消分配内存。有时,这是一件很困难的事情。因为你 并不总是事先知道内存应在何时被释放。当在系统中没有能够被分配的内存时, 可导致程序瘫痪,这种程序被称作具有内存漏洞。在 Java 编程语言中解除了程 序员取消分配内存的责任, 它可提供一种系统级线程以跟踪每一存储器的分配情 况。这种系统级线程就是垃圾收集机器,在 Java 虚拟机的空闲周期,垃圾收集 线程检查并释放那些可被释放的存储器。垃圾收集在 Java 技术程序的生命周期 中自动进行,它解除了取消分配内存的要求,并避免了存储器漏洞。然而,它的 运行时间却是非确定的,因此我们必须小心。
2.2 对象在内存中的分配
了解了内存中这些存放数据的方式后, 我们先来看看 C++中存放对象的机制: 在 C++中,对象可以是在堆栈中创建的,这样可达到更快的速度。然而,在 C++ 里创建“内存堆”(Heap)对象通常会慢得多。这种内存堆实际是一个大的内存 池,要求必须进行再循环(再生) 。这里可以把 C++的 Heap 想象是一块场地,在 这里面中每个对象不断监视属于自己的地盘, 他们可能在以后的某个时刻不再继 续占用目前所占用的空间,即释放后的内存会在堆里留下一个洞,所以再调用
2.3 垃圾收集的基本原理与算法
到这里大家可能会问垃圾收集器是如何知道哪些对象是存活的(需要的) , 而哪些对象是死亡的(不需要的)呢?许多 Java 的垃圾收集器都使用了引用的 根集,作为分析对象存活与否的依据。引用的根集是正在执行的 Java 程序随时 都可以访问的引用的变量的集合――也就是存在堆栈(Stack)或是静态存储空 间(Static storage)上的引用变量。 从这些根集变量出发可直接或是间接到达的 对象,垃圾收集器会认为这些对象是生命尚存的对象;相对的从这些根集变量出 发通过任意途径都无法到达的对象,就是死亡的,它们就会成为下一次垃圾收集 的对象。 具体过程就像这样: 从堆栈(Stack)和静态存储区域(Static storage) 开始, 走访所有引用之后, 就能找出所有活动的对象。 对于自己找到的每个引用 , 都必须再跟踪到它指向的那个对象,然后跟随那个对象中的所有引用,“跟踪追 击”到它们指向的对象,如此反复,直到遍历了根源于堆栈或静态存储区域中的 引用所形成的整个网络为止。中途经历的每个对象都必须仍处于活动状态。 对于垃圾收集器找到的那些活动对象,具体应采取哪些操作呢?这正是我们 接下去要讨论的,大部分垃圾收集器都是基于几种算法的工作方式,并且它们是 自适应的,也就是说他们会根据情况自动的选择恰当的工作方式。 1、基于 Copying 算法的“停止而后复制(stop-and-copy)”方式:就像 字面意思一样,当一个内存堆满了,程序首先会停止运行。随后,基于 Copying 算法的垃圾收集器从根集变量中跟踪所有存活的对象, 并将已找到的每个活动对 象从一个内存堆复制到另一个,留下所有的垃圾。除此以外,随着对象复制到新 堆,它们会一个接一个地首尾聚焦在一起。这样可使新堆显得更加紧凑(并使新 的存储区域可以简单地从最末端腾出空间,就像前面讲述的那样) 。当然,将一 个对象从一处挪到另一处时, 指向那个对象的所有引用 (引用变量) 都必须改变 。 对于那些通过跟踪内存堆的对象而获得的引用,以及那些静态存储区域,都可以 立即改变。但在“遍历”过程中,还有可能遇到指向这个对象的其他引用。一旦
1
Java 垃圾收集机器-Garbage Collector
Java 垃圾收集机器-Garbage Collector
[摘 要]:本文详细讨论了 Java 垃圾收集机制的基本原理,并结合 Java 语言特 性, 介绍利用 Java 垃圾收集机制提高程序的健壮性和性能的基本方法。 [关键词]:Garbage collector,Heap, Reference, 对象,JVM
4
Java 垃圾收集机器-Garbage Collector
发现这个问题,就得通过后续过程才能找到。 但是在有的情况下将使得“复制式垃圾收集器”显得效率极为低下,比如随 着程序进入了稳定状态之后,它几乎不产生或产生很少的垃圾。尽管如此,这时 的一个“复制式垃圾收集器”仍会将所有内存从一处复制到另一处,这显得非常 浪费。更糟的是:程序中的对象不仅不死亡,而且一个个都很庞大,对两个大的 内存堆管理也将成为不必要的消耗。
2百度文库
Java 垃圾收集机器-Garbage Collector
数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上 和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些 Java 数据 要保存在堆栈里——特别是对象的引用(也可称为对象的引用变量) ,但 Java 中的对象不会放在其中。 (2) 堆(Heap)。一种常规用途的内存池(也在 RAM 区域) ,其中保存了 Java 对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不 必知道要从堆里分配多少存储空间, 也不必知道存储的数据要在堆里停留多长的 时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需 用 new 命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保 存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时 会花掉更长的时间!这是导致 Java 性能不佳的因素之一。 SUN 的 JVM 使用分代方式(Generation)管理 堆 空 间 , “代”分配给新旧对象 的内存池。这些对象的不断积累会导致一个的内存状态,从而推动垃圾收集的开 始。如图说明了 SUN 的 JVM 中堆空间粗略的划分。
二、垃圾收集的基本原理:
2.1 JVM 中内存的划分
垃圾收集器对 Java 程序员来说,基本上是透明的,但是只有了解 GC 的工作 原理、如何优化 GC 的性能、如何与 GC 进行有限的交互,才能提高整个应用程序 的性能、全面提升内存的管理效率,为了说明其工作方式,我们首先看看内存中 几种常用的存放数据的地方: (1) 堆栈(Stack) : 位 于常规 RAM(随机访问存储器)区域,但可通过它的 “堆栈指针”获得处理器的直接支持。堆栈指针若向下移,会创建新的内存;若 向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次 于 CPU 的寄存器。创建程序时,Java 编译器必须准确地知道堆栈内保存的所有
12
3.2.1 软引用········································································ 13 3.2.2 弱引用········································································ 14 3.2.3 虚引用········································································· 15
2、基于 Tracing 算法、 Compacting 算法的 “标记和清除 (make-and- sweep)”方式:对于常规性的应用,标记和清除显得非常慢,但一旦知道自己 不产生垃圾,或者只产生很少的垃圾,它的速度就会非常快。标记和清除(make -and-sweep)采用的逻辑:基于 Tracing 算法从堆栈和静态存储区域开始,并 跟踪所有引用,寻找活动对象。然而,每次发现一个活动对象的时候,就会设置 一个标记(一个位或多个位) ,为那个对象作上“记号”。但此时尚不收集那个 对象。只有在标记过程结束,清除过程才正式开始。在清除过程中,不复存活的 对象会被释放然而,不会进行任何形式的复制(这时的堆中被使用的空间呈现不 连续的状态) 。所以假若收集器决定使这个断续的内存堆密集(compact),它将 使用 Compacting 算法重新整理他所找到的对象:垃圾收集器将所有的对象移置 堆的一端。堆的另一端就变成了一个相邻的空闲内存区。收集器会对它移动的所 有对象的所有引用进行更新,这样这些引用能够在新的位置识别同样的对象。为 了 简 化 对 象 引 用 的 更 新 , compacting 算法 增 加 了 间 接 的 一 层 ( level of indirection) 。间接层通过句柄(handle)和句柄表实现。在这种情况下,对象 引用总是指向句柄表中同样的句柄入口。反过来,句柄入口包含了句柄的实际引 用。当 compacting 标记和清除垃圾收集器移动对象时,收集器在句柄表中只修 改实际的对象引用对句柄入口的所有引用没有受到影响。 虽然使用句柄表简化了
3
Java 垃圾收集机器-Garbage Collector
new 的时候,存储分配机制必须进行某种形式的搜索,使新对象能够利用已经存 在的空洞,否则就会很快用光堆的存储空间。之所以内存堆的分配会在 C++里对 性能造成如此重大的性能影响,对可用内存的搜索正是一个重要的原因。所以创 建基于堆栈的对象要快得多。 而在 Java 中的内存堆(Heap)更像一条传送带:每次分配了一个新对象后, “Heap 指针”都会朝前移动,这意味着对象存储空间的分配可以达到非常快的 速度。因为“Heap 指针”只是单纯的往前移动至未经分配的区域,所以它与 C++ 的堆栈分配方式几乎是不相上下的(当然,在数据记录上会多花一些开销,但要 比搜索存储空间快多了) 。但是如果只是按那种方式分配,最终就要求进行大量 的内存页面交换(这对性能的发挥会产生巨大干扰) ,而且终究会用光内存,出 现内存分页错误 (page fault)。所以 Java 引入了“垃圾收集器”。它在收集“垃 圾”的同时,也负责重新紧密排列(compact)堆里的所有对象,消除内存空洞, 将“堆指针”移至尽可能靠近传送带开头的地方,远离发生(内存)分页错误的 地点。 垃圾收集器会重新安排所有东西, 使其成为一个高速、 无限自由的堆模型 , 同时游刃有余地分配存储空间。但是垃圾收集时的代价是非常高昂的,这也是导 致 Java 性能不佳的因素之一。
如图所 示 , 年 轻的 一 代 包 括年 轻的 对 象空 间( eden )和 两 个存 活 (survivor)空间(SS#1 和 SS#2) 。新对象被分配到 eden 中,那些存活较久的 对象则会从年轻的一代转移到老一代中。图中的 Perm 段叫做永久代(permanent generation) ,它保存了 JVM 的类和方法对象。 (3) 静态存储(Static)。这儿的“静态”(Static)是指“位于固定位置” (尽管也在 RAM 里)或是有且仅有一份。程序运行期间,静态存储的数据将随时 等候调用。可用 static 关键字指出一个对象的特定元素是静态的。但 Java 对象 本身永远都不会置入静态存储空间。
Java 垃圾收集机器-Garbage Collector
一、引言:·································································· 2 二、垃圾收集的基本原理:············································· 2 2.1 JVM 中内存的划分··············································· 2 2.2 对象在内存中的分配············································· 3 2.3 垃圾收集的基本原理与算法····································4 三、与垃圾收集器交互··················································· 7 3.1 FINALIZE ()方法·················································· 7
3.1.1 Finalize()用处之一:观察·································· 8 3.1.2 Finalize()用处之二:再生································ 11
3.2
JAVA.LANG.REF 包··················································
一、引言:
在面向对象的程序设计中,我们肯定是在不停的产生对象,而产生的每个对 象为了生存,都需要动用资源,尤其是内存资源。当对象不需要内存,就该加以 清除,是资源可被释放、可再被使用。在程序设计的环境中:典型的情况是我们 首先产生对象,比如使用 new 关键字来创建对象,同时返回该对象的一个引用 (C++中该引用可能是个指针) ,之后我们通过该引用使用这个对象,当我们不再 使用该对象, 或是该对象失去对它的所有引用时, 它就应该被摧毁。 在 C,C++ 或 其它语言中,程序员负责取消分配内存。有时,这是一件很困难的事情。因为你 并不总是事先知道内存应在何时被释放。当在系统中没有能够被分配的内存时, 可导致程序瘫痪,这种程序被称作具有内存漏洞。在 Java 编程语言中解除了程 序员取消分配内存的责任, 它可提供一种系统级线程以跟踪每一存储器的分配情 况。这种系统级线程就是垃圾收集机器,在 Java 虚拟机的空闲周期,垃圾收集 线程检查并释放那些可被释放的存储器。垃圾收集在 Java 技术程序的生命周期 中自动进行,它解除了取消分配内存的要求,并避免了存储器漏洞。然而,它的 运行时间却是非确定的,因此我们必须小心。
2.2 对象在内存中的分配
了解了内存中这些存放数据的方式后, 我们先来看看 C++中存放对象的机制: 在 C++中,对象可以是在堆栈中创建的,这样可达到更快的速度。然而,在 C++ 里创建“内存堆”(Heap)对象通常会慢得多。这种内存堆实际是一个大的内存 池,要求必须进行再循环(再生) 。这里可以把 C++的 Heap 想象是一块场地,在 这里面中每个对象不断监视属于自己的地盘, 他们可能在以后的某个时刻不再继 续占用目前所占用的空间,即释放后的内存会在堆里留下一个洞,所以再调用
2.3 垃圾收集的基本原理与算法
到这里大家可能会问垃圾收集器是如何知道哪些对象是存活的(需要的) , 而哪些对象是死亡的(不需要的)呢?许多 Java 的垃圾收集器都使用了引用的 根集,作为分析对象存活与否的依据。引用的根集是正在执行的 Java 程序随时 都可以访问的引用的变量的集合――也就是存在堆栈(Stack)或是静态存储空 间(Static storage)上的引用变量。 从这些根集变量出发可直接或是间接到达的 对象,垃圾收集器会认为这些对象是生命尚存的对象;相对的从这些根集变量出 发通过任意途径都无法到达的对象,就是死亡的,它们就会成为下一次垃圾收集 的对象。 具体过程就像这样: 从堆栈(Stack)和静态存储区域(Static storage) 开始, 走访所有引用之后, 就能找出所有活动的对象。 对于自己找到的每个引用 , 都必须再跟踪到它指向的那个对象,然后跟随那个对象中的所有引用,“跟踪追 击”到它们指向的对象,如此反复,直到遍历了根源于堆栈或静态存储区域中的 引用所形成的整个网络为止。中途经历的每个对象都必须仍处于活动状态。 对于垃圾收集器找到的那些活动对象,具体应采取哪些操作呢?这正是我们 接下去要讨论的,大部分垃圾收集器都是基于几种算法的工作方式,并且它们是 自适应的,也就是说他们会根据情况自动的选择恰当的工作方式。 1、基于 Copying 算法的“停止而后复制(stop-and-copy)”方式:就像 字面意思一样,当一个内存堆满了,程序首先会停止运行。随后,基于 Copying 算法的垃圾收集器从根集变量中跟踪所有存活的对象, 并将已找到的每个活动对 象从一个内存堆复制到另一个,留下所有的垃圾。除此以外,随着对象复制到新 堆,它们会一个接一个地首尾聚焦在一起。这样可使新堆显得更加紧凑(并使新 的存储区域可以简单地从最末端腾出空间,就像前面讲述的那样) 。当然,将一 个对象从一处挪到另一处时, 指向那个对象的所有引用 (引用变量) 都必须改变 。 对于那些通过跟踪内存堆的对象而获得的引用,以及那些静态存储区域,都可以 立即改变。但在“遍历”过程中,还有可能遇到指向这个对象的其他引用。一旦