Java 类加载之匿名类和主类相互依赖问题
qestion
/** * classinitializedorder for : java classload order test * * @author <a href="mailto:magicianisaac@gmail.com">isaac.zhang | 若初</a> * @since 2019/7/20 */ // case 1 public class classinitializedorder { private static boolean initialized = false; static { println("static 代码块执行。"); thread thread = new thread(() -> initialized = true); thread.start(); try { thread.join(); } catch (interruptedexception e) { e.printstacktrace(); } } public static void main(string[] args) { println("main 函数执行。"); system.out.println("initialized = " + initialized); } private static void println(object o){ system.out.println(o); } } ------------------------------------------------------------------- // case 2 public class classinitializedorder { private static boolean initialized = false; static { println("static 代码块执行。"); thread thread = new thread(new runnable() { @override public void run() { println("runnable 代码块执行。"); initialized = true; } }); thread.start(); try { thread.join(); } catch (interruptedexception e) { e.printstacktrace(); } } public static void main(string[] args) { println("main 函数执行。"); system.out.println("initialized = " + initialized); } private static void println(object o){ system.out.println(o); }
answer
- a:
initialized = true
- b:
initialized = false
- c: 编译错误
- d: 以上答案都是错的
explain
程序执行的时候,app classloader 会首先加载classinitializedorder.class
, 按照类的顺序依次执行。
private static boolean initialized = false;
case 1
我们都知道,static
块会在类加载的时候初始化,那么下一步会执行到thread thread = new thread(() -> initialized = true);
我们先来看一下当前行的字节码:
static {}; descriptor: ()v flags: acc_static code: stack=3, locals=2, args_size=0 0: iconst_0 1: putstatic #7 // field initialized:z 4: new #11 // class java/lang/thread 7: dup 8: invokedynamic #12, 0 // invokedynamic #0:run:()ljava/lang/runnable; 13: invokespecial #13 // method java/lang/thread."<init>":(ljava/lang/runnable;)v 16: astore_0 17: aload_0 18: invokevirtual #14 // method java/lang/thread.start:()v 21: aload_0 22: invokevirtual #15 // method java/lang/thread.join:()v 25: goto 33 28: astore_1 29: aload_1 30: invokevirtual #17 // method java/lang/interruptedexception.printstacktrace:()v 33: return
分析#12
可以看到当前行的处理需要()
也就是改匿名类本身来处理,invokedynamic
指令的在当前的执行又依赖于当前所处的主类,主类并没有执行结束,因此它需要等待主类执行结束,因此会在此停顿,如下:
case 2
继续查看字节码:
static {}; descriptor: ()v flags: acc_static code: stack=4, locals=2, args_size=0 0: iconst_0 1: putstatic #1 // field initialized:z 4: ldc #14 // string static 代码块执行。 6: invokestatic #2 // method println:(ljava/lang/object;)v 9: new #15 // class java/lang/thread 12: dup 13: new #16 // class com/sxzhongf/daily/question/july/classinitializedorder$1 16: dup 17: invokespecial #17 // method com/sxzhongf/daily/question/july/classinitializedorder$1."<init>":()v 20: invokespecial #18 // method java/lang/thread."<init>":(ljava/lang/runnable;)v 23: astore_0 24: aload_0 25: invokevirtual #19 // method java/lang/thread.start:()v 28: aload_0 29: invokevirtual #20 // method java/lang/thread.join:()v 32: goto 40 35: astore_1 36: aload_1 37: invokevirtual #22 // method java/lang/interruptedexception.printstacktrace:()v 40: return
查看#16
,我们可以看到这里变成了new #16 // class com/sxzhongf/daily/question/july/classinitializedorder$1
,可以明显看到从之前的invokedynamic
变成了 new 一个匿名类,那么它的结果呢?
依然还是block.我们来换一行代码试试?
public class classinitializedorder { private static boolean initialized = false; static { println("static 代码块执行。"); thread thread = new thread(new runnable() { @override public void run() { //println("runnable 代码块执行。"); system.out.println("runnable 代码块执行。"); //initialized = true; } }); thread.start(); try { thread.join(); } catch (interruptedexception e) { e.printstacktrace(); } }
我们看到我们只是修改了一行代码system.out.println("runnable 代码块执行。");
,那么结果呢?
执行成功的返回了。为什么?继续查看字节码
static {}; descriptor: ()v flags: acc_static code: stack=4, locals=2, args_size=0 0: iconst_0 1: putstatic #9 // field initialized:z 4: ldc #14 // string static 代码块执行。 6: invokestatic #3 // method println:(ljava/lang/object;)v 9: new #15 // class java/lang/thread 12: dup 13: new #16 // class com/sxzhongf/daily/question/july/classinitializedorder$1 16: dup 17: invokespecial #17 // method com/sxzhongf/daily/question/july/classinitializedorder$1."<init>":()v 20: invokespecial #18 // method java/lang/thread."<init>":(ljava/lang/runnable;)v 23: astore_0 24: aload_0 25: invokevirtual #19 // method java/lang/thread.start:()v 28: aload_0 29: invokevirtual #20 // method java/lang/thread.join:()v 32: goto 40 35: astore_1 36: aload_1 37: invokevirtual #22 // method java/lang/interruptedexception.printstacktrace:()v 40: return
查看#16
,看到的还是new
了一个匿名类,和上一个是一样的,为什么就可以成功呢?这个在于当前匿名类中没有依赖主类的代码信息。不存在上下依赖,那么就不会出现相互等待的情况发生,当然也就不会出现block。
那么就有朋友会问,为什么会相互等待呢?这里和我们join
就有关联了,我们来看一下它的实现代码。
public final synchronized void join(long millis) throws interruptedexception { long base = system.currenttimemillis(); long now = 0; if (millis < 0) { throw new illegalargumentexception("timeout value is negative"); } if (millis == 0) { while (isalive()) { wait(0); } } else { while (isalive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = system.currenttimemillis() - base; } } }
我们可以看到,首先它是synchronized
关键词修饰的,那就说明它同时只能被一个线程访问,再往下看,我们能发现,join的具体实现,其实就是wait()
来实现,当子线程中的程序再等待main线程的实现类初始化完成的时候,又依赖了主线程中的某些元素对象。那么就会开始等待主线程初始化完成,这个时候,根据classloader加载类的执行顺序,在#16
就会开始等待,那么主类无法初始化完成,造成相互等待现相。
result
- 匿名内置类的初始化不能依赖于外部类的初始化
- lambda表达式中
invokedynamic
作为主类字节码的一部分,需要等待主类初始化完成才能开始执行
总之,在类的初始化阶段,不能出现内置类(匿名/lambda)和主类初始化中相互依赖的对象
上一篇: html5.2 dialog简介详解
推荐阅读
-
Java 类加载之匿名类和主类相互依赖问题
-
【java基础】面试常见问题:类和对象,封装继承多态,final关键字,static关键字,类加载过程,双亲委派模型
-
解决IDEA和CMD中java命令提示错误: 找不到或无法加载主类的问题
-
Java 类加载之匿名类和主类相互依赖问题
-
cmd上运行java程序遇到的问题(找不到或无法加载主类)
-
【java基础】面试常见问题:类和对象,封装继承多态,final关键字,static关键字,类加载过程,双亲委派模型
-
解决IDEA和CMD中java命令提示错误: 找不到或无法加载主类的问题
-
jvm之java类加载机制和类加载器(ClassLoader)的用法