3 分配
3.1 锁堆分配
当所需要的空间大于512字节或者在已有的缓存中无法分配所需的大小,就会发生锁堆分配,顾名思义,锁堆分配需要获取堆上的锁,所以应该尽量避免。
If size < 512 or enough space in cache try cacheAlloc return if OK HEAP_LOCK Do If there is a big enough chunk on freelist takeit goto Got it else manageAllocFailure if any error goto Get out End Do Cot it: Initialise object Get out: HEAP_UNLOCK |
图表 7 锁堆分配
图表7即是锁堆分配的伪代码,垃圾收集器首先检测要分配的空间的大小,如果小于512字节或者在当前缓存内有足够的空间进行分配,则尝试缓存分配;如果不能进行缓存分配,或者缓存分配失败,则会发生锁堆。垃圾收集器开始在空闲链表中查询可用的空间,如果找到满足条件的空间,则在此空间内进行分配,并且将剩余的空间返回到空闲链表中。需要注意的是,如果剩余的空闲空间不足512字节加上头信息大小(32位架构上12字节,64架构上24字节),那么就不会返回到空闲链表中,这些小的存储空间就被称为“暗物质”。如果垃圾收集器无法找到满足条件的空闲空间,就会发生分配失败,开始进行垃圾收集工作。垃圾收集结束之后,继续在空闲链表中查找满足需求的空间,如果依然无法找到,就会发生内存溢出的错误。无论对象是否分配成功,堆锁都会被释放。
3.1.1 小提示
在某些情况下,比如在很大的堆中,空闲链表是由大量的空闲存储空间段组成,或者应用频繁的申请较大的存储空间,锁堆分配策略可能会存在问题。因为每次都需要从空闲链表的头开始查找满足需求的空闲空间,效率相对较低,所以有了快速空闲链表查找算法来解决这个问题。
每次锁堆分配尝试查找链表时,都会收集以下数据:
· 在找到满足需求的空间之前已经在空闲链表中查找过的存储段数量
· 在找到满足需求的空间之前所查找的空闲段中的最大段的大小。也就是不能满足需求的段的最大大小
当找到满足需求的空间之后,如果查找计数大于20,表示需要创建一个active hint指向空闲链表。然后,根据实际的需求来决定是从空闲链表头开始查找,还是从active hint指向的位置开始查找,一旦在一个段中进行了分配,active hint会被及时更新。
3.2 缓存分配
图表 8 堆中的缓存段
缓存分配是针对小对象设计的高性能分配策略,线程预先从堆中分配一段空间,对象直接在这一段空间内进行分配,无需获取堆的锁,所以缓存分配的效率是极高的。如果满足以下条件,就会进行缓存分配:
· 对象小于512字节,或者
· 线程本地堆中有足够的空间容纳对象
图表8是堆中缓存段的示意图。缓存段又叫做线程本地堆(thread local heap,TLH)。当垃圾收集器为一个线程分配线程本地堆时,使用锁堆分配一段空间给该线程独享使用。分配集合中对应线程本地堆的比特位不会被设置,直到线程本地堆满了,或者一轮垃圾收集工作开始的时候,才会被设置。为了提高分配线程本地堆的性能,垃圾收集器总是使用在空闲链表中找到的第一个空闲段,并且不大于40KB。
图表 9 在缓存段中分配的对象
图表9显示的是在缓存段中分配的一些对象。在缓存段中,对象总是从段顶开始分配,这样能够比从段底分配更加高效。图表9中还显示了在分配集合中,并没有设置缓存段的标识位。直到缓存段满了,或者一轮垃圾收集工作开始的时候,这些标识位才会被设置。
4 垃圾收集
在锁堆分配中如果发生了分配失败,或者有对System.gc()的显式调用,则会发生垃圾收集。调用System.gc()的线程或者发生分配失败的线程负责进行垃圾收集。首先,他获取垃圾收集所需的锁,然后挂起其他线程,再开始垃圾收集的三个阶段的工作:标识、清理以及压缩阶段(非必须的)。IBM JDK的垃圾收集是stop-the-world类型的,因为在垃圾收集过程中,所有的应用线程都被挂起。
在标识阶段,所有的活动对象都被标识。因为不可到达对象不太容易定位,所以要明确所有的可到达对象,那么余下的就是垃圾了。这个标识所有可达到对象的过程也被称作追踪(tracing)。
被保存的寄存器、线程的执行栈、类中的静态域、本地或者全局的JNI引用,共同构成了虚拟机的活动状态。虚拟机自身调用的函数都会生成一个C执行栈上的一个帧。这个帧可能包含一些对象实例,可能是要赋值给本地变量的对象,也可能是来自调用者的调用参数。在追踪阶段,所有这些引用都是被同等对待的。垃圾收集器自顶到底扫描每个线程的栈,4字节为一组(在64位架构上是8字节一组),垃圾收集器假设栈是4字节对齐的(在64位架构上是8字节对齐的),然后检查栈上每个4字节组是否是指向堆上的一个对象,有可能所指向的不是一个真正的对象,因为可能只是碰巧和一个整数或者浮点数的存储表示相同。垃圾收集器扫描线程栈,然后保守的处理他所找到的这些指针,只要这个指针指向一个对象地址,那么就假设他真的是一个对象引用,并且在垃圾收集的时候,不能移动这个对象。如果满足以下3个条件,这个槽位就被认为是指向一个对象的指针:
1. 8字节对齐的
2. 位于堆的地址范围内
3. 对应的分配比特位已经设置为1
以这种方式被引用的对象就是根对象,根对象的dosed标识位被设置,表示这个对象不能被移动,只有在压缩阶段时,垃圾收集器才会设置dosed标识位。从根对象开始,可以精确的追踪其他被引用对象,因为垃圾收集器知道这些引用确实是真正指向对象的引用,由于可以修改引用,所以这些被引用的对象在压缩阶段可以被移动。追踪阶段使用一个可以容纳4KB条目的栈,所有的引用都被压栈,同时,设置对应的标识比特位。首先,全部根对象被压栈,然后再依次出栈,在出栈的过程中继续追踪。普通对象(非数组对象)通过mptr访问类信息块来追踪被其引用的其他对象。一旦找到引用,并且被引用对象尚未被标识,那么该对象就被标识并且压栈。
对于数组对象,垃圾收集器检查每个条目,如果该对象尚未被标识,则对其进行标识并且压栈。为了避免标识栈溢出,每次只处理数组的一部分内容。
垃圾收集器重复以上过程,直到标识栈为空。
4.1.1 标识栈溢出
因为标识栈的大小是有限制的,所有有可能发生标识栈溢出的问题。虽然这种问题发生的几率非常小,但是当发生标识栈溢出时,对于垃圾收集的暂停时间有非常大的影响。
4.1.1.1 溢出集合
垃圾收集器需要一个能够映射整个堆的比特位数组来记录堆中未被追踪的对象,就是FR_bits数组,该数组是为进行增量压缩(Incremental Compaction, IC)设置的,对于每个可能的引用槽位(在32位架构上是4字节,在64位架构上是8字节),有一个对应的比特位。由于JVMObject头信息不会包含任何引用信息,所以每个对象对应的FR_bits数组中的前2个比特位是不被IC使用的,因此垃圾收集器使用FR_bits数组中的第1个冗余比特位实现溢出集合。
4.1.1.2 处理非系统堆对象的标识栈溢出
当线程尝试将一个引用压栈到标识栈时,如果此时发现标识栈已经满了,他会向自己的本地标识队列发布一个任务。如果发布动作失败了,线程会设置这个引用对象对应的FR_bits数组,以表示发生了标识栈溢出。
然后追踪工作继续处理已经设置了FR_bits数组的、无法被压栈的引用。
一旦线程处理完了标识栈,他就会尝试接管溢出集合,并且,为了确保溢出集合只被一个线程处理,该线程设置一个是否发生标识栈溢出的全局标识为False。一旦确立了溢出集合的所属权,这个线程就开始扫描FR_bits数组,查找所有的非零比特位。一旦找到非零的比特位,则将其清零,并且对应的引用被压栈。到一定量的引用被压栈之后,他们被发布到本地标识队列,以便于其他线程辅助处理溢出集合。
在处理溢出集合的同时,有可能发生标识栈溢出。如果发生这种情况,那么一个全局标识会被设置以标识发生了溢出,上面描述的处理过程会重复执行。
4.1.1.3 系统堆溢出机制
在收集根对象时,垃圾收集器会将所有系统堆和ACS堆中的对象引用也压栈,因此同样会发生标识栈溢出的问题。但是FR_bits数组只映射了非系统堆,所以无法用来记录系统堆和ACS堆中未被追踪的对象。
在垃圾收集的标识阶段,已经加载的类的地址是不会被修改的,因此,垃圾收集器需要记录在发生标识栈溢出前的那一刻,他所到达的追踪链条的位置。所以,引入两个全局变量“overflowSystemClasses”和“overflowACSClasses”来表示对应在系统堆和ACS堆中的进行位置。当处理溢出集合时,这两个变量告诉垃圾收集器应该在什么位置停止。
4.1.1.4 处理系统堆对象的标识栈溢出
在并行标识(parallelMark)阶段,如果线程已经处理完了标识栈,接下来需要检查overflowSystemClasses和overflowACSClasses两个变量是否被设置。如果其中某个变量被设置,那么这个线程就会试图获取对应对象列表的控制权,并且将这个变量设置为NULL。一旦线程获取了控制权,就会将引用压栈到标识栈,一定量的引用被压栈之后,向本地标识队列发布任务,以允许其他线程辅助进行后续工作。
如果在处理的过程中,再次发生了标识栈溢出,线程会记录在发生溢出之前处理到的位置,然后重复上面的工作。
4.1.2 并行标识
由于优化的按位清理算法和压缩避免机制的存在,使得一个垃圾收集周期的主要时间消耗在对象标识阶段。所以,开发了并行标识技术,该技术使得在单CPU的主机上不降低标识的性能,并且能够在8路主机上将性能提高4倍左右。
在标识阶段的时间主要消耗在一些辅助线程以及协调这些辅助线程共同工作上。一个线程作为主要线程,也就是我们所说的垃圾收集主线程。这个线程负责扫描C堆栈,找到活动的根对象。在一个具有N个CPU的主机上,会创建N-1个辅助线程来辅助完成后续的标识阶段工作。辅助线程的数量可以由虚拟机启动参数
-Xgcthreadsn来重新设定。设置为1表示没有辅助线程,该参数取值范围为1到N。
更高层面讲,每个标识线程拥有自己的本地栈以及共享的队列,这两个变量都包含了那些已经被标识但是尚未被扫描的对象的引用。辅助线程大部分的标识工作都是依赖本地的栈变量来完成的,只有在需要负载均衡的时候才会在共享队列上进行同步的操作。由于对于标识比特位的操作是原子操作,所以无需获取锁。
由于每个线程的栈都可以容纳4KB个条目,标识队列可以容纳2KB个条目,所以大大降低了标识栈溢出的发生几率。
4.1.3 并发标识
并发标识机制保证在堆内存增大的时候,能够降低垃圾收集的暂停时间。在堆满之前,开始进行并发标识:垃圾收集器通知每个线程扫描自己的执行栈以查找根对象,然后基于这些根对象开始进行并发的追踪,追踪是在进行锁堆分配的时候,由一个较低优先级的后台线程以及全部的应用线程一起完成。
由于垃圾收集器在应用运行的同时标识活动对象,所以必须记录已经追踪的对象的任何变化。为了达到这个目的,他采用了一种写隔离(write barrier)的技术,在对象发生变化时被激活。首先将堆分隔成512字节的段,每个段对应卡片表(card table)中的一个字节。当指向一个对象的引用发生变化时,对应这个对象所在段的卡片表中的字节被设置为0x01。使用字节而不是比特位有两个原因:字节的写入速度比比特位更快,另外,字节中其他的比特位还可以用作其他用途。
如果发生以下情况,就会开始STW(Stop The World)收集:
· 分配失败
· System.gc()
· 并发标识阶段完成
垃圾收集器开始进行并发标识阶段的工作,以试图在堆耗尽之前完成垃圾收集工作。虚拟机启动参数可以管理并发标识的时间。
在STW阶段,垃圾收集器扫描所有的根对象,并且通过标识卡片来查看需要进一步追踪的对象,然后按照普通的模式进行清理。这样能够保证在并发标识阶段开始时的不可到达对象都可以被清理,但是无法保证在并发标识阶段进行过程中变成不可到达的对象也被清理。
并发标识策略可以减少垃圾收集的暂停时间,但是会带来额外消耗,因为应用线程需要在获取堆锁的时候进行一些跟踪工作。具体的性能降低取决于有多少空闲的CPU时间可以给后台线程使用。另外,写隔离机制也会有额外的消耗。
开启并发标识的参数为:
-Xgcpolicy:<optthruput|optavgpause>
设置-Xgcpolicy参数为optthruput禁用并发标识。optthruput是默认设置。如果你的应用中不存在暂停时间带来的问题,可以使用这个默认选项获得最好的吞吐能力。
设置-Xgcpolicy参数为optavgpause启用并发标识。如果应用因为垃圾收集的暂停时间导致响应能力下降,可以使用这个参数来改善情况,但是会降低应用吞吐量。
标识阶段结束之后,标识集合中对于堆中每个可到达对象都进行了标识,而且是分配集合的子集。清理阶段就是计算分配集合和标识集合的差集,也就是说,找到那些已经被分配但是已经不被引用的对象。
最初,使用按位清理(bitsweep)技术,这种技术检查标识集合中较长的连续0序列,可能对应的就是空闲空间。一旦找到连续0序列,垃圾收集器检查这个序列开始位置对应的对象的大小,来计算可以释放多少空间。如果这个大小大于512字节加上对象头大小,那么这一段空间就会重新加入到空闲链表中去。
未返回到空闲链表中的小的空闲空间,就被称作是“暗物质”,当紧邻“暗物质”空间的对象被释放之后,或者压缩动作执行之后,他就可能再次返回到空闲链表中去。垃圾收集器不需要清理空闲段中的每个对象,因为这一段都是可以被清理的对象。在这个过程中,标识集合会整个复制到分配集合中去,这样分配集合就表示了堆中所有的已分配对象。
4.2.1 并发按位清理技术
并发按位清理技术能够尽量使用可用的处理器以提高清理阶段的执行效率。在并发清理阶段,垃圾收集器使用和并发标识阶段同样的辅助线程,所以,默认的参与并发处理的线程数也可以由虚拟机参数-Xgcghreadsn来设置。堆被分割成多个段,段的数量要远远大于并发清理线程的数量,计算公式如下:
· 32 * 辅助线程数量 或者
· 堆的最大大小/16MB
中选择较大的一个。每次一个辅助线程选择一个段来扫描,然后按位清理,并且保存每个段的清理结果,完成之后,再重新建立空闲链表。
在清理阶段结束之后,垃圾收集器可以对堆中剩余的活动对象进行压缩,以移除他们之间的空闲空间。这个压缩过程是非常复杂的,因为一旦移动了一个对象,那么所有指向这个对象的引用都需要修改。如果是来自栈的引用,那么垃圾收集器还不能确定这就是一个对象引用(有可能碰巧只是一个浮点数),就不能移动这个对象。这样的对象还会继续保持在原来的位置,并且dosed标识位会被设置。类似的,JNI操作中引用的对象也是要固化在原来的位置不能被移动的,直到JNI操作结束,不再引用这个对象的时候,才可能被移动。利用mptr的低三位设置为0,垃圾收集器可以在两个阶段内压缩那些可以移动的对象。其中一位用来标识对象被清理,这个清理标识位出现在两个地方:size+flags(即OLINK_IsSwapped)以及mptr(即GC_FirstSwapped),这两种情况中,最低的位(x01)都会被设置。
下图可以帮助你理解压缩过程:
图表 10 压缩阶段工作
图表10展示了压缩的效果。假设从A到B是一个走廊,走廊里面有一些家具(蓝色的块),代表对象。空白的区域代表空闲空间或者“暗物质”,有两个家具被固定在地板上(交叉纹理块),代表固化的或者不能被移动的对象。压缩的过程就类似你要把家具从B推到A,尽可能的靠近A端。但是,不幸的是,你不能把家具举起来越过固定在地板上的家具,所以,他们右侧的家具最远也就推到紧邻他们的位置。
4.3.1 避免压缩
图表 11 荒地
避免压缩主要是致力于为对象找到合适的放置位置,以减少或者避免移动对象。避免紧技术中最主要的一个概念叫做荒地预留。荒地预留技术试图在堆中预留一段空间,然后尽量在其他地方进行对象分配。在堆顶和荒地部分之间,定义一个边界。如果存在大对象的分配,或者上一次垃圾收集之后尚未满足分配需求的情况下,就会使用荒地空间。
图表11是堆中荒地的示意图。荒地在活动的堆空间的最后进行分配,初始大小是活动堆空间的5%,根据实际需求进行收缩或者扩展。在锁堆分配失败的情况下,如果对象大小小于64KB或者上一次垃圾收集的结果取得足够的进展,那么就再开始一轮垃圾收集工作。足够的进展的意思是,自从上一次垃圾收集以来,至少有30%的堆空间被占用。30%是默认值,可以通过-Xminf参数设置。如果没有取得足够进展,或者对象大于等于64KB,那么会尝试在荒地中进行分配。这样就能够避免垃圾收集和堆压缩动作。
如果未设置虚拟机参数-Xnocompactgc并且以下几个条件任何一个为true,那么就会发生堆压缩动作:
· 设置了虚拟机参数-Xcompactgc
· 清理阶段结束之后,还是无法满足分配需求
· 调用System.gc()并且在最后一次分配失败发生或者并发标识收集之前发生了压缩动作
· TLH消耗了至少一半的存储,并且TLH的平均大小低于1000字节
· 堆的空闲空间小于5%
· 堆的空闲空间小于128KB
4.3.2 增量压缩
4.3.2.1 介绍
垃圾收集释放了对象空间之后,在堆中就会产生碎片。那么会引发一种现象,堆中还有足够的空闲空间,但是由于他们是不连续的,所以无法进行后续的分配。
压缩动作就是用来整理堆中的碎片,他将堆中分散的已分配的存储段移动到堆的一端,那么在堆的另一端就会生成一个较大的连续的空闲空间。但是压缩会增加垃圾收集的暂停时间,对于1GB的堆,如果进行了压缩动作,垃圾收集的暂停时间可能增加到40秒。对于应用而言,这么长的暂停时间通常是不可接受的。增量压缩技术就是将压缩动作分散到多次垃圾收集周期中,以减少暂停时间。
增量压缩的另一个重要作用是清理暗物质。暗物质就是堆中的很小的(小于512字节)存储片,这些存储片不在空闲链表中,因此不能被重新利用。暗物质的存在程度直接影响了应用的吞吐能力,因为越多的暗物质就会导致堆中可用的存储空间越少,可用存储越少,垃圾收集发生的频率就会越高,对于应用的性能会产生非常明显的影响。这些暗物质分散在整个堆中,会占用堆的大部分空间。
4.3.2.2 增量压缩概览
图表 12 增量压缩
在图表12中,涂色的块表示已经分配的空间,未涂色的块表示空闲空间。暗物质会分布在空闲空间之间。下半部分表示在增量压缩之后,对象被移动到段的一侧,另一侧就整理出了较大的连续空闲空间。
只有在堆大小大于某个值(当前是128MB)时,才会发生增量压缩。如果堆小于128MB,增量压缩并不会带来比完整压缩更短的暂停时间。
增量压缩有如下两步:
1) 记录和标识指向压缩区域的所有引用,这个动作在标识阶段完成
2) 计算对象的新位置,在区域内进行压缩,并修改指向被移动对象的引用
增量压缩在一个周期内进行,一个增量压缩周期就是指在垃圾收集周期内,一次一个区域的完成整个堆的压缩动作。压缩动作会跨越多个垃圾收集周期,因此将压缩动作的耗时分散到多个垃圾收集周期,减少暂停随时间。
4.3.2.3 增量压缩相关的主要参数
默认是启用增量压缩的,是否运行增量压缩取决于一些触发条件。但是有两个参数可以由用户决定是使用增量压缩还是传统压缩:
· -Xpartialcompactgc,表示每次垃圾收集都使用增量压缩,除非必须进行完整的压缩动作
· -Xnopartialcompactgc,表示禁用增量压缩机制
但是,需要提醒的是,-X参数属于非标准的虚拟机参数,可能在未通知的情况下进行修改。
引用对象能够使得所有的对象引用都以同样的方式被操作和处理,因此垃圾收集器在堆上创建两个独立的对象:一个是对象本身,一个是引用对象。当对象处于不可到达状态时,该引用对象可以方便的加入到一个队列中去。SoftReference,WeakReference以及PhantomReference由用户创建,不能修改,即,他们只能指向创建时的那个对象,不能指向其他对象。带有finalizer方法的对象在创建时注册Finalizer类,因此FinalReference对象指向一个需要finalize的对象,并且关联到Finalizer队列。
在垃圾收集阶段,引用对象被特殊处理:在标识阶段,不会处理引用对象的引用字段,标识阶段结束之后,按照如下顺序处理引用字段:
1) Soft
2) Weak
3) Final
4) Phantom
对于SoftReference对象的处理稍有特殊,如果他所指向的对象未被标识,ST组件会清理这个SoftReference。如果存储不足,那么垃圾收集器根据最近最常用的规则来进行清理。使用率根据最后一次被调用get方法来测量。一旦一个引用对象被处理,他所指向的真实对象会被标识,这样能够确保如果一个FinalReference也指向同一个对象,他能够看到这个标识。FinalReferece不会被放到处理队列中去。因此,在垃圾收集周期可以成功处理引用对象。
指向未标识对象的引用对象最初被放到ReferenceHandler线程的队列中,ReferenceHandler线程把对象从他的队列中移除,同时查看这个对象自己的队列是否存在,如果存在,则这个对象被重新放入ReferenceHandler线程的队列中去,以进行后续的处理。因此,FinalReference会重新入队,确保最后finalize方法被finalizer线程执行。
4.4.1 JNI Weak引用
JNI Weak引用对象提供类似WeakReference对象的功能,但是处理机制是不同的。一段JNI代码可以创建或者删除JNI Weak引用对象,这个对象指向一个真正的对象。当引用对象指向的对象未被标识,垃圾收集器会清理这个引用对象,但是不同于前一章提到的队列机制。需要注意的是,如果清理JNI Weak引用对象失败,可能会引发内存泄漏以及性能方面的问题。对于全局的JNI引用,也是同样的处理方式。JNI Weak引用对象最后会被引用对象处理线程处理,因此,对于一个已经被finalize的对象,如果存在指向他的phantom引用,他的JNI Weak引用对象也会持续存在。
堆扩展发生在垃圾收集完毕并且所有线程重新启动之后,但是HEAP_LOCK尚被持有的情况下。如果满足以下条件之一,堆的活动空间会进行扩展,直到堆的最大限制:
· 垃圾收集无法释放足够的空间来满足分配需求
· 空闲空间低于最小空闲空闲设定,-Xminf参数,默认30%
· 垃圾收集占用了超过13%的时间,并且按照最小扩展量(-Xmine)扩展之后,还是无法满足堆内最大空闲空间(-Xmaxf)要求的。
每次扩展的量计算规则如下:
· 如果由于无法满足堆的空闲空间为-Xminf(30%),垃圾收集器计算能够满足-Xminf堆空闲的扩展量。如果这个计算结果大于最大扩展量-Xmaxe(默认为0,即没有最大扩展量限制),那么会采用-Xmaxe设定的数量。如果计算结果小于-Xmine(默认为1MB),那么会按照-Xmine设定进行扩展
· 如果由于垃圾收集之后仍然不能满足分配需求,并且垃圾收集的时间占用总运行时间的比例不超过13%,则按照实际的分配需求来扩展
· 如果由于其他原因触发扩展,则垃圾收集器计算能够满足17.5%堆空闲的扩展量。类似上面,根据-Xmaxe和-Xmine的设定进行调整
· 最终,如果垃圾收集未能释放出满足分配需求的空间,则堆的扩展要保证至少满足分配需求
所有计算出的扩展量,在32位架构上是64KB的整倍,在64位架构上是4MB的整倍。
堆收缩发生在垃圾收集完毕并且所有线程都处于挂起状态时。如果满足以下任何一个条件,就不会发生堆收缩:
· 垃圾收集未能释放满足分配需求的空闲空间
· -Xmaxf参数设定为100%(默认是60%)
· 在最近的3次垃圾收集之中发生过堆扩展
· 由于调用System.gc()发生的垃圾收集,并且在收集周期开始之前,堆的空闲空间小于-Xminf(30%)
如果以上条件都不满足,并且存在大于-Xmaxf的空闲空间,垃圾收集器会计算收缩量,以保证能够拥有-Xmaxf的空闲空间,并且不小于初始大小(-Xms)。计算出的收缩量在32位架构上是64KB的整倍,在64位架构上是4MB的整倍。
如果满足以下任何一个条件,会在收缩之前发生压缩:
· 在本次垃圾收集周期未进行压缩
· 堆尾没有空闲存储片,或者堆尾的空闲存储片小于需要收缩量的10%
· 在上一次垃圾收集周期中未发生压缩和收缩
从1.3.0以后,引入了Resettable JVM,该虚拟机只能运行在z/OS平台。在http://www.s390.ibm.com/Java有详细的文档
posted on 2008-04-22 11:29
YODA 阅读(2176)
评论(0) 编辑 收藏