Java内存难题

Java内存难题,java,memory-management,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 { {

假设我有以下代码

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。您可以使用一个变量分配(重写第一个数组使用的堆栈帧并使该数组可用于垃圾收集)使此代码工作。这个谜题可以解释

问题是:为什么下面的代码仍然不起作用

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];
}

您在实例化之前依赖GC进行收集

你不能做吗

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

我打赌
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.
}

谜题很有趣,但对于不想考虑(或更重要地依赖)垃圾收集更神秘方面的实用主义程序员来说,一旦不再需要设置
data1=null
会解决问题吗?如果是这样的话,我宁愿这样做,然后使用奇怪的同步块和虚拟变量魔法

当然,令人遗憾的是,当数组超出作用域时,内存并没有立即释放,这正是人们所希望的


这应该在JVM中修复。

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

如果担心程序内存不足,可以使用System.gc()显式尝试触发垃圾收集循环。但即使这样也不能保证在分配数据2时有足够的内存可用。调用System.gc()只是向运行时发出一个提示,提示您现在需要一个垃圾收集周期

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


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

真恶心。。。这种行为不完全依赖于垃圾收集器的实现吗?这种行为似乎不是确定性的(即JLS中未指定)。我错了吗?JLS允许GC完全不做任何事情来实现(您将如何为GC指定契约?)。查看本文中链接的文章,它似乎并不依赖于GC,而是依赖于生成的字节码(这使得堆栈上的数组引用太长)。另一方面,本文还指出,它在BEA和IBM虚拟机上,或者在Sun的实验性新垃圾收集器上不会失败。令人困惑的是,GC总是在抛出OutOfMemoryError之前进行收集,如其文档中所述:因此,至少这是一种可靠的行为。您的代码不必可靠地收集数据1,因为它没有超出范围,需要进一步优化,很明显,数据1可以安全地回收。他不应该这样做,如果他这样做了,也不会对他有任何好处。JVM应该在抛出OutOfMemoryError之前尽最大努力释放内存,这样垃圾收集器将被自动调用,甚至是在完全扫描模式下。GC总是在抛出OutOfMemoryError之前进行收集,如其文档中所述:java.sun.com/javase/6/…这样至少是一种可靠的行为。有趣的是:我不知道这个。但它仍然不是确定性的,因为垃圾收集器不能保证在一个周期内收集所有未使用的对象。正如所解释的。当然,一个务实的程序员是可以的,直到他/她因为无知而在生产中造成问题。不确定拒绝卷入这样的细节是否算作无知。这似乎是一个罕见的边缘案例,除了JVM专家之外,没有人会知道。但是在抛出OutOfMemoryError之前,JVM是否会强制执行完全(非后台)垃圾收集?似乎是这样。publicstaticvoidmain(String[]args)抛出InterruptedException{synchronized(newobject()){byte[]data1=newbyte[size];}int i=0;int k=0;byte[]data2=newbyte[size];}运行良好。如果反汇编此代码,您将看到“k”变量覆盖了“data1”的stackframe。
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.
}