Java记忆难题

时间:2009-07-08 09:12:03

标签: java memory-management

假设我有以下代码

package memoryleak;

public class MemoryLeak {

    public static int size;

    static {
        size = (int) (Runtime.getRuntime().maxMemory()*0.6);
    }

    public static void main(String[] args) throws InterruptedException {
        {
            byte[] data1 = new byte[size];
        }

        byte[] data2 = new byte[size];
    }
}

此代码生成OutOfMemoryError。您可以使用一个变量分配使这个代码工作(它重写第一个数组使用的堆栈帧,并使make数组可用于垃圾收集)。这个难题解释了here

{
    byte[] data1 = new byte[size];
}
int i = 0;
byte[] data2 = new byte[size];

问题是:为什么以下代码仍无效?

Object o = new Object();
synchronized (o) {
    byte[] data1 = new byte[size];
}
int i = 0;
byte[] data2 = new byte[size];

以下作品:

Object o = new Object();
synchronized (o) {
    byte[] data1 = new byte[size];
}
int i = 0;
synchronized (o) {
    byte[] data2 = new byte[size];
}

4 个答案:

答案 0 :(得分:6)

我敢打赌,synchronized会向框架添加一个元素,导致data1向上移动一个广告位而不会被i破坏。 synchronized需要解锁锁定的同一对象,即使本地/字段发生更改。

synchronized代码看起来像这样:

Object $sync = o;
$sync.lock();
try {
    byte[] data1 = new byte[size];
} finally {
    $sync.unlock();
}

所以拿最后一段代码:

Object o = new Object();            // Slot 0.
synchronized (o) {                  // Slot 1.
    byte[] data1 = new byte[size];  // Slot 2.
}                                 
int i = 0;                          // Slot 1.
synchronized (o) {                  // Slot 2. (clobbers data1, was slot 1)
    byte[] data2 = new byte[size];  // Slot 3.
}

答案 1 :(得分:0)

谜题很有意思,但对于那些不想考虑(或者更重要的是依赖于)垃圾收集更加神秘的方面的实用程序员来说,只要不再需要就设置data1 = null问题?如果是这样,我宁愿这样做,然后奇怪的同步块和虚拟变量魔术。

当然,令人遗憾的是,一旦数组超出范围,内存就不会被释放,这是人们在this thread中所希望的。

这应该在JVM中修复。

答案 2 :(得分:0)

所有这些行为都取决于实现。垃圾收集器在其自己的异步线程中运行,该线程与程序的同步行为无关。你根本不知道data1引用的数组何时会被垃圾收集 - 你只能希望它在超出范围之后的“合理”时间内发生/所有对它的引用都消失了。

如果您担心程序中的内存不足,可以使用System.gc()显式尝试触发垃圾回收循环。但即使这样也无法保证在分配data2时有足够的内存可用。调用System.gc()只是对运行时的一个提示,你现在想要一个垃圾回收周期。

在Java中,内存分配和释放是不确定的。垃圾收集器将在运行时运行,您无法在程序级别运行它。您发布的代码片段之间没有相关差异,因为gc行为是非确定性的,并且触发它的精确时刻是实现和系统相关的。有时这对您的应用程序来说是个问题 - 例如,如果它是操作系统或在内存受限的嵌入式设备中运行 - 并且您需要使用C ++或其他语言进行编码,其中内存管理是确定性的。但是,对于我们大多数人来说,我们只相信垃圾收集器会以合理合理的方式运行,并且对于大多数用途来说已经足够好了 - 尽管如您所见,您可以创建导致问题的人为代码。

更新:令人尴尬。正如其他一些评论者提醒我的那样,在jvm抛出OutOfMemory错误之前,会显式触发垃圾收集周期。但是,行为仍然是不确定的:正如this link所解释的那样,jvm并不能保证在一个垃圾收集周期中检测到所有死对象。

答案 3 :(得分:-1)

您在实例化之前依靠GC来收集?

你能做不到吗

Object o = new Object();
byte[] data1 = new byte[size];
GC.Collect()
byte[] data2 = new byte[size];