【问题标题】:Java Garbage Collecting Reference ClassesJava 垃圾收集参考类
【发布时间】:2016-08-16 19:17:29
【问题描述】:

我正在测试 gc 如何与 java.lang.ref 包中的类一起工作,仅供学习:)

以下是我的代码。

public static void main(String [] args) {

    int mb = 1024*1024;

    //Getting the runtime reference from system
    Runtime runtime = Runtime.getRuntime();

    System.out.println("##### Heap utilization statistics [MB] #####");
    ArrayList<Object> sb = new ArrayList<Object>();
    for(int i =0; i < 5000000; i++){
        sb.add(new Object());
        if( i % 1000 == 0) {
            System.out.println(i);
        }
    }

    SoftReference<ArrayList> wr = new SoftReference<ArrayList>(sb);

    // System.gc()

    //Print used memory
    System.out.println("Used Memory:"
        + (runtime.totalMemory() - runtime.freeMemory()) / mb);

    //Print free memory
    System.out.println("Free Memory:"
        + runtime.freeMemory() / mb);

    //Print total available memory
    System.out.println("Total Memory:" + runtime.totalMemory() / mb);

    //Print Maximum available memory
    System.out.println("Max Memory:" + runtime.maxMemory() / mb);
}

结果:

Used Memory:95,
Free Memory:28,
Total Memory:123,
Max Memory:247

我撤消了对“System.gc()”的注释,重新运行代码,结果是

Used Memory:1,
Free Memory:122,
Total Memory:123,
Max Memory:247

是的,首先收集ArrayList的实例。据我所知,仅由 SoftReference 引用的实例是可软访问的,因此在由于缺少剩余堆空间而真正需要 GC 时进行收集。代码的第一个结果的剩余空间约为 150(free mem 28 + left max mem 124)。我不明白为什么要收集 ArrayList 的实例。

其次,我通过修改运行代码:

sb.add(new Object()); -> sb.add(new StringBuffer(i));

结果:

Used Memory:245,
Free Memory:2,
Total Memory:247,
Max Memory:247

为什么不一样??

最后,我再次运行代码并进行了修改: 来自

SoftReference<ArrayList> wr = new SoftReference<ArrayList>(sb);

WeakReference<ArrayList> wr = new WeakReference<ArrayList>(sb);

结果:

Used Memory:245,
Free Memory:2,
Total Memory:247,
Max Memory:247

我猜想 ArrayList 的实例是被收集的,因为这些实例只被 WeakReferece 引用,所以这些都是弱可达的。但他们没有被收集。

我现在假设我对 Reference 工作方式的理解是不正确的。

请任何人告诉我原因。

谢谢^^

【问题讨论】:

  • 这不可能是您实际运行的代码。由于您从不删除 sb 引用本身,因此肯定不会收集 ArrayList。您不是刚刚在第二次和第三次测试之间删除了一些 sb = null; 行吗?
  • 首先 gc 不会删除仍然有引用的对象。而且您永远无法确定 System.gc() 是否会运行,这取决于 jvm。
  • 正如 Dolda 所指出的,您强烈引用了ArrayList。如果有的话,您看到的差异可能是由于ArrayList 反复调整自身大小,这会在循环期间分配逐渐更大的数组。 (这些数组就是被垃圾收集的东西。)另外,我不确定我是否理解你关于StringBuffer 的问题。 StringBuffer 比普通的 Object 大得多,因为它里面有一个数组。因此,不应该期望它使用更多的内存吗?
  • 请不要使用StringBuffer,因为它在十多年前就被替换了。这会使用很多更多内存,因为您在其中保留了一个非常大的char[]
  • 如果您使用new StringBuffer(i),其中i 接近5000000,那么每个缓冲区 将使用大约10 MB。 new Object() 将使用大约 16 个字节,即使对于 i 的大值。

标签: java memory garbage-collection jvm heap-memory


【解决方案1】:

嵌入式问题最容易回答:如果将new StringBuffer(i) 替换为new StringBuffer(i) 并增加i,则您正在创建容量增加的StringBuffer 实例,因此应该不会感到意外这些对象比无状态的Object 实例需要更多的内存。

主要问题不是那么容易回答,因为您向我们展示了一个难以重现的结果,而中间又转向了一个更容易重现的结果,这表明您在您的代码比您说的要多,或者您的测试环境之间有细微的变化。原则上,这两种结果都是可能的,但与您所做的更改完全无关。

首先,当您调用System.gc() 时,您在局部变量中持有对ArrayList 的强引用,因此附加引用是弱引用还是软引用完全无关紧要。在大多数设置和测试运行中,您会遇到 ArrayList 和包含的对象仍然占用内存。

但这并不是故事的结局。正如“finalize() called on strongly reachable object in Java 8” 中所讨论的,即使持有强引用,对象 也可以被收集,前提是JVM 可以证明该引用不会被使用。正如进一步讨论的那样,这是否会发生仅取决于 JVM 的优化状态和执行的代码,因此您的示例程序不太可能发生这种情况,该程序由通常在解释器中运行的唯一 main 方法组成,但事实并非如此不可能。

但如果发生这种情况,此逻辑适用于您的方法中所有未使用的引用,其中包括对SoftReference 的引用。 WeakReference 实例。如果 Reference 对象本身被收集,它与所指对象的语义将再次无关紧要。然后,ArrayList、包含的对象和引用的对象被收集在一起。

如果在调用 System.gc() 之前将 sb 变量显式设置为 null 并在其后的引用对象上调用 get(),您可能会遇到不同的结果,但请记住,System.gc() 仍然是只是对 JVM 的一个提示,可能会被忽略,因此完全没有效果。

【讨论】:

    猜你喜欢
    • 2014-04-21
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-07
    • 2014-05-13
    • 2016-03-26
    相关资源
    最近更新 更多