1 类的加载
1 概念:类的加载就是把类的.class文件中的二进制数据读入到内存中。把它存放在java运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
2 来源: JVM可以从多种来源加载类的class德二进制数据
1 从本地文件系统加载类的class文件
2 从网络上下载类的class的二进制文件.
3 从zip文件或jar文件或其他归档文件中提取.class文件。
4 从一个专有的数据库中提取class文件.
5 把一个java源文件动态编译为class文件。
类加载的最终产品是位于堆区的java.lang.Class对象。Class对象封装了类在方法区的数据结构。并且向java程序提供了访问类在方法区的数据结构的接口。
类的加载是由加载器完成的,可分为两种:
1 java虚拟机自带的加载器,包括启动类加载器,拓展类加载器和系统类加载器。
2 用户自定义的类加载器,是java.lang.ClassLoader类的子类的实例。用户可以通过它来制定类的加载器。
类的加载器并不需要等到某个类被首次主动使用时再加载它,java虚拟机规范允许类加载器在预料某个类将要被使用时就预先加载它。
2 类的验证
当类别加载后,就进入连接阶段。连接就是把已经读入到内存的类的二进制数据合并到虚拟机的运行环境去。连接的第一步是类的验证,保证被加载的类有正确的内部结构,并且与其他类协调一致。如果jvm检查到错误,那么就会抛出相应的Error对象。由java编译器生成的java类的二进制数据肯定是正确的,为什么还要进行类的验证,因为java虚拟机并不知道某个特定的.class文件到底是如何被创建的,这个.class文件有可能是由正常的java编译器生成的,也可能是由黑客特制的,黑客视图通过它来破坏jvm环境。类的验证能提高程序的健壮性,确保程序被安全的执行。
类验证的内容:
1 类文件的结构检查,确保类文件遵从java类文件的固定格式。
2 语义检查确保本身符号java语言的语法规定,如验证final类型的类没有子类等
3 字节码验证:确保字节码可以被jvm安全的执行,字节码代表java方法(包括静态和实例方法),它是由被称做操作码的单字节指令组成的序列,每一个操作码后都跟着一个或多个操作数。字节码验证步骤会检查每个操作码是否合法,即是否有合法的操作数。
4 二进制兼容的验证,确保相互引用的之间协调一致,如Worker类的goWork方法会调用Car类的run方法,jvm在验证Worker类时,会检查方法区内是否存在car类的run方法,如不存在会(Worker 类和car类版本不兼容就会出现问题),就会抛出NoSuchMethodError错误。
3 类的准备:在准备阶段,jvm虚拟机为类的静态变量分配内存,并设置默认的初始值。如int初始化值是0占是2个字节等。
4 类的解析:在解析阶段,jvm会把类的二进制数据中的符号引用替换为直接引用。在Worker类的gotowork方法中会引用Car类run方法。
Java代码
1.public void gotowork()
2. {
3. car.run(); //这段代码在Worker类的二进制数据中表示为符号引用
4. }
在Worker类的二进制数据中,包含了一个对Car类的run方法的的符号引用,它有run方法的全名和相关描述符号组成,在解析阶段jvm会把这个符号引用替换为一个指针,该指针指向Car类的run方法在方法区的内存地址,这个指针就是直接引用。
5 类的初始化:
在初始化阶段,jvm执行类的初始化语句,为类的静态变量赋予初始值。在程序中静态变量的初始化有两种途径:(1) 在静态变量的声明处进行初始化,(2)在静态代码块进行初始化。jvm会按照他们的先后顺序进行初始化。
jvm初始化一个类包含的步骤:
1 如果累没有加载和连接,那就先进行加载和链接。
2 假如类存在直接的父类,并且这个类的父类没有初始化,那就先初始化直接的父类。
3 假如类中存在初始化语句,那就依次执行这些初始化语句。
当初始化一个类的直接父类时,也需要重复以上步骤,这会确保程序主动使用一个类时,这个类及所有的父类和间接父类都已初始化。
Java代码
1.package init;
2.
3.public class Base {
4. static int a = 1;
5. static {
6. System.out.println("init Base");
7. }
8.
9. public Base() {
10. System.out.println("Base()");
11. }
12.
13.}
14.
15.public class Sub extends Base {
16. static int b = 1;
17. static {
18. System.out.println("init Sub");
19. }
20.
21. public Sub() {
22. System.out.println("Sub()");
23. }
24.}
25.
26.public class InitTest {
27. static {
28. System.out.println("init InitTest");
29. }
30.
31. public static void main(String[] args) throws Exception {
32. // System.out.println("b="+Sub.b);
33. // init InitTest
34. // init Base
35. // init Sub
36. // b=1
37.
38. // Base b;//不会初始化Base类
39. // b=new Base();
40. // System.out.println("创建一个Base类的实例");
41. // System.out.println(b.a);
42. // System.out.println("b=" + Sub.b);//没有初始化Base类 仅仅初始化Sub类
43. // System.out.println("创建一个Sub");
44. // new Sub();
45.
46. }
47.
48.}
6 类的初始化时机:
1 创建类的实例,包括用new创建实例,或者通过反射,克隆和反序列号来创建实例。
2 调用类的静态方法。
3 访问某个类的或接口的静态变量或者对该静态变量赋值。
4 调用java api中某些反射方法如Class.forName("Worker"),如果Worker类没有初始化,那么forname方法会初始化Worker类,然后返回这个Worker类的class实例。
5 初始化类的一个子类,例如初始化一个Sub类,可以看做是对它父类Base类的主动使用,先初始化Base类。
7 jvm启动时被表明为启动类的类,如含有main方法的类
以下使用java类的方式被看做是对类的被动使用都不会导致类的初始化。
1 对final类类型的静态变量如果在编译时期就能计算出变量的取值,那么这种变量被看做编译时常量。java程序对类的编译时常量的使用,不会导致类的初始化。
Java代码
1.public class Tester{
2. public final static int a=3;// 编译时常量
3. public final static int b =(int)Math.random();// 非编译时常量
4.}
当另外一个类使用Tester.a时,不会在使用方法的字节码中保存一个表示Tester.a的符号的引用,也不会分配内存。而是直接在字节码中嵌入常量值。使用Test.b时,看做是对类的主动使用,会进行类的初始化。
2 在jvm虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是不适用于接口,在初始化一个类是,并不会先初始化他所实现的接口。
在初始化一个接口时,并不会先初始化他的父接口。一个父接口并不会因为它的子接口或实现类初始化而初始化,只要当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。
只有当程序访问的静态变量和静态方法的确在当前类或接口中定义是,才可以看做是对类或接口的主动使用。
Java代码
1.package initbase;
2.
3.class Base {
4. static int a = 32;
5. static {
6. System.out.println("初始化Base");
7. }
8.
9. public static void methodA() {
10. System.out.println("method of Base");
11. }
12.}
13.
14.class Sub extends Base {
15. static {
16. System.out.println("init Base");
17. }
18.}
19.
20.public class Sample {
21. public static void main(String[] args) {
22. System.out.println(Sub.a); // 仅仅初始化Base
23. Sub.methodA();
24. }
25.}
3 调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。当程序调用Class类的静态方法forName("ClassA")时,才是对ClassA的主动使用,将导致classA被初始化,他的静态代码块被执行。
Java代码
1.package loaderinit;
2.
3.class ClassA {
4. static {
5. System.out.println("now init ClassA");
6. }
7.}
8.
9.public class Tester {
10. public static void main(String[] args) throws Exception {
11. ClassLoader loader = ClassLoader.getSystemClassLoader();// 获得系统的类加载器
12. System.out.println("系统类加载器:" + loader);
13. Class objClass = loader.loadClass("loaderinit.ClassA");
14. System.out.println("after load ClassA");
15. System.out.println("before init Class");
16. objClass = Class.forName("loaderinit.ClassA");// 初始化ClassA
17. }
18.
19.}
类加载器:
类加载器用来把类加载到jvm中,从jdk1.2开始类的加载过程采用父亲委托机制,能保证java平台的安全。jvm自带的根加载器以外,其余的类加载器有且只有一个父加载器。java虚拟机自带的几种加载器:
根(Bootstrap)类加载器:该类没有父加载器,负责加载虚拟机的核心类库,如java.lang.*等。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库,根加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,没有继承java.lang.ClassLoader。
拓展(Extension)类加载器:它的父加载器为根加载器,它从java.ext.dirs系统属性所指定的目录中加载类库,或者从jdk的安装目录的jre\lib\ext子目录下加载类库。如用户把用户创建的jar文件放在这个目录下也会自动由拓展类加载器加载,拓展类加载器是纯java类,是java.lang.ClassLoader类的子类。
系统(System)类加载器:也称为应用类加载器,它的父类加载器为拓展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类。它是用户自定义的类加载器的默认父加载器。也是纯java类 ,java.lang.ClassLoader的子类。
自定义类加载器:继承抽象类java.lang.ClassLoader.
Java代码
1.package classloader;
2.
3.public class Example {
4.
5. /**
6. * @param args
7. */
8. public static void main(String[] args) {
9. Class c = null;
10. ClassLoader loader1, loader2;
11. // 获得系统类加载器
12. loader1 = ClassLoader.getSystemClassLoader();
13. System.out.println("系统类加载器:" + loader1);
14.
15. while (loader1 != null) {
16. loader2 = loader1;
17. loader1 = loader1.getParent();
18. System.out.println(loader2 + "的父亲加载器是:" + loader1);
19. }
20. try {
21. c = Class.forName("java.lang.Object");
22. loader1 = c.getClassLoader();
23. System.out.println("java.lang.Object 的加载器:" + loader1); // 根(Bootstrap)加载器
24.
25. c = Class.forName("classloader.Example");
26. loader1 = c.getClassLoader();
27. System.out.println("classloader.Example 的加载器:" + loader1); // 是根(Bootstrap)加载器
28. System.out.println(Thread.currentThread().getContextClassLoader());
29. } catch (Exception e) {
30. e.printStackTrace();
31. }
32.
33. }
34. /**
35. *
36. * 系统类加载器:sun.misc.Launcher$AppClassLoader@19821f
37. * sun.misc.Launcher$AppClassLoader@19821f的父亲加载器是:sun.misc.Launcher$ExtClassLoader@addbf1
38. * //拓展类加载器 sun.misc.Launcher$ExtClassLoader@addbf1的父亲加载器是:null
39. * java.lang.Object 的加载器:null //根加载器 classloader.Example
40. * 的加载器:sun.misc.Launcher$AppClassLoader@19821f
41. * sun.misc.Launcher$AppClassLoader@19821f //系统类加载器
42. *
43. */
44.}
如果给你带来帮助,欢迎微信或支付宝扫一扫,赞一下。