代码之家  ›  专栏  ›  技术社区  ›  Denis Bazhenov

Java内存难题

  •  3
  • Denis Bazhenov  · 技术社区  · 16 年前

    假设我有以下代码

    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。您可以使用一个变量分配(重写第一个数组使用的堆栈帧并使该数组可用于垃圾收集)使此代码工作。这个谜题解释了 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 回复  |  直到 16 年前
        1
  •  6
  •   Tom Hawtin - tackline    16 年前

    我敢打赌 synchronized 将元素添加到框架中,导致 data1 向上移动一个槽而不被撞倒 i . 需要解锁锁定的同一对象,即使本地/字段发生更改。

    这个 同步的 代码如下所示:

    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.
    }
    
        2
  •  0
  •   Community Mohan Dere    9 年前

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

    当然,令人遗憾的是,当数组超出范围时,内存并没有立即释放,这正是人们希望的 this thread

    这应该在JVM中修复。

        3
  •  0
  •   cygil    16 年前

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

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

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

        4
  •  -1
  •   Spence    16 年前

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

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