Generations
Young Generation
组成:eden + 2 survivor spaces
Young generation的gc称作minor collection
minor collection的时间和object数量成正比
typically, 每次minor collection都会有一些surviving objects转移到tenured generation
survivor space的使用:
在任何时候,总有一个survivor space是empty的,在下一次coping collection时,会将eden和另一个survivor space里的live object copy到这个里面。
live objects在两个survivor space里copy来copy去,直到对象old enough可以放到tenured generation里(copy 过去的)
Tenured Generations
当tenured generation满的时候会出发major collection
major collection是对整个heap进行gc,花费的时间比minor collection的时间更长
Permanent Generation
类和方法的定义放在permanent generation.
Performance Considerations
性能关心的指标有:
Throughput: CPU不花在垃圾回收时间上的比例。包含内存分配时间。
Pauses: 因垃圾回收而造成无法响应的时间
Footprint: 程序所用的内存(is the working set of a process, measured in pages and cache lines)
Promptness: 对象死掉(没有其他对象引用时,unreachable时)到内存可用时(gc后才可用嘛)的时间差,(is the time between when an object becomes dead and when the memory becomes available, an important consideration for distributed systems, including remote method invocation (RMI))
Trade-off
如果young generation很大,那么可以大大提高throughput,但是在pause时间,footprint,promptness方面要付出代价。
如果young generation很小,那么pause时间会降低,但是会影响throughput。
一个generation的大小不会影响另外一个generation的gc频率和pause时间。
Measurement
-verbose:gc
每次gc的时候,都会输出
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
325407K->83000K
前面的数字表示minor gc前总heap里live objects的大小,后面的数字包括live objects和无法回收的垃圾,
比如在tenured区或者被tenured或permanent引用的objects括号里的
(776768K)
是总heap里committed部分的大小(可以不向操作系统申请直接使用的内存),
不算permanent generation,再减掉一个survivor space。(因为survivor space同时只会用一个)0.2300771 secs
是gc时间
-XX:+PrintGCDetails
下面这个例子是使用serial collector时的输出:
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]]
[DefNew: 64575K->959K(64576K), 0.0457646 secs]
是young generation的情况,包括live objects前后的变化,young generation里commit的heap大小196016K->133633K(261184K)
是总heap的情况,包括live objects前后的变化,总heap里commited部分的大小。
-XX:+PrintGCTimeStamps
比之前再多打印gc的timestamp
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]
上面这个例子是minor collection和major collection同时启动的意思。
注意上面的括号里的数字,(8128K) + (24576K)
正好等于(32704K)
,也就是young / tenured generation committed size 相加的数值。
Sizing the Generations
-Xmx
规定了要为heap保留的内存空间。如果-Xms
比-Xmx
小,那么就不是所有被reserved的空间都会被committed,uncommited的空间被认为是virtual的。
下面是serial collector的内存模型:
Total Heap
本小节讨论的heap的growing and shrinking和default heap size对parallel collector是不起作用的。
但是设置heap total size和generation size对parallel collector是起作用的。
因为collection发生在generation满的时候,所以throughput和内存大小是反比的。(内存小,更容易满,更频繁gc)。所以总的可用内存大小是影响垃圾收集性能的重要因素。
默认情况下,jvm在每次gc的时候会grow或者shrink heap大小,以保证free space和live objects的比例能够维持在一个特定的范围内。
-Xms<min>
,-Xmx<max>
: 这两个是调整total heap size的-XX:MinHeapFreeRatio=<minimum>
,-XX:MaxHeapFreeRatio=<maximum>
: 这两个控制free space在total heap里的比例。
如果free space小于XX%,那么generation会扩展(扩展的部分自然就是Free space),最大不超过generation最大大小。
如果free space大于XX%,那么generation会缩小,最小不低于generation 最小大小。
Parameter | Default Value | 64 bit server |
---|---|---|
MinHeapFreeRatio |
40 | 40 |
MaxHeapFreeRatio |
70 | 70 |
-Xms |
3670k | 要加30%左右,因为64位机器上的对象更大 |
-Xmx |
64m | 要加30%左右,因为64位机器上的对象更大 |
对于服务器程序的原则是:
保证越多内存越好
将
-Xms
和-Xmx
设置成一样大,关掉jvm自动调整大小的机制。增加处理器的时候同时也增加内存,因为内存分配可以并行。
The Young Generation
young generation的比例也很重要。young generation比例越高,那么minor collection的次数也越少。但是此消彼长,会造成tenured generation空间变小,那么会增加major collection的频率。
默认情况下,-XX:NewRatio
控制了young generation的大小。比如:-XX:NewRatio=3
,3代表young generation和tenured generation的比例是1:3。也就是说eden和2个survivor加起来所占整个heap size的1/4。
-XX:NewSize
和-XX:MaxNewSize
规定了young generation的上下界。可以将这两个设置成一样,来固定young generation的大小。
Survivor Space Sizing
可以用SurvivorRatio
调整survivor space的大小,不过一般survivor space不是性能的关键因素。比如:-XX:SurivorRatio=6
,代表survivor space是eden的1/6,也是整个young generation的1/8(还记得survivor space有两个的事情吗?)
如果survivor space太小,那么就会更容易溢出,从而把对象放到tenured generation里。如果太大会被浪费。
每一次gc的时候jvm会选择一个阈值来确定一个对象是否能被copy 到tenured generation里。这个阈值被选择为能够保持survivor space半满的状态。
用-XX:+PrintTenuringDistribution
可以显示这个阈值,以及对象的年龄。
对于服务器程序的原则是:
决定你能够给jvm的最大heap内存,然后根据young generation的大小来找到最好的设置。(最大堆内存一定要小于实际物理内存,否则会有错误)
如果total heap size固定,那么增加young generation会降低tenured generation,保证tenured generation大到足够hold住程序在任何时候都能使用的live object,在此基础上再加上10%~20%。
-
除了以上对老年代的限制:
保证young generation有很多内存
增加处理器的时候也加大young generation的大小,因为内存分配可以并行。
Available Collectors
serial collector用单个线程处理所有的gc工作。适合单处理器机器。也比较适合数据量小的程序(100M)。在某些硬件和OS里,serial collector是默认的,也可用
-XX:+UseSerialGC
来强制使用。-
parallel collector(throughput collector),在执行minor collection的时候是并行的,适合数据量中大型的,运行在多处理器或多线程硬件的程序上。在某些硬件和OS里,parallel collector是默认的,也可用
-XX:+UserParallelGC
。JDK1.6的新特性:可以启动parallel compaction是,支持并行major collection,用
-XX:+UserParallelOldGC
打开这个特性。
concurrent collector大部分时间是并发来gc的(程序还可以在跑),这样pause time就更短。适用于数据量大中型,且对响应时间比较在意,而对throughput不是太在意的程序。用
-XX:+UseConcMarkSweepGC
Selecting a Collector
除非对pause time很在意,你可以先跑你的程序,让jvm自动为你选择collector,可以适当增加heap size来提高性能,如果性能还达不到要求,那么按照下列原则来做:
如果程序的数据量很小(100M以下),用
-XX:+UseSerialGC
-
如果在单处理器上跑的,然后也没有pause time要求的:
让JVM自己选
或者用
-XX:+UseSerialGC
-
如果程序的峰值性能是第一要务,且pause time没有要求或者停个1秒以上也无所谓的,可以:
让JVM自己选
用
-XX:+UseParallelGC
,还可以加上+XX:+UseParallelOldGC
-
如果相应时间比throughput更重要,且pause time必须比1秒短,那么可以:
用
-XX:+UseConcMarkSweepGC
上面这些指导意见不是万能的,因为性能很依赖heap size,live objects的数量,处理器的数量和速度。pause time对这些因素特别敏感,所以上面讲的1秒只是一个大概的估计。parallel collector在很多数据-硬件的组合下pause time会超过一秒。而concurrent collector也可能在某些数据-硬件组合下无法小于一秒。
Parallel Collector
-XX:+UseParallelGC
,默认minor collection是并行的,major collection是单线程的。
加上-XX:+UseParallelOldGC
,可以让major collection也并行。
默认有N个处理器,那就会有N个gc线程。但是可以用:-XX:ParallelGCThreads=N
调整。
在单处理器机器上,性能反而不如serial collector,因为涉及到synchronize的开销。
因为有多个gc线程,所以在将对象从young generation升到tenured generation的时候会产生一些碎片。每个gc线程会在tenured generation里保留一块区域,而这种分配方式会产生碎片。如果减少gc线程数量就会减少碎片,而且能够增加tenured generation的大小。
Generations
前面讲过的,parallel collector的generation布局是不太一样的。
Ergonomics
JDK1.5开始,parallel collector是server-class机器的默认选择,而且parallel collector使用了一种自动tuning的方法,用户可以设定gc行为的要求,而不是用调整generation size或者其他low-level的tuning细节,能够被设置的行为有:
-
最大gc停止时间
最大gc pause time
-XX:MaxGCPauseMillis=<N>
,默认对max pause time是没有要求的,如果设置了,那么heap size和其他gc相关参数会调整到能保证gc的pause time满足小于这个值。设置这个可能会降低throughput,而且也未必能够达到。
-
throughput
throughput可以用gc时间比非gc时间来衡量,可以用
-XX:GCTimeRatio=<N>
来设定,结果就是 1/(1+N)。默认值是99,也就是1%的时间用来gc。
-
footprint
最大heap footprint用
-Xmx<N>
来设置,collector会隐性的设置最小heap size来满足其他要求
Priority of Goals
这些要求的优先顺序和上面列出的顺序是一致的,也就是说献满足了第一个才会去满足第二个。
Generation Size Adjustment
还可以调整generation的增长、减少比率,默认增长比率为20%,缩小比率为5%。
-XX:YoungGenerationSizeIncrement=<Y>
young generation增长比率-XX:TenuredGenerationSizeIncrement=<T>
tenured generation增长比率-XX:AdaptiveSizeDecrementScaleFactor=<D>
缩小因子,如果增长比率是X,那么缩小比率就是 X / D
如果collector决定在启动的时候就增长generation,那么会在原增长比率上有一个增加量,这个增加量会随着gc次数越来越低,这个主要是为了提高启动的性能。缩小时没有增加量。
如果最大pause time的目标没有达成,那么会一次自会缩小一个generation。如果两个generation都达不到目标,那么pause time较长的那个会先被缩小。
如果throughput的目标没有达到,那么两个generation区域都会被增加。然后按照比例扩展,比如young generation的gc时间占到总gc时间的25%,而增长比率是20%,那么实际会增长5%。
Default Heap Size
如果没有设置-Xms
和-Xmx
,那么起始和最大heap大小是根据机器的内存来计算的。
用作Heap的内存的比率是由DefaultInitialRAMFraction
和DefaultMaxRAMFraction
控制的。
起始heap:
内存大小 / DefaultInitialRAMFraction
,默认:内存大小 / 64最大heap:
MIN(内存大小 / DefaultMaxRAMFraction, 1GB)
,默认:MIN(内存大小 / 4, 1GB)
Excessive GC Time and OutOfMemoryError
parallel collector如果花了太多时间在gc,会抛出OutOfMemoryError
,这个值是如果98%以上的时间花在gc上,而少于2%的heap被释放的时候。
这个主要是防止程序长时间停滞,可以用-XX:-UseGCOverheadLimit
关掉这个特性。
The Concurrent Collector
-XX:+UseConcMarkSweepGC
concurrent collector适合期望更短的pause time,而且也能够将处理器资源分享给gc的程序。拥有大量long-lived data(庞大的tenured generation),而且又跑在多处理器机器上的可以用这个。
通过在程序运行的同时使用另外的garbage collector线程跟踪reachable objects,concurrent collector尝试减少因major collection引起的pause time。
在每次major collection cycle
concurrent collector会在一开始暂停所有的程序进程一小会儿,然后在中期阶段会有第二次pause。
第二次pause时间更长,在此期间会启动多个线程来做gc工作。
此后,大量跟踪live objects,清扫unreachable objects的工作会由一个或多个garbage collector thread在程序运行的同时完成。
minor collection可以在major collection的时候穿插进行,行为和parallel collection类似,而且在minor collection的时候程序也会pause。
具体算法要看这篇paper: A Generational Mostly-concurrent Garbage Collector
Overhead of Concurrency
concurrent collector是拿处理器资源去换更短的major collection pause time。
明显的开销就是处理器:在N个处理器的系统里,会使用K / N的处理器做gc,1 <= K <= ceiling (N / 4),K的数量是动态的。
另一个开销是并发,所以虽然gc pause time缩短了,但是程序throughput可能会因此降低。
因为在垃圾收集的时候,还有其他处理器可供程序使用,所以gc线程不会pause程序。
一般来说只会有shorter pause,不过因为处理器资源被占用了,所以会程序会slow down,特别是当这个程序是最大程度利用处理器的程序时,会比较明显。
当到达极限的时候, 随着N的增加,因concurrent gc造成的处理器资源缩减会随之变小,而concurrent gc的好处会变大。
因为在current gc的时候至少有一个处理器被使用,所以在单处理器的机器上这个没用。
Concurrent Mode Failure
concurrent collector使用一个或多个gc线程和程序线程一起跑,从而可以在tenured或permanent generation满之前完成垃圾收集。
前面讲过在普通操作中,concurrent collector在程序线程还在跑的时候做了大部分跟踪和清扫的工作,所以程序线程只会出现短暂pause。
但是如果遇到下面两个情况中的一个,那么程序线程会全部pause,然后完成gc:
concurrent collector无法在tenured generation满之前回收完unreachable对象
无法用tenured generation里已有的free space blocks分配内存。
这种无法concurrently 完成gc的情况称为concurrent mode failure,而且这也说明需要调整concurrent collector参数。
Excessive GC Time and OutOfMemoryError
如果有太多时间花在gc的时候,会抛出OutOfMemoryError
:98%的时间花在gc,而少于2%的heap被回收。
可以用-XX:-UseGCOverheadLimit
关掉这个特性。
这个策略和parallel collection一样,但是时间的计算不一样。这里时间值只计算程序全部停止的时间,也就是concurrent mode failure或者显式调用gc时(System.gc()
)的时间。
Floating Garbage
concurrent collector和其他hotspot collector一样,是一种tracing collector,这种collector至少会跟踪heap里所有的reachable对象。
因为程序线程和gc线程是同时跑的,所以被gc跟踪的对象可能会在gc结束的时候变成unreachable。
这种还没被回收的对象称为floating garbage.
floating garbage的数量依赖于concurrent gc的时长和程序object reference update的频率(mutation)。
而且,since the young generation and the tenured generation are collected independently,each acts a source of roots to the other。(意思是说,当ygc的时候,tenured generation的对象作为young generation对象的源头来看是否reachable,反之亦然)
floating garbage会在下一次concurrent collection cycle的时候回收掉。
大拇指原则,试着把tenured generation增加20%给floating garbage用。
Concurrent Collection Cycle
一个concurrent collection cycle是:
initial mark pause,把能够直接reachable from roots和reachable from heap里其他地方的objects标记为live
concurrent tracing phase,concurrent多线程trace reachable object graph
remark pause,在collector跟踪完一个对象之后,程序线程可能会更新的对象内部的引用,这会造成concurrent tracing漏掉一些对象,这个pause就是要找到这些对象
concurrent sweeping phase,concurrent收集被标记为unreachable的对象
一个cycle结束后,就等下一次major collection cycle,期间不会消耗什么资源
Starting a Concurrent Collection Cycle
serial collector是在tenured generation满的时候才进行major collection的。
concurrent collection不能这样,它要保证collection能够在tenured generation满之前能够执行完,
否则就会产生concurrent mode failure,然后程序会面临更长时间的pause。
有这么几种开始的方式:
根据历史情况,估计tenured generation还剩多少时间满,执行concurrent collection cycle所需要的时间,在此基础上再加上预留量,然后择机触发。
当tenured generation的占用超过initiating occupancy的时候,也会触发concurrent collection,默认大约为92%,不过各个jdk版本可能不一样,用
-XX:CMSInitiatingOccupancyFraction=<N>
(N,1~100)可以设定。
Scheduling Pauses
young generation collection和tenured generation collection的pause是独立出现的。
他们不会重叠,但是会连在一起,看上去就好像一个比较长的pause一样。
为了避免这种问题,concurrent collector会尝试把remark pause放在前一个和下一个young generation pause之间。
initial mark pause不会特别处理,因为它要比remark pause短得多。
Incremental Mode
concurrent collector可以以递增的模式进行,在concurrent phase里会使用一个或者多个处理器。
为了降低对程序的影响,incremental mode会周期性的停止concurrent phase把处理器让给程序用。
incremental mode(i-cms)会把大段的concurrent工作分成小片在young generation collection之间做。
这个对于只有少量处理器,但是又想降低pause time的程序挺有用的。
The concurrent collection cycle typically includes the following steps:
stop all application threads and identify the set of objects reachable from roots, then resume all application threads
concurrently trace the reachable object graph, using one or more processors, while the application threads are executing
concurrently retrace sections of the object graph that were modified since the tracing in the previous step, using one processor
stop all application threads and retrace sections of the roots and object graph that may have been modified since they were last examined, then resume all application threads
concurrently sweep up the unreachable objects to the free lists used for allocation, using one processor
concurrently resize the heap and prepare the support data structures for the next collection cycle, using one processor
i-cms用duty cycle控制在两个young generation之间的时间里,有百分制多少的时间能够被用来gc。
i-cms能够根据程序的行为自动计算duty cycle(推荐的方法:automatic pacing),或者也可以用参数固定这个值。
Command Line Options
Option | Description | JDK5 and earlier Default Value | JDK6 and later Default Value |
---|---|---|---|
-XX:+CMSIncrementalMode |
Enables incremental mode. Note that the concurrent collector must also be enabled (with -XX:+UseConcMarkSweepGC ) for this option to work. |
disabled | disabled |
-XX:+CMSIncrementalPacing |
Enables automatic pacing. The incremental mode duty cycle is automatically adjusted based on statistics collected while the JVM is running. | disabled | enabled |
-XX:CMSIncrementalDutyCycle=<N> |
The percentage (0-100) of time between minor collections that the concurrent collector is allowed to run. If CMSIncrementalPacing is enabled, then this is just the initial value. | 50 | 10 |
-XX:CMSIncrementalDutyCycleMin=<N> |
The percentage (0-100) which is the lower bound on the duty cycle when CMSIncrementalPacing is enabled. | 10 | 0 |
-XX:CMSIncrementalSafetyFactor=<N> |
The percentage (0-100) used to add conservatism when computing the duty cycle. | 10 | 10 |
-XX:CMSIncrementalOffset=<N> |
The percentage (0-100) by which the incremental mode duty cycle is shifted to the right within the period between minor collections. | 0 | 0 |
-XX:CMSExpAvgFactor=<N> |
The percentage (0-100) used to weight the current sample when computing exponential averages for the concurrent collection statistics. | 25 | 25 |
Recommended Options
在Java SE 6用i-cms, 用下面的参数:
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
在Java SE 5用i-cms,用下面的参数:
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps \
-XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10
Basic Troubleshooting
i-cms在程序运行的时候收集统计信息来计算duty cycle,这样concurrent collection工作才能够在heap满之前完成。
但是这种预测并不总是准确的,也会发生heap在concurrent collection之前就满了。如果有太多次这种情况。可以试试下面的步骤(一次一个):
Step | Options |
---|---|
1. increase the safety factor | -XX:CMSIncrementalSafetyFactor=<N> |
2. increase the minimum duty cycle | -XX:CMSIncrementalDutyCycleMin=<N> |
3. Disable automatic pacing and use a fixed duty cycle | -XX:-CMSIncrementalPacing -XX:CMSIncrementalDutyCycle=<N> |
Measurements
和其他collector一样,还是用-verbose:gc -XX:+PrintGCDetails
来看gc情况,只不过concurrent collector的输出和minor collection是串在一起的。
通常情况下是很多minor collection出现在concurrent collection cycle。
[GC [1 CMS-initial-mark: 13991K(20288K)] 14103K(22400K), 0.0023781 secs]
[GC [DefNew: 2112K->64K(2112K), 0.0837052 secs] 16103K->15476K(22400K), 0.0838519 secs]
...
[GC [DefNew: 2077K->63K(2112K), 0.0126205 secs] 17552K->15855K(22400K), 0.0127482 secs]
[CMS-concurrent-mark: 0.267/0.374 secs]
[GC [DefNew: 2111K->64K(2112K), 0.0190851 secs] 17903K->16154K(22400K), 0.0191903 secs]
[CMS-concurrent-preclean: 0.044/0.064 secs]
[GC [1 CMS-remark: 16090K(20288K)] 17242K(22400K), 0.0210460 secs]
[GC [DefNew: 2112K->63K(2112K), 0.0716116 secs] 18177K->17382K(22400K), 0.0718204 secs]
[GC [DefNew: 2111K->63K(2112K), 0.0830392 secs] 19363K->18757K(22400K), 0.0832943 secs]
...
[GC [DefNew: 2111K->0K(2112K), 0.0035190 secs] 17527K->15479K(22400K), 0.0036052 secs]
[CMS-concurrent-sweep: 0.291/0.662 secs]
[GC [DefNew: 2048K->0K(2112K), 0.0013347 secs] 17527K->15479K(27912K), 0.0014231 secs]
[CMS-concurrent-reset: 0.016/0.016 secs]
[GC [DefNew: 2048K->1K(2112K), 0.0013936 secs] 17527K->15479K(27912K), 0.0014814 secs]
CMS-initial-mark: indicates the start of the concurrent collection cycle.
CMS-concurrent-mark: indicates the end of the concurrent marking phase。
CMS-concurrent-sweep: marks the end of the concurrent sweeping phase.
CMS-concurrent-preclean, Not discussed before, Precleaning represents work that can be done concurrently in preparation for the remark phase CMS-remark.
The final phase is indicated by the CMS-concurrent-reset: and is in preparation for the next concurrent collection.
以上这些阶段的特点:
phase | 特点 |
---|---|
CMS-initial-mark | 通常比minor collection pause time短 |
CMS-concurrent-mark, CMS-concurrent-preclean, CMS-concurrent-sweep | 要比minor collection pause time长得多(但是程序在这个阶段是不停的) |
CMS-concurrent-remark | 和minor collection差不多. The remark pause is affected by certain application characteristics (e.g., a high rate of object modification can increase this pause) and the time since the last minor collection (i.e., more objects in the young generation may increase this pause). |
Other Considerations
Permanent Generation Size
permanent generation一般不会因为gc对程序有什么性能上的影响,但是如果是那种动态加载class的程序(比如JSP)。
这种情况就需要更大的permanent generation大小。-XX:MaxPermSize=<N>
Finalization; Weak, Soft and Phantom References
Some applications interact with garbage collection by using finalization and weak, soft, or phantom references. These features can create performance artifacts at the Java programming language level.
An example of this is relying on finalization to close file descriptors, which makes an external resource (descriptors) dependent on garbage collection promptness. Relying on garbage collection to manage resources other than memory is almost always a bad idea.
The Resources section includes an article that discusses in depth some of the pitfalls of finalization and techniques for avoiding them.
Explicit Garbage Collection
在程序里显式的调用System.gc()
会引起major collection,而实际上可能minor collection就够了,这个会对性能产生很大影响。
可以用-XX:+DisableExplicitGC
让JVM忽略这种请求。
另外一种常见的情况是RMI,使用RMI的程序会引用在其他JVM里的对象,因此本地JVM的gc是不会回收这种对象的。
所以RMI会定期的强执行major collection(DGC,分布式gc),DGC的频率是可以控制的:java -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 ...
Soft References
Soft references are kept alive longer in the server virtual machine than in the client. The rate of clearing can be controlled with the command line option -XX:SoftRefLRUPolicyMSPerMB=<N>
, which specifies the number of milliseconds a soft reference will be kept alive (once it is no longer strongly reachable) for each megabyte of free space in the heap. The default value is 1000 ms per megabyte, which means that a soft reference will survive (after the last strong reference to the object has been collected) for 1 second for each megabyte of free space in the heap. Note that this is an approximate figure since soft references are cleared only during garbage collection, which may occur sporadically.
Resources
GC output examples 讲解了如何读懂不同collector的gc output
How to Handle Java Finalization's Memory-Retention Issues 讲了一些finalization的陷阱以及避免的方法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。