可以看到, Full GC 的次数很少。但如果使用弱引用来指向创建的对象, 使用JVM参数 -Dweak.refs=true, 则情况会发生明显变化. 使用弱引用的原因很多, 比如在 weak hash map 中将对象作为Key的情况。在任何情况下, 使用弱引用都可能会导致以下情形:

    1. 2.059: [Full GC (Ergonomics) 20365K->19611K(22528K), 0.0654090 secs]
    2. 2.125: [Full GC (Ergonomics) 20365K->19711K(22528K), 0.0707499 secs]
    3. 2.196: [Full GC (Ergonomics) 20365K->19798K(22528K), 0.0717052 secs]
    4. 2.268: [Full GC (Ergonomics) 20365K->19873K(22528K), 0.0686290 secs]
    5. 2.337: [Full GC (Ergonomics) 20365K->19939K(22528K), 0.0702009 secs]
    6. 2.407: [Full GC (Ergonomics) 20365K->19995K(22528K), 0.0694095 secs]

    可以看到, 发生了多次 full GC, 比起前一节的示例, GC时间增加了一个数量级! 这是过早提升的另一个例子, 但这次情况更加棘手. 当然,问题的根源在于弱引用。这些临死的对象, 在添加弱引用之后, 被提升到了老年代。 但是, 他们现在陷入另一次GC循环之中, 所以需要对其做一些适当的清理。像之前一样, 最简单的办法是增加年轻代的大小, 例如指定JVM参数: -Xmx64m -XX:NewSize=32m:

    这时候, 对象在 minor GC 中就被回收了。

    1. 2.162: [Full GC (Ergonomics) 31561K->12865K(61440K), 0.0181392 secs]
    2. 2.184: [GC (Allocation Failure) 37441K->17585K(61440K), 0.0024479 secs]
    3. 2.189: [GC (Allocation Failure) 42161K->27033K(61440K), 0.0061485 secs]
    4. 2.195: [Full GC (Ergonomics) 27033K->14385K(61440K), 0.0228773 secs]
    5. 2.221: [GC (Allocation Failure) 38961K->20633K(61440K), 0.0030729 secs]
    6. 2.227: [GC (Allocation Failure) 45209K->31609K(61440K), 0.0069772 secs]
    7. 2.234: [Full GC (Ergonomics) 31609K->15905K(61440K), 0.0257689 secs]

    最有趣的是中的虚引用, 使用同样的JVM参数启动, 其结果和弱引用示例非常相似。实际上, full GC 暂停的次数会小得多, 原因前面说过, 他们有不同的终结方式。

    如果禁用虚引用清理, 增加JVM启动参数 (-Dno.ref.clearing=true), 则可以看到:

    main 线程中抛出异常 java.lang.OutOfMemoryError: Java heap space.

    建议使用JVM参数 -XX:+PrintReferenceGC 来看看各种引用对GC的影响. 如果将此参数用于启动 弱引用示例 , 将会看到:

    1. 2.173: [Full GC (Ergonomics)
    2. 2.234: [FinalReference, 1 refs, 0.0000037 secs]
    3. 2.234: [PhantomReference, 0 refs, 0 refs, 0.0000039 secs]
    4. 2.234: [JNI Weak Reference, 0.0000027 secs]
    5. [PSYoungGen: 9216K->8676K(10752K)]
    6. [ParOldGen: 12115K->12115K(12288K)]
    7. 21331K->20792K(23040K),
    8. [Metaspace: 3725K->3725K(1056768K)],
    9. 0.0766685 secs]
    10. [Times: user=0.49 sys=0.01, real=0.08 secs]
    11. 2.250: [Full GC (Ergonomics)
    12. 2.307: [SoftReference, 0 refs, 0.0000173 secs]
    13. 2.307: [WeakReference, 2298 refs, 0.0001535 secs]
    14. 2.307: [FinalReference, 3 refs, 0.0000043 secs]
    15. 2.307: [PhantomReference, 0 refs, 0 refs, 0.0000042 secs]
    16. 2.307: [JNI Weak Reference, 0.0000029 secs]
    17. [PSYoungGen: 9215K->8747K(10752K)]
    18. [ParOldGen: 12115K->12115K(12288K)]
    19. [Metaspace: 3725K->3725K(1056768K)],
    20. 0.0734832 secs]
    21. 2.323: [Full GC (Ergonomics)
    22. 2.383: [SoftReference, 0 refs, 0.0000161 secs]
    23. 2.383: [WeakReference, 1981 refs, 0.0001292 secs]
    24. 2.383: [FinalReference, 16 refs, 0.0000049 secs]
    25. 2.383: [PhantomReference, 0 refs, 0 refs, 0.0000040 secs]
    26. 2.383: [JNI Weak Reference, 0.0000027 secs]
    27. [PSYoungGen: 9216K->8809K(10752K)]
    28. [ParOldGen: 12115K->12115K(12288K)]
    29. 21331K->20925K(23040K),
    30. [Metaspace: 3725K->3725K(1056768K)],
    31. 0.0738414 secs]
    32. [Times: user=0.52 sys=0.01, real=0.08 secs]

    只有确定 GC 对应用的吞吐量和延迟造成影响之后, 才应该花心思来分析这些信息, 审查这部分日志。通常情况下, 每次GC清理的引用数量都是很少的, 大部分情况下为 0。如果GC 花了较多时间来清理这类引用, 或者清除了很多的此类引用, 就需要进一步观察和分析了。

    解决方案

    如果程序确实碰到了 mis-, ab- 问题或者滥用 weak, soft, phantom 引用, 一般都要修改程序的实现逻辑。每个系统不一样, 因此很难提供通用的指导建议, 但有一些常用的办法:

    • 弱引用(Weak references) —— 如果某个内存池的使用量增大, 造成了性能问题, 那么增加这个内存池的大小(可能也要增加堆内存的最大容量)。如同示例中所看到的, 增加堆内存的大小, 以及年轻代的大小, 可以减轻症状。
    • 虚引用(Phantom references) —— 请确保在程序中调用了虚引用的 clear 方法。编程中很容易忽略某些虚引用, 或者清理的速度跟不上生产的速度, 又或者清除引用队列的线程挂了, 就会对GC 造成很大压力, 最终可能引起 OutOfMemoryError