您好,登录后才能下订单哦!
小编给大家分享一下java中类加载和类初始化顺序的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!
1.对于静态变量,静态代码块,变量,初始化代码块,构造器,他们的依次顺序为,(静态变量,静态代码块)>(变量,初始化代码块)>构造器
存在子类和父类的情况如下:
(1)单个类的执行情况,代码如下:
public class InitOrderTest {
// 静态变量
public static String staticField = "静态变量"; // 变量 public String field = "变量"; // 静态初始化块 static { System.out.println(staticField); System.out.println("静态初始化块"); } // 初始化块 { System.out.println(field); System.out.println("初始化块"); } // 构造器 public InitOrderTest() { System.out.println("构造器"); } public static void main(String[] args) { new InitOrderTest(); }
}
输出如下:
(2)对于继承的情况如下:
class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
protected int i = 1;
protected int j = 8;
// 变量
public String p_Field = "父类--变量";
// 静态初始化块 static { System.out.println(p_StaticField); System.out.println("父类--静态初始化块"); } // 初始化块 { System.out.println(p_Field); System.out.println("父类--初始化块"); } // 构造器 public Parent() { System.out.println("父类--构造器"); System.out.println("i=" + i + ", j=" + j); j = 9; }
}
public class SubClass extends Parent {
// 静态变量 public static String s_StaticField = "子类--静态变量"; // 变量 public String s_Field = "子类--变量"; // 静态初始化块 static { System.out.println(s_StaticField); System.out.println("子类--静态初始化块"); } // 初始化块 { System.out.println(s_Field); System.out.println("子类--初始化块"); } // 构造器 public SubClass() { System.out.println("子类--构造器"); System.out.println("i=" + i + ",j=" + j); } // 程序入口 public static void main(String[] args) { new SubClass(); }
}
输出如下:
现在,结果已经不言自明了。子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。
静态变量、静态初始化块,变量、初始化块初始化了顺序取决于它们在类中出现的先后顺序。
执行过程分析
(1)访问SubClass.main(),(这是一个static方法),于是装载器就会为你寻找已经编译的SubClass类的代码(也就是SubClass.class文件)。在装载的过程中,装载器注意到它有一个基类(也就是extends所要表示的意思),于是它再装载基类。不管你创不创建基类对象,这个过程总会发生。如果基类还有基类,那么第二个基类也会被装载,依此类推。
(2)执行根基类的static初始化,然后是下一个派生类的static初始化,依此类推。这个顺序非常重要,因为派生类的“static初始化”有可能要依赖基类成员的正确初始化。
(3)当所有必要的类都已经装载结束,开始执行main()方法体,并用new SubClass()创建对象。
(4)类SubClass存在父类,则调用父类的构造函数,你可以使用super来指定调用哪个构造函数(也就是Beetle()构造函数所做的第一件事)。
基类的构造过程以及构造顺序,同派生类的相同。首先基类中各个变量按照字面顺序进行初始化,然后执行基类的构造函数的其余部分。
(5)对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分。
接下来分析java的类加载机制,在分析类加载机制之前,请看下面一道题:
class Singleton {
private static Singleton singleton = new Singleton();
public static int value1;
public static int value2 = 0;
private Singleton() { value1++; value2++; } public static Singleton getInstance() { return singleton; }
}
class Singleton2 {
public static int value1;
public static int value2 = 0;
private static Singleton2 singleton2 = new Singleton2();
private Singleton2() { value1++; value2++; } public static Singleton2 getInstance2() { return singleton2; }
}
public class TestClassLoader {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("Singleton1 value1:" + singleton.value1);
System.out.println("Singleton1 value2:" + singleton.value2);
Singleton2 singleton2 = Singleton2.getInstance2(); System.out.println("Singleton2 value1:" + singleton2.value1); System.out.println("Singleton2 value2:" + singleton2.value2); }
}
输出结果如下:
jvm类加载分为5个过程:加载,验证,准备,解析,初始化,使用,卸载,如下所示:
下面来看看加载,验证,准备,解析,初始化这5个过程的具体动作。
1.1 加载
加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。
在加载阶段,JVM需要完成3件事:
1)通过类的全限定名获取该类的二进制字节流;
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
1.2 连接
1.2.1 验证
验证是连接阶段的第一步,主要确保加载进来的字节流符合JVM规范。
验证阶段会完成以下4个阶段的检验动作:
1)文件格式验证
2)元数据验证(是否符合Java语言规范)
3)字节码验证(确定程序语义合法,符合逻辑)
4)符号引用验证(确保下一步的解析能正常执行)
1.2.2 准备
准备是连接阶段的第二步,主要为静态变量在方法区分配内存,并设置默认初始值。
1.2.3 解析
解析是连接阶段的第三步,是虚拟机将常量池内的符号引用替换为直接引用的过程。
1.3 初始化
初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。
注:
1)当有父类且父类为初始化的时候,先去初始化父类;
2)再进行子类初始化语句。
什么时候需要对类进行初始化?
1)使用new该类实例化对象的时候;
2)读取或设置类静态字段的时候(但被final修饰的字段,在编译器时就被放入常量池的静态字段除外static final);
3)调用类静态方法的时候;
4)使用反射Class.forName(“xxxx”)对类进行反射调用的时候,该类需要初始化;
5) 初始化一个类的时候,有父类,先初始化父类(注:1. 接口除外,父接口在调用的时候才会被初始化;2.子类引用父类静态字段,只会引发父类初始化);
6) 被标明为启动类的类(即包含main()方法的类)要初始化;
7)当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
以上情况称为对一个类进行主动引用,且有且只要以上几种情况需要对类进行初始化。
再回过头来分析一开始的面试题:
Singleton输出结果:1 0
原因:
1 首先执行main中的Singleton singleton = Singleton.getInstance();
2 类的加载:加载类Singleton
3 类的验证
4 类的准备:为静态变量分配内存,设置默认值。这里为singleton(引用类型)设置为null,value1,value2(基本数据类型)设置默认值0
5 类的初始化(按照赋值语句进行修改):
执行private static Singleton singleton = new Singleton();
执行Singleton的构造器:value1++;value2++; 此时value1,value2均等于1
执行
public static int value1;
public static int value2 = 0;
此时value1=1,value2=0
Singleton2输出结果:1 1
原因:
1 首先执行main中的Singleton2 singleton2 = Singleton2.getInstance2();
2 类的加载:加载类Singleton2
3 类的验证
4 类的准备:为静态变量分配内存,设置默认值。这里为value1,value2(基本数据类型)设置默认值0,singleton2(引用类型)设置为null,
5 类的初始化(按照赋值语句进行修改):
执行
public static int value2 = 0;
此时value2=0(value1不变,依然是0);
执行
private static Singleton singleton = new Singleton();
执行Singleton2的构造器:value1++;value2++;
此时value1,value2均等于1,即为最后结果
类加载器之间的层次关系如下:
类加载器之间的这种层次关系叫做双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。
双亲委派模型的工作过程
如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。
只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。
双亲委派模型的代码实现
双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法当中。
1)首先检查类是否被加载,没有则调用父类加载器的loadClass()方法;
2)若父类加载器为空,则默认使用启动类加载器作为父加载器;
3)若父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。
loadClass源代码如下:
破坏双亲委派模型
双亲委派模型很好的解决了各个类加载器加载基础类的统一性问题。即越基础的类由越上层的加载器进行加载。
若加载的基础类中需要回调用户代码,而这时顶层的类加载器无法识别这些用户代码,怎么办呢?这时就需要破坏双亲委派模型了。
下面介绍两个例子来讲解破坏双亲委派模型的过程。
JNDI破坏双亲委派模型
JNDI是Java标准服务,它的代码由启动类加载器去加载。但是JNDI需要回调独立厂商实现的代码,而类加载器无法识别这些回调代码(SPI)。
为了解决这个问题,引入了一个线程上下文类加载器。 可通过Thread.setContextClassLoader()设置。
利用线程上下文类加载器去加载所需要的SPI代码,即父类加载器请求子类加载器去完成类加载的过程,而破坏了双亲委派模型。
Spring破坏双亲委派模型
Spring要对用户程序进行组织和管理,而用户程序一般放在WEB-INF目录下,由WebAppClassLoader类加载器加载,而Spring由Common类加载器或Shared类加载器加载。
那么Spring是如何访问WEB-INF下的用户程序呢?
使用线程上下文类加载器。 Spring加载类所用的classLoader都是通过Thread.currentThread().getContextClassLoader()获取的。当线程创建时会默认创建一个AppClassLoader类加载器(对应Tomcat中的WebAppclassLoader类加载器): setContextClassLoader(AppClassLoader)。
利用这个来加载用户程序。即任何一个线程都可通过getContextClassLoader()获取到WebAppclassLoader。
看完了这篇文章,相信你对“java中类加载和类初始化顺序的示例分析”有了一定的了解,如果想了解更多相关知识,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。