Java技术_基础技术(0003)_类执行顺序详解+实例(阿里面试题)+详细讲解+流程图
类加载机制
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。以下陈述的内容都已HotSpot为基准。
1. 加载
在加载阶段(可以参考java.lang.ClassLoader的loadClass()方法),虚拟机需要完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等);
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中(堆中)生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
加载阶段和连接阶段(Linking)的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。
2. 验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段大致会完成4个阶段的检验动作:
文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以魔术0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证:确保解析动作能正确执行。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
3. 准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
public static int value=123;
1
那变量value在准备阶段过后的初始值为0而不是123.因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。
至于“特殊情况”是指:public static final int value=123,即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注为final之后,value的值在准备阶段初始化为123而非0.
4. 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
5. 初始化
类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序制定的主观计划去初始化类变量和其他资源,或者说:初始化阶段是执行类构造器<clinit>()方法的过程.
<clinit>()方法是由编译器按顺序自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的
例子1:
1 package static1; 2 3 public class StaticTest 4 { 5 public static void main(String[] args) 6 { 7 staticFunction(); 8 } 9 10 static StaticTest st = new StaticTest(); 11 12 static 13 { 14 System.out.println("1"); 15 } 16 17 { 18 System.out.println("2"); 19 } 20 21 StaticTest() 22 { 23 System.out.println("3"); 24 System.out.println("a="+a+",b="+b); 25 } 26 27 public static void staticFunction(){ 28 System.out.println("4"); 29 } 30 31 int a=110; 32 static int b =112; 33 }
执行结果:
2
3
a=110,b=0
1
4
字节码结果分析
- 165行:属于类的构造方法(顺序进行 类变量赋值、静态块执行),代码的第10行执行 static StaticTest st = new StaticTest(); 为st赋值,要先new一个StaticTest实例变量(会调用实例构造方法-字节码的第109行)
- 109行:属于实例构造方法:(顺序执行 初始化成员变量(-非类变量)、非静态块,再执行实例构造方法);代码执行顺序如下
- 先17-19行的非静态块
- 31行的成语变量赋值
- 执行实例构造方法(21-25行)
javp -v -p StaticTest.class
1 Classfile /Users/wuxiong.wx/work/code/testDemo/target/classes/static1/StaticTest.class 2 Last modified 2023-4-21; size 1074 bytes 3 MD5 checksum 1f95645f966d54b5c0d941d86616d858 4 Compiled from "StaticTest.java" 5 public class static1.StaticTest 6 minor version: 0 7 major version: 52 8 flags: ACC_PUBLIC, ACC_SUPER 9 Constant pool: 10 #1 = Methodref #17.#41 // static1/StaticTest.staticFunction:()V 11 #2 = Methodref #21.#42 // java/lang/Object."<init>":()V 12 #3 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream; 13 #4 = String #45 // 2 14 #5 = Methodref #46.#47 // java/io/PrintStream.println:(Ljava/lang/String;)V 15 #6 = Fieldref #17.#48 // static1/StaticTest.a:I 16 #7 = String #49 // 3 17 #8 = Class #50 // java/lang/StringBuilder 18 #9 = Methodref #8.#42 // java/lang/StringBuilder."<init>":()V 19 #10 = String #51 // a= 20 #11 = Methodref #8.#52 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21 #12 = Methodref #8.#53 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 22 #13 = String #54 // ,b= 23 #14 = Fieldref #17.#55 // static1/StaticTest.b:I 24 #15 = Methodref #8.#56 // java/lang/StringBuilder.toString:()Ljava/lang/String; 25 #16 = String #57 // 4 26 #17 = Class #58 // static1/StaticTest 27 #18 = Methodref #17.#42 // static1/StaticTest."<init>":()V 28 #19 = Fieldref #17.#59 // static1/StaticTest.st:Lstatic1/StaticTest; 29 #20 = String #60 // 1 30 #21 = Class #61 // java/lang/Object 31 #22 = Utf8 st 32 #23 = Utf8 Lstatic1/StaticTest; 33 #24 = Utf8 a 34 #25 = Utf8 I 35 #26 = Utf8 b 36 #27 = Utf8 main 37 #28 = Utf8 ([Ljava/lang/String;)V 38 #29 = Utf8 Code 39 #30 = Utf8 LineNumberTable 40 #31 = Utf8 LocalVariableTable 41 #32 = Utf8 args 42 #33 = Utf8 [Ljava/lang/String; 43 #34 = Utf8 <init> 44 #35 = Utf8 ()V 45 #36 = Utf8 this 46 #37 = Utf8 staticFunction 47 #38 = Utf8 <clinit> 48 #39 = Utf8 SourceFile 49 #40 = Utf8 StaticTest.java 50 #41 = NameAndType #37:#35 // staticFunction:()V 51 #42 = NameAndType #34:#35 // "<init>":()V 52 #43 = Class #62 // java/lang/System 53 #44 = NameAndType #63:#64 // out:Ljava/io/PrintStream; 54 #45 = Utf8 2 55 #46 = Class #65 // java/io/PrintStream 56 #47 = NameAndType #66:#67 // println:(Ljava/lang/String;)V 57 #48 = NameAndType #24:#25 // a:I 58 #49 = Utf8 3 59 #50 = Utf8 java/lang/StringBuilder 60 #51 = Utf8 a= 61 #52 = NameAndType #68:#69 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 62 #53 = NameAndType #68:#70 // append:(I)Ljava/lang/StringBuilder; 63 #54 = Utf8 ,b= 64 #55 = NameAndType #26:#25 // b:I 65 #56 = NameAndType #71:#72 // toString:()Ljava/lang/String; 66 #57 = Utf8 4 67 #58 = Utf8 static1/StaticTest 68 #59 = NameAndType #22:#23 // st:Lstatic1/StaticTest; 69 #60 = Utf8 1 70 #61 = Utf8 java/lang/Object 71 #62 = Utf8 java/lang/System 72 #63 = Utf8 out 73 #64 = Utf8 Ljava/io/PrintStream; 74 #65 = Utf8 java/io/PrintStream 75 #66 = Utf8 println 76 #67 = Utf8 (Ljava/lang/String;)V 77 #68 = Utf8 append 78 #69 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; 79 #70 = Utf8 (I)Ljava/lang/StringBuilder; 80 #71 = Utf8 toString 81 #72 = Utf8 ()Ljava/lang/String; 82 { 83 static static1.StaticTest st; 84 descriptor: Lstatic1/StaticTest; 85 flags: ACC_STATIC 86 87 int a; 88 descriptor: I 89 flags: 90 91 static int b; 92 descriptor: I 93 flags: ACC_STATIC 94 95 public static void main(java.lang.String[]); 96 descriptor: ([Ljava/lang/String;)V 97 flags: ACC_PUBLIC, ACC_STATIC 98 Code: 99 stack=0, locals=1, args_size=1 100 0: invokestatic #1 // Method staticFunction:()V 101 3: return 102 LineNumberTable: 103 line 7: 0 104 line 8: 3 105 LocalVariableTable: 106 Start Length Slot Name Signature 107 0 4 0 args [Ljava/lang/String; 108 109 static1.StaticTest(); 110 descriptor: ()V 111 flags: 112 Code: 113 stack=3, locals=1, args_size=1 114 0: aload_0 115 1: invokespecial #2 // Method java/lang/Object."<init>":()V 116 4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 117 7: ldc #4 // String 2 118 9: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 119 12: aload_0 120 13: bipush 110 121 15: putfield #6 // Field a:I 122 18: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 123 21: ldc #7 // String 3 124 23: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 125 26: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 126 29: new #8 // class java/lang/StringBuilder 127 32: dup 128 33: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V 129 36: ldc #10 // String a= 130 38: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 131 41: aload_0 132 42: getfield #6 // Field a:I 133 45: invokevirtual #12 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 134 48: ldc #13 // String ,b= 135 50: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 136 53: getstatic #14 // Field b:I 137 56: invokevirtual #12 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 138 59: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 139 62: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 140 65: return 141 LineNumberTable: 142 line 22: 0 143 line 18: 4 144 line 31: 12 145 line 23: 18 146 line 24: 26 147 line 25: 65 148 LocalVariableTable: 149 Start Length Slot Name Signature 150 0 66 0 this Lstatic1/StaticTest; 151 152 public static void staticFunction(); 153 descriptor: ()V 154 flags: ACC_PUBLIC, ACC_STATIC 155 Code: 156 stack=2, locals=0, args_size=0 157 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 158 3: ldc #16 // String 4 159 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 160 8: return 161 LineNumberTable: 162 line 28: 0 163 line 29: 8 164 165 static {}; 166 descriptor: ()V 167 flags: ACC_STATIC 168 Code: 169 stack=2, locals=0, args_size=0 170 0: new #17 // class static1/StaticTest 171 3: dup 172 4: invokespecial #18 // Method "<init>":()V 173 7: putstatic #19 // Field st:Lstatic1/StaticTest; 174 10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 175 13: ldc #20 // String 1 176 15: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 177 18: bipush 112 178 20: putstatic #14 // Field b:I 179 23: return 180 LineNumberTable: 181 line 10: 0 182 line 14: 10 183 line 32: 18 184 } 185 SourceFile: "StaticTest.java"
java中类不会被初始化(仅加载)的几种情况
1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。子类和父类都会被加载
1_1. 通过AA.num引用静态常量,不会触发AA类的加载、也不会初始化(常量在编译期间会存入调用类的常量池中)
2. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触
* 发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。会加载类。
3. 通过类名获取 Class 对象(Class.forName(**)),不会触发类的初始化,会加载
4. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)。
5. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)。
6. 定义对象数组,不会触发该类的初始化。该类会加载 (A[] bs=new A[10];)
6_1. new对象(子类):会加载父类、子类,再初始化父类、子类 —— 应该是这个
* (还是先加载父类、初始化父类,再加载子类、初始化子类?)
深入理解JAVA虚拟机:https://doc.yonyoucloud.com/doc/wiki/project/java-vm/class-initialization.html
类初始化是类加载过程的最后一个阶段,到初始化阶段,才真正开始执行类中的 Java 程序代码。虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化:
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的 Java 代码场景是:使用 new 关键字实例化对象时、读取或设置一个类的静态字段(static)时(被 static 修饰又被 final 修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时。
- 使用 Java.lang.refect 包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类。
虚拟机规定只有这四种情况才会触发类的初始化,称为对一个类进行主动引用,除此之外所有引用类的方式都不会触发其初始化,称为被动引用。下面举一些例子来说明被动引用。
通过子类引用父类中的静态字段,这时对子类的引用为被动引用,因此不会初始化子类,只会初始化父类:
1 package static2; 2 3 class A { 4 public static int a = 10; 5 public static final int num = 1998; 6 static { 7 System.out.println("A-初始化"); 8 } 9 } 10 public class AA extends A{ 11 static { 12 System.out.println("AA-初始化"); 13 } 14 }
1 package static2; 2 3 import java.lang.reflect.Field; 4 import java.util.ArrayList; 5 import java.util.List; 6 import java.util.Vector; 7 import java.util.stream.Collectors; 8 9 public class PrintClassInfo { 10 11 public static void printLoadedClass(String packageNameFilter)throws Exception{ 12 if(packageNameFilter==null){ 13 packageNameFilter="static2"; 14 } 15 final String condition=packageNameFilter; 16 ClassLoader classLoader=Thread.currentThread().getContextClassLoader(); 17 Class loader=ClassLoader.class; 18 Field classesFiled = loader.getDeclaredField("classes"); 19 classesFiled.setAccessible(true); 20 Vector<Class> classVector = (Vector<Class>)classesFiled.get(classLoader); 21 List<Class> classes=new ArrayList<>(classVector); 22 classes = classes.stream().filter(clazz->clazz.getName().contains(condition)).collect(Collectors.toList()); 23 System.out.println("加载的类:"); 24 classes.forEach(clazz-> System.out.println(clazz.getName())); 25 } 26 27 }
1 package static2; 2 3 /** 4 * java中类不会被初始化(仅加载)的几种情况:https://blog.csdn.net/CaptHua/article/details/120925452 5 * 6 * 1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。子类和父类都会被加载 7 * 8 * 输出结果: 9 * A-初始化 10 * 10 11 * ************************ 12 * 加载的类: 13 * static2.PrintClassInfo 14 * static2.Test1 15 * static2.A 16 * static2.AA 17 */ 18 public class Test1 extends PrintClassInfo{ 19 public static void main(String[] args) throws Exception { 20 System.out.println(AA.a); 21 22 System.out.println("************************"); 23 24 printLoadedClass("static2"); 25 } 26 }
1 package static2; 2 3 /** 4 * 1_1. 通过AA.num引用静态常量,不会触发AA类的加载、也不会初始化(常量在编译期间会存入调用类的常量池中) 5 * 6 * 输出结果: 7 * 1998 8 * ************************ 9 * 加载的类: 10 * static2.PrintClassInfo 11 * static2.Test1_1 12 */ 13 public class Test1_1 extends PrintClassInfo{ 14 public static void main(String[] args) throws Exception { 15 System.out.println(AA.num); 16 17 System.out.println("************************"); 18 19 printLoadedClass("static2"); 20 } 21 }
1 package static2; 2 3 /** 4 * 2. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触 5 * 发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。会加载类。 6 * 7 * 输出结果: 8 * class static2.A 9 * ************************ 10 * 加载的类: 11 * static2.PrintClassInfo 12 * static2.Test2 13 * static2.A 14 * 15 */ 16 public class Test2 extends PrintClassInfo{ 17 public static void main(String[] args) throws Exception { 18 System.out.println(A.class); 19 20 System.out.println("************************"); 21 22 printLoadedClass(null); 23 } 24 }
1 package static2; 2 3 /** 4 * 3. 通过类名获取 Class 对象,不会触发类的初始化,会加载 5 * 6 * ************************ 7 * 加载的类: 8 * static2.PrintClassInfo 9 * static2.Test3 10 * static2.A 11 */ 12 public class Test3 extends PrintClassInfo{ 13 public static void main(String[] args) throws Exception { 14 Class.forName("static2.A",false,Thread.currentThread().getContextClassLoader()); 15 16 System.out.println("************************"); 17 18 printLoadedClass(null); 19 } 20 }
1 package static2; 2 3 /** 4 * 4. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)。 5 * 6 * 输出结果: 7 * ************************ 8 * 加载的类: 9 * static2.PrintClassInfo 10 * static2.Test4 11 * static2.A 12 */ 13 public class Test4 extends PrintClassInfo{ 14 public static void main(String[] args) throws Exception { 15 Class.forName("static2.A",false,Thread.currentThread().getContextClassLoader()); 16 17 System.out.println("************************"); 18 19 printLoadedClass(null); 20 } 21 }
1 package static2; 2 3 /** 4 * 5. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)。 5 * 6 * 输出结果: 7 * ************************ 8 * 加载的类: 9 * static2.PrintClassInfo 10 * static2.Test5 11 * static2.A 12 */ 13 public class Test5 extends PrintClassInfo{ 14 public static void main(String[] args) throws Exception { 15 ClassLoader classLoader=Thread.currentThread().getContextClassLoader(); 16 classLoader.loadClass("static2.A"); 17 18 System.out.println("************************"); 19 20 printLoadedClass(null); 21 } 22 }
1 package static2; 2 3 /** 4 * 6. 定义对象数组,不会触发该类的初始化。该类会加载 5 * 6 * 输出结果: 7 * ************************ 8 * 加载的类: 9 * static2.PrintClassInfo 10 * static2.Test6 11 * static2.A 12 */ 13 public class Test6 extends PrintClassInfo{ 14 public static void main(String[] args) throws Exception { 15 A[] bs=new A[10]; 16 17 System.out.println("************************"); 18 19 printLoadedClass(null); 20 } 21 }
1 package static2; 2 3 /** 4 * 6_1. new对象(子类):会加载父类、子类,再初始化父类、子类 —— 应该是这个 5 * (还是先加载父类、初始化父类,再加载子类、初始化子类?) 6 * 7 * 输出结果: 8 * ************************ 9 * 加载的类: 10 * static2.PrintClassInfo 11 * static2.Test6 12 * static2.B 13 */ 14 public class Test6_1 extends PrintClassInfo{ 15 public static void main(String[] args) throws Exception { 16 A b=new AA(); 17 18 System.out.println("************************"); 19 20 printLoadedClass(null); 21 } 22 }
例子2:
一、总体原则
列出执行顺序的原则(这里本人出了简化,比较明了。可能有漏的,请帮忙补充,但应付该实例足以):
==父类先于子类;
==静态先于非静态;
==变量和块先于构造方法;
==变量声明先于执行(变量赋值、块执行);(这一点是根据数据在内存中是如何存储的得出的,基本类型、对象、String均不一样,但是总结起来应该是这样)
==变量赋值与块优先级相同;
==同优先级的从上至下执行;
二、实例
public class StaticTest { public static int k = 0; // 1 public static StaticTest s1 = new StaticTest("t1"); // 2 public static StaticTest s2 = new StaticTest("t2"); //3 public static int i = print("i"); //4 public static int n = 99; //5 public int j = print("j");// 6 { print("构造快"); } // 7 static{ print("静态块"); } // 8 public StaticTest(String str) { System.out.println((++k) + ":" + str + " i=" + i + " n=" + n); ++n; ++i; } // 9 public static int print(String str) { System.out.println((++k) + ":" + str + " i=" + i + " n=" + n); ++i; return ++n; } public static void main(String[] args) { StaticTest t = new StaticTest("init"); } }
三、结果
syso1 ==== 1:j i=0 n=0 syso2 ==== 2:构造快 i=1 n=1 syso3 ==== 3:t1 i=2 n=2 syso4 ==== 4:j i=3 n=3 syso5 ==== 5:构造快 i=4 n=4 syso6 ==== 6:t2 i=5 n=5 syso7 ==== 7:i i=6 n=6 syso8 ==== 8:静态块 i=7 n=99 syso9 ==== 9:j i=8 n=100 syso10 ==== 10:构造快 i=9 n=101 syso11 ==== 11:init i=10 n=102
四、说明
一步一步按照上面的原则来分析,应该比较详细了,这一部分内容在下载的代码里也有。分析如下:
/**
* ====step1====首先声明StaticTest类的static变量并给与默认值。
*
* 故,执行1、2、3、4、5变量的声明。运行后变量如下:
*
* k=0; s1=null; s2=null; i=0; n=0; j未声明;
*
* ====step1 end====
*/
/**
* ====step2====变量声明完成后,为这些static变量赋值以及执行static块(赋值和块优先级一致,在前面的先执行).
*
* 故,执行1、2、3、4、5的赋值代码,8的块代码
*
* ========step2_1====当step2执行到2时,new了一个新的StaticTest对象.
* (此时2、3、4、5的赋值、8的代码都没执行,变量k被赋值为0,其余仍与step1中一致 )
* 这时,static变量已经声明(static变量是类属性,与对象无关),所以这里直接声明非static变量并给与默认值。
*
* 故,执行6变量的声明,运行后变量如下:
*
* k=0; s1=null; s2=null; i=0; n=0; j=0(s1的);
*
* ========step2_2====变量声明完成后,为这些非static变量赋值以及执行非static块。
*
* 故,执行6变量的赋值代码(syso1====k、n、i、j均+1),7的块代码(syso2====k、n、i均+1),运行后变量如下:
*
* k=2; s1=null; s2=null; i=2; n=2; j=1(s1的);
*
* ========step2_3====变量赋值、块执行完毕后,执行构造方法。
*
* 故,执行9构造方法(syso3====k、n、i均+1),运行后变量如下:
*
* k=3; s1=StaticTest对象; s2=null; i=3; n=3; j=1(s1的);
*
* ========step2_4====s1构造完毕后,继续执行3。同样是new一个新的StaticTest对象.
* 过程与step2_1至step2_3一致。
*
* 故,执行3(syso4====k、n、i、j(j=0)均+1;syso5====k、n、i均+1;syso6====k、n、i均+1),
* 运行后变量如下:
*
* k=6; s1=StaticTest对象; s2=StaticTest对象; i=6; n=6; j=1(s2的);
*
* ========step2_5====s2构造完毕后,继续执行4、5的赋值,为i和n赋值.
*
* 故,执行4(syso7====k、n、i均+1,i被重新赋值)、5(n被重新赋值)的赋值代码,运行后变量如下:
*
* k=7; s1=StaticTest对象; s2=StaticTest对象; i=7; n=99; j未声明;
*
* ========step2_6====static变量赋值完毕后,执行8的static块。
*
* 故,执行8(syso8====k、n、i均+1),运行后变量如下:
*
* k=8; s1=StaticTest对象; s2=StaticTest对象; i=8; n=100; j未声明;
*
* ====step2 end====
*/
/**
* ====step3====static变量及块完成后,声明非static变量并给与默认值.
*
* 故,执行6变量的声明。运行后变量如下:
*
* k=8; s1=StaticTest对象; s2=StaticTest对象; i=8; n=100; j=0(main的);
*
* ====step3 end====
*/
/**
* ====step4====非static变量声明完成后,为这些变量赋值以及执行非static块(赋值和块优先级一致,在前面的先执行)。
*
* 故,执行6的赋值代码(syso9====k、n、i均+1,j=++i),7的块代码(syso10====k、n、i均+1)。运行后变量如下 :
*
* k=10; s1=StaticTest对象; s2=StaticTest对象; i=10; n=102; j=9(main的);
*
* ====step4 end====
*/
/**
* ====step5====变量赋值、块执行完毕后,执行构造方法。
*
* 故,执行9构造方法(syso11====k、n、i均+1),运行后变量如下:
*
* k=11; s1=StaticTest对象; s2=StaticTest对象; i=11; n=103; j=9(main的);
*
* ====step5 end====
*/
参考
- Java虚拟机类加载机制 https://blog.csdn.net/u013256816/article/details/50829596
- Java虚拟机类加载机制——案例分析 https://blog.csdn.net/u013256816/article/details/50837863
- Java技术_基础技术(0003)_类执行顺序详解+实例(阿里面试题)+详细讲解+流程图 https://developer.aliyun.com/article/49257
-
java中类不会被初始化(仅加载)的几种情况:https://blog.csdn.net/CaptHua/article/details/120925452