java SE平台的一大优势就是将开发者从复杂的内存分配和垃圾回收中解脱出来。但是当垃圾回收成为系统瓶颈时,了解垃圾回收和
内存分配这些隐藏的实现显得相当有用。垃圾收集器假定应用使用对象的方式,通过可调整的参数来实现性能的优化。
一个对象在运行的程序中不被任务引用时被认为是垃圾,最简音的垃圾回收算法就是循环遍历每一个可达的对象,任何被留下的对象
就被认为是垃圾对象。这种方式花费的时间与存活的对象数目成正比,不适用于那些持有大量存活对象的大型应用。
JVM集成了大量不同的垃圾回收算法,用于分代垃圾回收时组合使用。当单纯垃圾回收检查堆中所有存活对象时,分代垃圾回收利用
大多数应用的许多可见的以经验为主的属性来简化回收无用对象的工作。这许多的属性即为弱分代假设。
关于弱分代假设:
(1)大多数对象很快就会变成垃圾对象
(2)对象通常不会存活很长时间
的确有一些对象存活很长时间,如有些对象应用初始化时分配,存活到应用结束。同样也有一些存活一段时间的对象,但是大部分应
用对象都符合这一点”die young”,即对象多数年轻死去。
基于此种情况的优化,对内存进行分代管理,即不同的内存池持有不同年龄段的对象。当哪个代(内存池)无空间时,垃圾回收就会
在该带触发。绝大多数对象都会分配在标识为年轻代的内存池中,并且死在这里。当年轻代内存池存满时,就会触发叫做minor garb
-age collection的垃圾回收,此垃圾回收只回收年轻代的垃圾。假设弱分代假设成立,年轻代大多数对象是垃圾,是可以回收的,m
-inor garbage collection是最优的选择。此种垃圾回收的价值在于可以在第一阶段就快速的回收大量存活对象(年轻代的许多存活
对象都会很快变为垃圾对象)。通常的,年轻代幸存的一小部分对象会在minor collection期间被转移到老年代。最终,老年空间存
满数据时必须进行垃圾回收,也就是Major garbage collection,此种垃圾回收会对整个堆空间进入垃圾回收。通常情况下,Major
garbage Collection比Minor Garbage Collection多花费非常多的时间,因为有更多数量的对象参与其中。
作为Ergonomics区域需要注意的,针对不同种类的应用,需要选择不同的垃圾回收器以获取良好的性能。串行垃圾收集器(The Seri
-al Garbage Collection)是为小数据量应用设计的,并且它的默认参数对大多数小的应用都是有效的。并行或者吞吐量垃圾收集器
(The Parallel or Throughput Garbage Collection)是为具有中等数据量的应用设计的,ergonomics 选择的堆空间大小参数加上
自适应大小的功能为服务应用提供良好的性能。这些默认的选择大多数情况都有效,但并不是全部情况,这也是本文档的核心所在。
注:如果垃圾回收成为系统的瓶颈,那么很可能需要订制的堆空间大小与个人的代空间大小。检查垃圾收集大的冗余输出,然后探索
个人性能度量对垃圾收集器参数的敏感性。
在初始化时,最大内存空间其实是预订的,并不会真的分配物于内存除非需要。此预订的对象内存空间可以划分为年轻代与老年代。
年轻代由一个Eden空间与两个Survivor空是组成。绝大多数对象初始化分配在Eden空间。任何时候总有一个Survivor空间是空的,并
且是作为Eden空间存活对象的一个目的地。另外一个Survivor空间则为下次复制收集的目的地。对象以这种方式被复制在两个Surviv
-or直到它们老的足够被复制到老年代中。
性能考虑:
垃圾回收性能考虑的两个主要衡量标准:
(1)Throughput 是很长一段时间内未被用于垃圾回收的总时间的百分比,Throughput包含用于内存分配的时间,但通常不不规则要
调整分配内存的速度。
(2)Pauses 是指程序无响应的时间,因为正在进行垃圾回收。
用户对垃圾回收有着不同的需求。例如,一些人以为Web服务的正确度量是吞吐量,因为垃圾回收期间的暂停可能是可以忍受的,或者
或以简单的以网络延迟掩盖。但是在交互式图形应用中,即使是短暂的停顿也可能会对用户体验产生负面影响。
有一些用户对于其他方面考虑很在意。Footprint是一个进程的工作集,以内存分页与缓存行衡量。在内存有限或者多进程的系统上,
footprint可能规定扩展性。及时性是指从对象死亡到其所占空间变为重新可用的时间,是对于分布式系统的一个重要考虑,包括远程
方法调用。
通常情况下,选择调整代空间大小是一种在这些考虑之间的权衡。例如,设置一个非常大的年轻代空间可能最大化throughput(吞吐
量),但是是以空间占用,及时性,和暂停时间为代价的。可以通过设置一个较小的年轻代空间最小化年轻代的停顿,代价是吞吐量
。一个代空间大小的改变不会影响其他代的垃圾回收频率与停顿时间。
没有一个正确的方式可用来设置代空间的大小,最好的选择就是让程序使用内存的方式与用户的需求决定。因此JAVA虚拟机对垃圾收
集器的选择可能不是最好的,并且有可能会被 Sizing the Generations区域描述的命令行选项覆盖。
吞吐量与空间占用最好通过特定应用的度量来衡量。例如,一个Web服务器的吞吐量可以使用客户端负载发生器测试,然而服务器的空
间占用可能在Solaris操作系统上通过使用pmap来检测。但是由于垃圾回收产生的停顿可以通过虚拟机自身的诊断输出很容易的评估出
来。
命令行选项”-verbose:gc”用来在每次垃圾回收时打印堆空间信息与垃圾回收信息。例如,如下是一个大型服务应用输出的:
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
此输出结果表明已进行了两次minor collection和一次major collection,箭头前后的数据表示垃圾回收发生前后存活对象所占用的
空间。在minor collection后,此空间里包含一些是垃圾但是无法回收的对象,它们要么被老年代对象包含,要么被老年代指向。
括号里的数据为堆空间真实大小:用于存放java戏象的空间,并且不用从操作系统申请更多的内存。需要注意的是,此数字仅包含一
个Survivor空间,任何存储对象的时候都仅有一个Survivor空间被使用,除了垃圾回收期间。
最后一项数字则表示垃圾回收所花费的时间。
命令行选项-XX:+PrintGCDetails会输出垃圾回收的一些额外信息。对于串行垃圾收集器使用此参数的输出如下:
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]
此结果表示回收了大约年轻代98%的空间(DefNew: 64575K->959K(64576K)),花费了0.0457646秒的时间。整个堆空间的使用减少到
大约51% (196016K->133633K(261184K)),最后的时间0.0459067 secs(大于年轻代垃圾回收的时间)则表明还存在一些轻微的垃圾回
收的额外开销。
命令行选项-XX:+PrintGCTimeStamps会在每次垃圾回收前记录时间戳,并与垃圾回收信息一起输出。这对于查看垃圾回收的频繁程度
非常有用。
111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505 secs]
111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]
此垃圾回收发生在程序运行了111秒的时候,minor collection也是在这个时间发生。另外,此信息同时也展示Tenured标识的major
collection信息,老年代空间的使用率被减少到大约10% (18154K->2311K(24576K))并花费了0.1290354秒的时间。