Java中的静态内存有什么问题?

19

这个问题针对的是Java语言。我明白有一部分内存被静态代码保留。

我的问题是这个静态内存是如何填充的?静态对象是在导入时放入静态内存还是在首次引用时放入?此外,静态对象是否适用于与所有其他对象相同的垃圾收集规则?


public class Example{
    public static SomeObject someO = new SomeObject();
}
/********************************/
// Is the static object put into static memory at this point?
import somepackage.Example;

public class MainApp{
    public static void main( Sting args[] ){
// Or is the static object put into memory at first reference?
       Example.someO.someMethod();
// Do the same garbage collection rules apply to a 
//     static object as they do all others?
       Example.someO = null;
       System.gc();
    }
}
6个回答

34

导入语句在编译后的代码中与任何指令都没有关联。它们仅在编译时建立别名。

有一些反射方法可以允许加载类但不初始化,在大多数情况下,你可以假设每当引用一个类时,它已经被初始化。

静态成员变量的初始化器和静态块按源代码顺序执行,就好像它们都是一个静态初始化器块。

通过静态成员变量引用的对象会强制保留其引用,直到该类被卸载。正常的ClassLoader永远不会卸载一个类,但那些被应用服务器使用的会在适当条件下进行卸载。然而,这是一个棘手的领域,已经成为了很多难以诊断的内存泄漏问题的来源——这是不使用全局变量的另一个原因。


作为(间接)奖励,这里有一个棘手的问题要考虑:

public class Foo {
  private static Foo instance = new Foo();
  private static final int DELTA = 6;
  private static int BASE = 7;
  private int x;
  private Foo() {
    x = BASE + DELTA;
  }
  public static void main(String... argv) {
    System.out.println(Foo.instance.x);
  }
}

这段代码会输出什么?试一下,你会发现它输出的是"6"。这里有几个因素在起作用,其中一个是静态初始化的顺序。代码的执行顺序如同它被写成这样:

public class Foo {
  private static Foo instance;
  private static final int DELTA = 6;
  private static int BASE;
  static {
    instance = null;
    BASE = 0;
    instance = new Foo(); /* BASE is 0 when instance.x is computed. */
    BASE = 7;
  }
  private int x;
  private Foo() {
    x = BASE + 6; /* "6" is inlined, because it's a constant. */
  }
}

为什么你说系统类加载器从不卸载任何类?似乎只有引导类加载器不会卸载类。https://dev59.com/L3RB5IYBdhLWcg3w9b59#453073 - Pacerier
我并没有说系统类加载器永远不会卸载任何类。我说的是普通的ClassLoader从不卸载类。系统类加载器并不是普通的,它是由运行时创建的特殊类加载器。我不知道是否可能将其卸载,但我知道我从未见过这样的情况。 - erickson
“Normal”类加载器是什么意思?似乎每个类(除了由引导加载器加载的类)都可以被卸载... - Pacerier
是的,在JLS下,JVM可以卸载系统类加载器是允许的。它有多频繁发生?您认为对类加载器(系统或其他)进行必要的操纵是否不寻常?我的回答重点是,除非采取有意的操作,否则类始终保持已加载状态。 - erickson
@Pacerier,你所提出的“透明卸载”想法,即卸载所有可以重建的内容,但透明地保留初始化状态和static字段,可以解释为“实际上不是卸载,而是丢弃可重建数据”。在这方面,今天已经发生了一些事情,例如当使用“共享类数据”存档时,它被内存映射到进程中,因此,其中的一部分可能会从物理内存中透明地删除并由操作系统重新加载。此外,很少使用的代码可能会被去优化,丢弃编译代码。足够接近了... - Holger
显示剩余9条评论

5

通常情况下,不存在所谓的“静态”内存。大多数虚拟机都有堆的永久代(用于加载类),这通常不会被垃圾回收。

静态对象的分配方式与任何其他对象相同。但是,如果它们存在时间较长,则将在垃圾回收器中移动到不同的代中。但它们不会最终进入永久代空间。

如果您的类永久持有此对象,则只有在虚拟机退出时才会释放它。


3
这个静态变量some0会在你的类在代码中被引用时立即初始化。在你的示例中,这将在main方法的第一行执行。
您可以通过创建一个静态初始化块来验证此操作。在此初始化块中放置断点,您将看到何时调用它。或者更简单的方法是...在SomeObject的构造函数中设置断点。

3

静态变量的初始化在sun JVM规范的第2.11节 静态初始化器中有所涉及。然而,该规范并未定义垃圾回收的实现方式,因此我想静态对象的垃圾回收规则将因您的虚拟机而异。


2

需要注意的是,只有指针(或任何其他原始类型)存储在PermGenSpace中(这是静态内容存储区的正确名称)。

因此,由指针引用的对象与任何其他对象一样位于普通堆中。


0

如果静态字段被更改以引用不同的对象,则由静态字段指向的原始对象与任何其他对象一样符合GC的条件。

如果类本身被卸载并且整个对象图从堆中删除,则它也可以被释放(即使没有置空)。当然,类何时可以被卸载是一个好话题,可以引发许多其他问题... :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接