我们通常知道来自ClassLoader的类cannot be unloaded,但似乎lambda的合成类可以。
证明:
public class Play {
static String test = new String("test");
public static void main(String[] args) throws Exception {
WeakReference<String> wr = new WeakReference<>(test);
Runnable run = test::toString;
System.out.println(Play.class.getClassLoader() == run.getClass().getClassLoader());
System.out.printf("sys=%s, lambda=%s", Runnable.class, run.getClass());
run.run();
test = null;
run = null;
while (wr.get() != null) {
System.gc();
}
}
}
输出:
true
sys=interface java.lang.Runnable, lambda=class Play$$Lambda$1/918221580
<then terminates>
这表明lambda的clousure引用的String(将被合成类引用)被解除引用,这意味着合成的类也必须已经GC。这是有道理的,否则会出现各种基于类加载器的内存泄漏。
我的问题是他们打破了班级无法卸载设计吗?如果是这样,我们可以用自己的课程来做这件事吗?
答案 0 :(得分:2)
有趣的是,尽管你的测试不足以确定,但实际上,为lambda表达式生成的类可以获取垃圾,如下面的代码所示:
import java.lang.invoke.*;
import java.lang.ref.WeakReference;
public class LambdaClassUnloading {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup l = MethodHandles.lookup();
MethodType t=MethodType.methodType(void.class);
Runnable r=(Runnable)LambdaMetafactory.metafactory(l, "run",
MethodType.methodType(Runnable.class), t,
l.findStatic(LambdaClassUnloading.class, "testMethod", t), t)
.getTarget().invokeExact();
System.out.println("generated "+r);
r.run();
WeakReference<Class<?>> ref=new WeakReference<>(r.getClass());
r=null;
while(ref.get()!=null) System.gc();
System.out.println("class collected");
}
private static void testMethod() {
System.out.println("testMethod() called");
}
}
这打印出类似的东西:
generated java8test.LambdaClassUnloading$$Lambda$1/723074861@77459877
testMethod() called
class collected
虽然数字可能会有所不同。
但是出于实际目的,重要的是要理解上面的代码反射调用的生成器工具通常通过invokedynamic
字节代码指令触发,该指令将永久链接到第一次完成的引导的结果过程
换句话说,考虑到当前JRE如何处理它,只要包含invokedynamic
指令的代码可以访问,永久链接的lambda类也将保持不变。这符合Andremoniy’s answer中演示的行为;行Runnable run = test::toString;
的代码可以再次执行,并将生成与第一次执行相同的类的实例,因为它的构造函数永久链接到它。
但理论上,替代JRE实现可以让引导方法返回MutableCallSite
并在稍后重新定位,例如生成更优化的类的实例或强制在语义上等效的lambda表达式之间共享类。在这种情况下,当没有更多的lambda实例时,现有的lambda类可能比其定义类更早地收集垃圾。
所以答案是,确实有一个“神奇的类卸载”,但是目前,它与大多数实际目的无关,当然,如果它发生在未来的JVM中,它仍然会保留语义指定的语义。 Java语言规范。无论方法引用test::toString
捕获的实例如何存储,如果功能接口的实例无法访问,它将不会阻碍其收集。
如果您对更多详细信息感兴趣,请参阅文章“anonymous classes in the VM” by John Rose。您也可以搜索sun.misc.Unsafe.defineAnonymousClass
...
答案 1 :(得分:1)
我认为你对lambda类卸载感到困惑,这就是原因。您必须不测试wr.get()
,因为取消引用此String
实例不会基于它来讨论有关lambda的任何内容。您必须为课程WeakReference
:
...
Runnable run = test::toString;
WeakReference<Class> wcr = new WeakReference<Class>(run.getClass());
...
并尝试“等待”,同时将其取消引用:
while (wcr.get() != null) {
System.gc();
}
所以,这个循环永远不会完成。