G1中的引用处理
JDK中的引用主要有以下几种:
- 强引用
- 软引用
- 弱引用
- 虚引用
- FinalReference
在Reference.java这个类中描述了Reference的4个可能的状态:
- Active:对象是活跃的,这个活跃的意思是指GC可以通过可达性分析找到对象或者对象是软引用对象,且符合软引用活跃的规则。从活跃状态可以到Pending状态或者Inactive状态。新创建的对象总是活跃的。
- Pending:指对象进入上面的pengding_list,即将被送入引用队列。
- Enqueued:指引用线程ReferenceHandler把pending_list的对象加入引用队列。
- Inactive:对象不活跃,可以将对象回收了。
其中除了Pending到Enqueued状态是有引用线程ReferenceHandler参与的,其他的变化都是GC线程完成的。
如何判断是否还有强引用:
- 对象所在分区的不在CSet中,或者对象在CSet中,但是没有被复制到新的分区。
软引用可回收的条件:
- 对象存活时间是否超过了阈值_max_interval
关于finalize:
- 当我们定义的类覆写了finalize方法后,在该类的初始化过程中,这个类的对象会被包装成一个java.lang.ref.Finalizer并添加到Finalizer类的静态链表unfinalized中。当执行第一次垃圾回收时,发现该对象具有finalize方法且没被执行过,因而这个对象不会被回收,而是从unfinalized链表中移除,然后添加到Finalizer类的静态引用队列queue中。查看Finalizer源码可以看到一个内部类:java.lang.ref.Finalizer.FinalizerThread,该类就是用于监视Finalizer的引用队列queue的。当它发现queue队列的变化,就依次将队列中的对象移除,并调用该对象的finalize()函数。当执行第二次垃圾回收时,发现该类虽然覆写了finalize方法,但已经执行过了,就可以直接将该类回收。以上是覆写了finalize函数的类的回收过程。对于没有覆写finalize函数的类或者已经执行过一次finalize函数的类,在垃圾回收时更简单,直接被回收即可。这里finalize函数只会被执行一次的原因是防止类在执行finalize函数时将该类复活,从而导致该类永远无法被回收。
引用处理的参数优化
- 参数PrintReferenceGC,默认值为false,可以打开该参数以输出更多信息。如果是调试版本还可以打开TraceReferenceGC获得更多的引用信息。
- 参数ParallelRefProcBalancingEnabled,默认值为true,在处理引用的时候,引用(软/弱/虚/final/cleaner)对象在同一类型的队列中可能是不均衡的,如果打开该参数则表示可以把链表均衡一下。注意这里的均衡不是指不同引用类型之间的均衡,而是同一引用类型里面有多个队列,同一引用类型多个队列之间的均衡。
- 参数ParallelRefProcEnabled,默认值为false,打开之后表示在处理一个引用的时候可以使用多线程的处理方式。这个参数主要是控制引用列表的并发处理(8.3节和8.4节介绍的内容)。另外引用的处理在GC回收和并发标记中都会执行,在GC中执行的引用处理使用的线程数目和GC线程数目一致,在并发标记中处理引用使用的线程数目和并发标记线程数一致。实际中通常打开该值,减少引用处理的时间。
- 参数RegisterReferences,默认值true,表示可以在遍历对象的时候发现引用对象类型中的对象是否可以回收,false表示在遍历对象的时候不处理引用对象。目前的设计中在GC发生时不会去遍历引用对象是否可以回收。需要注意的是该参数如果设置为false,则在GC时会执行软引用对象是否可以回收,这将会增加GC的时间,所以通常不要修改这个值。
- 参数G1UseConcMarkReferenceProcessing,默认值true,表示在并发标记的时候发现对象。该值为实验选项,需要使用XX:+UnlockExperimentalVMOptions才能改变选项。
- 参数RefDiscoveryPolicy,默认值为0,0表示ReferenceBasedDiscovery,指如果引用对象在我们的处理范围内,则对这个引用对象进行处理。1表示ReferentBasedDiscovery,指如果引用对象在我们的处理范围内或者引用对象里面的对象在处理范围内,则对引用对象处理。1会导致处理的对象更多。
- 参数SoftRefLRUPolicyMSPerMB,默认值为1000,即对软引用的清除参数为每MB的内存将会存活1s,如最大内存为1GB,则软引用的存活时间为1024s,大约为17分钟,但是如果内存为100GB,这个参数不调整,软引用对象将存活102400s,大约为28.5小时。所以需要根据总内存大小以及预期软引用的存活时间来调整这个参数。
字符串去重
目的:优化重复字符串对象的内存使用
和String.intern()方法的区别
- String.intern()必须去找出哪些字符串需要进行驻留(intern)
- intern方法
- G1的字符串去重发生在YGC阶段或FullGC标记阶段。
- 每个String对象都有个专属的char[]数组,在JVM内部使用typeArrayOOp表示,这样JVM可以进行自动优化,并且判断两个字符串内容是否一致。
- G1的字符串去重会增加GC时间
- intern必须显式调用,才能达到去重的目的;字符串去重是JVM自动进行的。
字符串可以去重的条件:
-
对象是字符串,且位于新生代
-
GC的对象复制阶段
- 复制到S区,并且对象的年纪是StringDeduplicationAgeThreshold。这是为了让那些小对象能够经过几次GC后才处理,这样大多数生命周期短的对象不会被处理。
- 如果不是复制到S区,即晋升到Old,并且对象的年纪小于StringDeduplicationAgeThreshold。对于小对象,如果已经处理过,不用再处理了。如果是大对象,则不会发生去重。
-
FullGC的标记阶段
- FullGC之后,所有分区都会标记为old,所以只需要考虑第二点:对于小对象,如果已经处理过,不用再处理了。如果是大对象,则不会发生去重。
-
字符串去重的过程
- 找到需要去重的对象
- 去重:
- 回收:当发生GC的时候,会尝试对去重后的字符串对象进行回收。发生的时机主要有:YGC发生中会回收,在并发标记的过程中处理引用的时候也会进行字符串去重的回收,在FGC中标记活跃对象时也会发生回收。
字符串去重的参数优化
- 参数UseStringDeduplication,默认值为false,打开参数表示允许字符串去重。
- 参数StringDeduplicationAgeThreshold,默认值为3,控制字符串是否参与去重的阈值。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。