如何防止对象被垃圾收集?

时间:2009-08-25 18:00:55

标签: java garbage-collection

如何防止对象被垃圾收集?

是否有通过最终确定或幻像参考或任何其他方法的方法?

我在接受采访时被问到这个问题。采访者建议可以使用finalize()

11 个答案:

答案 0 :(得分:32)

持有参考。如果您的对象过早收集,则表明您的应用程序设计存在错误。

垃圾收集器仅收集应用程序中没有引用的对象。如果没有自然引用收集对象的对象,请问自己为什么要保持活着。

您通常没有引用但想要保留对象的一个​​用例是单例。在这种情况下,您可以使用静态变量。单例的一种可能实现方式如下:

public class Singleton {
  private static Singleton uniqueInstance;

  private Singleton() {
    }

  public static synchronized Singleton getInstance() {
    if (uniqueInstance == null) {
      uniqueInstance = new Singleton();
    }
    return uniqInstance;
  }
}

编辑:从技术上讲,您可以在终结器中的某处存储引用。这将阻止收集对象,直到收集器再次确定没有更多引用。但是,终结器最多只会调用一次,因此必须确保在第一次收集后不需要完成对象(包括其超类)。但是,我建议你不要在实际程序中使用这种技术。 (这会让像我这样的同事大喊大叫WTF!?;)

  protected void finalize() throws Throwable {
    MyObjectStore.getInstance().store(this);
    super.finalize(); // questionable, but you should ensure calling it somewhere.
  }

答案 1 :(得分:9)

面试官正在寻找的特技答案可能是他希望您知道可以通过强制内存泄漏来防止垃圾收集移除对象。

显然,如果你在一些长期存在的环境中保留对象的引用,它将不会被收集,但这不是OP的招聘人员所询问的。这不是在finalize方法中发生的事情。

你可以采取哪些措施来防止在finalize方法中进行垃圾收集,你可以编写一个无限循环,在其中调用Thread.yield();(可能是为了防止空循环被优化掉):

@Override
protected void finalize() throws Throwable { 
    while (true) { 
        Thread.yield(); 
    } 
} 

我在这里的引用是Elliot Back的一篇文章,其中描述了通过此方法强制内存泄漏。

敲定方法是邪恶的另一种方式

答案 2 :(得分:4)

最好的方法是使用Unsafe,但在某些情况下,ByteBuffer可能是一种可能的解决方法。

还要搜索关键字“堆外”内存。

<强>不安全

优于ByteBuffer的优势:

  • 允许直接表示对象,而无需进行序列化,因此更快
  • 没有边界检查,所以更快
  • 显式释放控制
  • 可以分配超过JVM限制

工作并不容易。该方法在以下文章中描述:

它们都包含以下步骤:

  • 我们需要一个sizeof运算符,Unsafe没有。在In Java, what is the best way to determine the size of an object?询问如何制作一个。最好的选项可能是instrument API,但这需要您创建一个Jar并使用特殊的命令行选项......

  • 获得sizeof后,使用Unsafe#allocateMemory分配足够的内存,基本上是malloc并返回地址

  • 在堆对象上创建一个常规对象,使用Unsafe#copyMemory将其复制到已分配的内存中。为此,您需要堆上对象的地址和对象的大小

  • Object设置为指向已分配的内存,然后将Object投射到您的班级。

    似乎无法直接使用Unsafe设置变量的地址,因此我们需要将对象包装到数组或包装器对象中,并使用Unsafe#arrayBaseOffsetUnsafe#objectFieldOffset

  • 完成后,使用freeMemory

  • 释放已分配的内存

如果我认为这不是段错误,我会发一个例子: - )

<强>字节缓冲区

优于不安全的优势:

  • 在Java版本中稳定,而Unsafe可能会中断
  • 绑定检查,比...更安全,不安全,允许内存泄漏和SIGSEGV

JLS says

  

直接缓冲区的内容可能位于正常的垃圾收集堆之外。

使用基元的示例:

ByteBuffer bb = ByteBuffer.allocateDirect(8);

bb.putInt(0, 1);
bb.putInt(4, 2);
assert bb.getInt(0) == 1;
assert bb.getInt(4) == 2;

// Bound chekcs are done.
boolean fail = false;
try {
    bb.getInt(8);
} catch(IndexOutOfBoundsException e) {
    fail = true;
}
assert fail;

相关主题:

答案 3 :(得分:3)

如果仍然存在对该对象的引用,则不会收集垃圾。如果没有任何参考,你不应该在乎。

换句话说 - 垃圾收集器只收集垃圾。让它发挥作用。

答案 4 :(得分:2)

我怀疑你可能指的是你的finalize方法是否存放了对正在最终确定的对象的引用。在这种情况下(如果我对Java Language Spec的读取是正确的),finalize方法将永远不会重新运行,但该对象将不会被垃圾回收。

这不是人们在现实生活中所做的事情,除非可能是偶然的!

答案 5 :(得分:2)

这听起来像是一个只会面试的时间 - 你会看到它的问题。当你的对象被垃圾收集时运行finalize(),因此在那里放置一些东西以阻止收集是非常不正常的。通常你只需要一个参考,这就是你所需要的。

我甚至不确定如果你在终结器中为某些东西创建一个新的引用会发生什么 - 因为垃圾收集器已经决定收集它然后你最终得到一个空引用?在任何情况下,似乎都是一个糟糕的主意。例如

public class Foo {
   static Foo reference;
  ...
  finalize (){ 
     reference = this; 
  }
}

我怀疑这会起作用,或者它可能有效但依赖于GC实施,或者是“未指明的行为”。但看起来很邪恶。

答案 6 :(得分:1)

关键是如果我们设置指向对象null的实际引用变量,尽管我们有指向该对象的实例变量未设置为null。 该对象自动符合垃圾回收的条件。如果将对象保存到GC,请使用此代码...

public class GcTest {

    public int id;
    public String name;
    private static GcTest gcTest=null;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();

        System.out.println("In finalize method.");
        System.out.println("In finalize :ID :"+this.id);
        System.out.println("In finalize :ID :"+this.name);

        gcTest=this;

    }

    public static void main(String[] args) {

        GcTest myGcTest=new GcTest();
        myGcTest.id=1001;
        myGcTest.name="Praveen";
        myGcTest=null;

        // requesting Garbage Collector to execute.
        // internally GC uses Mark and Sweep algorithm to clear heap memory.
        // gc() is a native method in RunTime class.

        System.gc();   // or Runtime.getRuntime().gc();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("\n------- After called GC () ---------\n");
        System.out.println("Id :"+gcTest.id);
        System.out.println("Name :"+gcTest.name);


    }

}
  

输出:

最终确定方法。
最终确定:ID:1001
最终确定:ID:Praveen

-------调用GC()后--------

Id:1001
姓名:Praveen

答案 7 :(得分:1)

我想知道他们想要的是资源池的模式(例如,对于网络/数据库连接或线程),您使用finalize将资源返回到池中,以便保存资源的实际对象不是GC的。

愚蠢的例子,在类似Java的伪代码中并且缺少任何类型的同步:

class SlowResourceInternal {
   private final SlowResourcePool parent;
   <some instance data>

   returnToPool() {
       parent.add(this);
   }
}

class SlowResourceHolder {
    private final SlowResourceInternal impl;

    <delegate actual stuff to the internal object>

    finalize() {
        if (impl != null) impl.returnToPool();
    }
}

答案 8 :(得分:0)

我相信这有一种模式。不确定它是否是工厂模式。但是你有一个对象可以创建所有对象并保存对它们的引用。完成它们后,在工厂中取消它们的引用,使调用显式。

答案 9 :(得分:0)

我们可以通过三种方式实现相同目标- 1)增加Heap-Eden空间的大小。 2)使用静态引用创建Singleton类。 3)覆盖finalize()方法,并且永远不要让该对象取消引用。

答案 10 :(得分:0)

有3种防止对象垃圾收集的方法,如下所示:-

  1. 增加JVM的堆大小

    // Xms specifies initial memory to be allocated
    // and Xmx specifies maximum memory can be allocated
    java -Xms1024m -Xmx4096m ClassFile
    
  2. 将SingleTon类对象用作@Tobias

    public class MySingletonClass {
         private static MySingletonClass uniqueInstance;
    
         // marking constructor as private
         private MySingletonClass() {
         }
    
         public static synchronized MySingletonClass getInstance() {
            if (uniqueInstance == null) {
                uniqueInstance = new Singleton();
            }
            return uniqInstance;
        }
    }
    
  3. 我们可以重写finalize方法。那是在对象上执行的最后一个方法。因此,它将保留在内存中。

    // using finalize method 
    class MyClassNotGc{ 
    
        static MyClassNotGc staticSelfObj; 
    
        pubic void finalize() { 
            // Putting the reference id 
            //Object reference saved. 
            //The object won't be collected by the garbage collector
            staticSelfObj = this; 
        } 
    
    }