Java 为什么添加到构造函数中的静态ArrayList会导致泄漏?

Java 为什么添加到构造函数中的静态ArrayList会导致泄漏?,java,constructor,arraylist,Java,Constructor,Arraylist,我正在使用NetBeans IDE,它给了我一个毫无意义的警告。警告状态为“在构造函数中泄漏”。以下代码是基本设置(我刚刚删除了与问题无关的代码)。基本上,我只想保留一个所有Square对象的列表。这是我需要担心的警告吗?或者这仅仅是内存泄漏的可能原因,取决于具体情况 不管怎样,有人能解释为什么这会被视为泄漏吗 public class Square { private static ArrayList<Square> squares; public Square(

我正在使用NetBeans IDE,它给了我一个毫无意义的警告。警告状态为“在构造函数中泄漏”。以下代码是基本设置(我刚刚删除了与问题无关的代码)。基本上,我只想保留一个所有
Square
对象的列表。这是我需要担心的警告吗?或者这仅仅是内存泄漏的可能原因,取决于具体情况

不管怎样,有人能解释为什么这会被视为泄漏吗

public class Square {
    private static ArrayList<Square> squares;

    public Square() {
        if(squares == null) {
            squares = new ArrayList<>();
        }

        squares.add(this); // I get a warning on this line
    }
}
公共类广场{
私有静态阵列列表方块;
公众广场(){
如果(平方==null){
squares=新的ArrayList();
}
squares.add(this);//我在这行得到一个警告
}
}
我知道这只是一个警告,但我不喜欢忽视警告,除非我完全理解正在发生的事情,并且能够根据具体情况做出明智的选择

谢谢

免责声明:这只是猜测

如果这是唯一修改
squares
的地方,那么这意味着
squares
对象永远不能被垃圾收集,因为每个对象总是至少有一个引用。如果是这样的话,也许您的IDE足够聪明,可以发现这一点

有人能解释为什么这会被视为泄漏吗

public class Square {
    private static ArrayList<Square> squares;

    public Square() {
        if(squares == null) {
            squares = new ArrayList<>();
        }

        squares.add(this); // I get a warning on this line
    }
}
这是一个(潜在的)泄漏,因为
列表和列表中的任何对象都不会被垃圾收集。如果没有其他代码从列表中删除对象,或者清除或为空列表,那么对象将通过列表泄漏


也许您需要理解在垃圾收集语言的上下文中“内存泄漏”是什么意思。在C语言或C++语言中,当对象丢失时,会发生存储泄漏;i、 e.应释放/处置对象的代码未能释放/处置对象。在垃圾收集语言中,当GC无法释放/处置对象时会发生泄漏,因为该对象似乎仍在使用中;i、 e.GC仍然可以通过跟踪找到对象



然而,重读这个问题,我同意肖恩·欧文的观点。消息“在构造函数中泄漏此信息”很可能是指构造函数在构造函数完成之前使对象的引用可见。这也称为“不安全出版物”。它可能是潜在并发错误的来源。(这甚至可能是单线程应用程序中的一个问题;例如,如果您创建Square的子类…

我认为警告与垃圾收集无关,尽管这确实是一个问题。这些实例永远不会被GC'ed(除非收集了整个
ClassLoader


警告是说,
正在从构造函数中传递给另一个方法。在构造函数完成之前,
根据构造函数中包含的逻辑,此
不一定是一个完全格式化和初始化的对象。构造器中的任何内容都是在其他任何内容到达对象之前发生的。但是在构造器完成之前,其他的东西正在使用
this
。这可能会导致令人惊讶的错误。

我假设您理解静态变量由类装入器引用的类对象引用。因此,除非类加载器本身符合垃圾收集的条件,或者类加载器通过某种方式删除引用类,否则它将永远不会被垃圾收集。因此,每次调用构造函数时,您都会将数据添加到一个几乎永远不会被垃圾收集的列表中。

(不是真正的答案,但…)

String ins = new String();
while(c != null && ins != null) {
  ins = new String();
  readInputInto(ins);
}
如果您的目标是在列表中保留您创建的所有正方形列表,那么有一种更好的方法来实现这一点:

public class Square
{
    private static final List<Square> allSquares = new ArrayList<Square>();

    // Constructor: private!
    private Square() {}

    // Create a square
    public static Square newSquare()
    {
        Square ret = new Square();
        allSquares.add(ret);
        return ret;
    }
}
为什么添加到构造函数中的静态ArrayList会导致内存泄漏

没有。这不是你收到警告的根本原因。然而,还有许多其他问题

  • 不应传递到构造函数外部。(如果您不遵循这一点,可能会出现零星问题)

  • Square
    类保存其创建的所有对象的列表。这意味着对于创建的每个对象,至少存在一个引用

    Square aSq = new Square(); // two references, aSq and reference in ArrayList
    new Square(); // one reference in ArrayList
    
    因此,在类出现在内存中之前,创建的所有对象都不会被垃圾收集。因此内存泄漏


  • 不管出于什么原因,我认为这是一种糟糕的做法

    OOP的理由是封装一组契约。这些合同必须对该类的订户清晰可见

    当有人实例化
    new Square()
    时,程序员不太清楚实例化是否也在将此实例添加到非垃圾收集列表中

    所以,我程序员很高兴地频繁地实例化Square,并相信只要我有意识地废除对这些简单实例化的Square的所有引用,它就会被垃圾收集。但我错了——因为尽管我有意识地努力,但没有一个实例会被垃圾收集

    String ins = new String();
    while(c != null && ins != null) {
      ins = new String();
      readInputInto(ins);
    }
    
    你知道,垃圾收集为我们程序员提供了一些习惯性的捷径做法。例如,下面的while结构举例说明了简单实例化对象的简化。程序员会毫不犹豫地反复重新实例化字符串,因为他/她知道前一个实例将被垃圾收集

    String ins = new String();
    while(c != null && ins != null) {
      ins = new String();
      readInputInto(ins);
    }
    
    但是,将字符串替换为正方形,并将其交给普通程序员。程序员将假定与字符串具有相同的垃圾回收能力

    因此,尝试在构造函数中执行太多操作是不好的做法,尤其是在构造函数中隐藏具有重大影响的操作


    您只需将实例添加到构造函数外部的列表中即可。

    您不能只执行
    private static ArrayList squares=new ArrayList()