当前位置: 首页 > 知识库问答 >
问题:

当存在静态引用时,为什么该类是GCed

岳京
2023-03-14

我试图理解静态变量中类加载器泄漏的可能性。正如我在许多地方看到的,静态变量被类对象引用,而类对象又与类加载器相关。这是否意味着如果我编写了如下代码,那么Classloader将不会被GCed<代码>专用静态最终GCTester INSTANCE=新GCTester()

public class GCTester {
 private static final GCTester INSTANCE=new GCTester();
  private GCTester() {
    System.out.println(this + " created");
  }
  @Override
  public void finalize() {
    System.out.println(this + " finalized");
  }
  }

和主类:

public class TestMe {
public static void main(String[] args) throws Exception {
System.out.println("in main");
testGetObject();
System.out.println("Second gc() call (in main)");
System.gc();
Thread.sleep(10000);
System.out.println("End of main");
 }

public static void testGetObject() throws Exception {
System.out.println("Creating ClassLoader");
ClassLoader cl = new URLClassLoader(new URL[] {new File("./x").toURI().toURL()});
System.out.println("Loading Class");
Class<?> clazz = cl.loadClass("GCTester");

System.out.println("Getting static field");
Field field = clazz.getField("INSTANCE");

System.out.println("Reading static value");
Object object = field.get(null);
System.out.println("Got value: " + object);

System.out.println("First gc() call");
System.gc();
Thread.sleep(10000);
}
}

带有类加载和卸载选项的输出启用:

   [0.108s][info][class,load] TestMe source: file:/C:/Users/someone/eclipse-workspace/Test/bin/
   [0.108s][info][class,load] java.lang.NamedPackage source: jrt:/java.base
   [0.108s][info][class,load] java.net.URLClassLoader source: jrt:/java.base
   in main
   Creating ClassLoader
  [0.109s][info][class,load] jdk.internal.access.JavaNetURLClassLoaderAccess source: jrt:/java.base
   [0.109s][info][class,load] java.net.URLClassLoader$7 source: jrt:/java.base
   [0.110s][info][class,load] java.net.URI$Parser source: jrt:/java.base
   Loading Class
   [0.111s][info][class,load] GCTester source: file:/C:/Users/someone/eclipse-workspace/Test/bin/
   Getting static field
   Reading static value
   [0.111s][info][class,load] jdk.internal.reflect.UnsafeFieldAccessorFactory source: jrt:/java.base
   GCTester@782830e created
   Got value: GCTester@782830e
   First gc() call
   Second gc() call (in main)
   End of main

在这里我们可以看到 finalize 没有被调用,同样的事情在没有静态的情况下也会发生 私有的最终 GCTester INSTANCE=new GCTester();所以我不明白为什么它说

静态变量由类对象引用,而类对象又与类加载器相关

我如何重现内存泄漏与静态验证元空间?

共有2个答案

郭远
2023-03-14

您的代码略有误导。您似乎认为您在此处初始化的URLClassLoader

ClassLoader cl = new URLClassLoader(new URL[] {new File("./x").toURI().toURL()});

将负责加载这个类

Class<?> clazz = cl.loadClass("GCTester");

实际上,<code>URLClassLoader</code>将把类加载委托给应用程序类加载器,即用于初始化<code>TestMe</code>类的类加载。在此处查看更多详细信息

  • UrlClassLoader 委派和继承层次结构

您可以通过调用和打印来验证这一点

clazz.getClassLoader()

它不会是与< code>cl相同的对象。

Javadoc中提到了这一点

在父类加载器中首次搜索后,将按照为类和资源指定的顺序搜索URL。

您可能已经使用classpath中包含的GCTester类运行了Java程序,因此应用程序类加载器可以找到并加载它。

相反,把它取出来,移到另一个jar中,或者作为一个< code >单独提供。class文件,它不包含在应用程序的类路径中。然后在调整路径后再次运行程序,以便能够找到它。例如

ClassLoader cl = new URLClassLoader(new URL[] { new File("entirely-separate-dir/").toURI().toURL() });
Class<?> clazz = cl.loadClass("com.example.GCTester");

您可以再次验证clzzClassLoader实际上是URLClassLoader,但您的程序现在应该打印fin的print语句。

com.example.GCTester@bebdb06 finalized

因为GCTester.INSTANCE字段引用的GCTester对象被垃圾收集,而"owning"ClassLoader也被垃圾收集。

你也会问

如何用静态变量在元空间中重现内存泄漏?

您必须保持一个直接或间接指向< code>ClassLoader(本例中为< code>URLClassLoader)的引用,该引用加载了带有这些静态变量的类。

赵雅懿
2023-03-14

System.gc();

这是一个提示,不是指令。不能保证这真的会运行任何终结器,所以,不幸的是,这意味着如果你没有看到终结器运行,它就没有任何意义。如果你确实看到了终结器,那就证明你的对象正在被GCed。

一个更好的方法是使用基于JVMTI的调试工具,比如visualvm。

即使它确实工作了,不,您的类加载器也不能在这里被垃圾收集。不是因为“静态变量”,而是因为您如何编写测试方法。

在我解释原因之前,需要一些信息:

Java代码(. java文件)被编译成字节码(. class文件),在字节码中,本地变量不太能实现。这些最终成为所谓的“插槽”,但javac可能会覆盖插槽。例如,这个方法:

void foo() {
   int x = 5;
   System.out.println(x);
   int y = 10;
   System.out.println(y);
}

很可能最终编译成只有1个插槽(甚至可能是0个插槽,两个数字都完全内联)。那是因为这本书本来可以写得和:

void foo() {
   int x = 5;
   System.out.println(x);
   x = 10;
   System.out.println(x);
}

< code>javac的行为得到了很好的规定(javac不允许任意应用优化,它需要遵循一个规范——有一个明确的“代码在这里使用1个槽”样式的答案,您可以使用< code>javap -c来反编译此代码,如果您对此感兴趣,请确定)。然而,没有人*用敏锐的感觉来编写java程序,他们的java代码在字节码中将会是什么样子,所以试图去考虑槽不是正确的方法。相反,要认识到局部变量,即使超出了作用域,在字节码级别仍然是“活的”。如果你再也没有用那种方法碰过它们,也要意识到它们可能不会了。

幸运的是,当方法结束时,所有局部变量和参数都完全消失了。这就是您获得确定性的方式:一旦一个方法完成,您就可以确定它引用的任何对象不再因为该方法而被阻止进入GC。

你肯定看到了堆栈跟踪,对吧?每一个没有“死”的线程(一个线程一旦启动,然后完成/停止,就死了。死线程永远不会再运行代码)当前都在运行某个方法,但它之所以会出现,是因为其他方法调用了它,其他方法调用它,一直回到run()方法-堆栈跟踪列出了它们。跟踪中的每个方法,每个非死线程的每个方法都是GC的起点:这些线程可以访问的每个局部变量和参数都是不可收集的,然后,您可以从这组初始的“仍然活动”对象中“到达”的每个对象也被认为是“仍然活动的”-继续进行,直到完成,瞧:你有你的“活动对象”和“符合收藏条件”对象。很多方法都有可访问的这个引用,这也是一个局部变量/参数,请记住这一点。

您的 Object o local var 指向您的单例 GCTester 对象,因此它是一个活动对象,无法收集。(此时,您的方法将永远不会再碰该对象。这意味着它的插槽可以被重用,因此它不会是实时的。但是尝试进行“时隙分析”不是一个好主意,所以你必须假设最坏的情况:如果你不希望它被GCed,它可能是,如果你真的想,它就不会)。当您具有类 X 的活动实例时,则无法收集类 X。当无法收集类时,无法收集其类装入器。

因此,按顺序:

  • GC检查活动线程并找到您的线程。
  • GC在每个线程堆栈中每个方法的槽中查找活动对象。它通过您的本地varo查找GCTester的实例。
  • 该实例导致GCTester.class不符合条件。
  • 这会导致加载程序不合格。

实际上,消除它(最好的方法:将与GCTester的交互放在一个单独的helper方法中,但将gc()和sleep()调用留在原处。现在你可以确定没有一个方法有一个槽指向它)。

然后,如果你的gc调用没有触发终结器(因为规范说它不必触发),投资学习visualvm或其他一些严肃的调试工具。

*)我四舍五入。

 类似资料:
  • 问题内容: 我在这里阅读Threadlocal的JavaDoc https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html 它说:“ ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。” 但是我的问题是,为什么他们选择使其静态化(通常)-使其具有“每个线程”状态

  • 问题内容: 什么是静态嵌套类?静态和非静态嵌套类有什么区别? 问题答案: 静态内部类是嵌套在具有修饰符的另一个类中的类。除了可以访问在其内部定义的类的私有成员之外,它与顶级类几乎相同。 类是静态内部类。类是一个非静态的内部类。两者之间的区别是,非静态内部类的实例被永久连接到的实例-你不能创建一个没有。不过,您可以独立创建对象。 中的代码,并且都可以访问x; 不允许使用其他代码。

  • Spring留档指出: “如果最后两段代码实际上存在于同一个应用程序中,那么可以删除两个RowMapper匿名内部类中存在的重复,并将其提取到单个类(通常是静态内部类)中,然后根据需要由DAO方法引用。” 本例中的“最后两个片段”是使用两个具有相同逻辑的行映射器映射其调用结果的数据库方法。 我的问题是为什么内部RowMapper类需要是静态的。。。还是一定要这样?我的DAO中有一个自动连接的服务方

  • 问题内容: 我开始用Java编程。 一本书说,在这种情况下,我应该使用static,但没有明确说明为什么应该使用静态方法或含义。 你能澄清一下吗? 问题答案: 的概念与某物是类的一部分还是对象(实例)有关。 对于声明为的方法,它表示该方法是一个类方法- 该方法是类的一部分,而不是对象的一部分。这意味着另一个类可以通过引用来调用另一个类的类方法。例如,调用的run方法可以通过以下方式完成: 另一方面

  • 问题内容: java为什么无法从静态环境引用非静态? 问题答案: 要了解原因,你必须了解两者之间的区别。 实例(非静态)方法适用于特定类型(类)的对象。这些是使用新的创建的,如下所示: 要调用实例方法,请在实例(myObject)上调用它: 但是,只能直接在类型上调用静态方法/字段,如下所示: 前一条语句不正确。也可以使用诸如此类的对象引用来引用静态字段, myObject.staticMetho

  • 问题内容: 在Java中,外部类可以是public,final,default或abstract。为什么不像静态 问题答案: 外部类已经是隐式静态的。 非静态嵌套类(=内部类)意味着内部类隐式对其父类具有引用。 这就是为什么对于嵌套类,您可以区分静态和非静态。对于外部类来说,这是没有意义的。 这是一个了解静态/非静态嵌套类之间的区别的示例。您应该了解为什么在外部类中它没有意义。