java类加载机制、双亲委派模型、分派(重载的判断)

加载-->验证-->准备-->解析-->初始化-->使用-->卸载

    其中验证、准备、解析统称为连接

1. Java类的编译过程

这是由.java源码文件转为 .class二进制字节码文件的过程。
我们编写好的源代码,就是*.java文件。使用“javac test.java”就可以编译test.java文件。

编译过程主要有三步:

  • 词法分析和输入到符号表
  • 注解处理
  • 语义分析和生成字节码

详细过程:

源代码文件*.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器 -> 注解抽象语法树 -> 字节码生成器 -> JVM字节码文件*.class 

最后剩成的JVM字节码文件,使用命令“javap -c test”可以查看test.class的字节码信息,主要包含三项内容:

  • 结构信息:class文件相关信息。

  • 元数据:Java源码中的声明和常量信息。

  • 方法信息:Java源码语句和表达式对应的字节码。

2. 类加载机制

 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转化解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

加载:获取class文件并放入内存,在内存中的方法区生成一个java.lang.Class对象,作为访问入口。

连接

    -验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求。

    -准备:正式为类变量(被static修饰的变量,不包括实例变量,实例变量在对象实例化时随对象分配到java堆中)分配内存并设置类变量初始值,且初始值是指数据类型的零值。

    -解析:将常量池内的符号引用替换为直接引用

初始化:执行类构造器<clinit>()方法的过程,<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的。

附:静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值但不能访问

public class Test {
	
	static {
		i = 0;                      //给变量赋值可以正常通过
		System.out.println(i);		//错。编译器提示“非法向前引用”
	}
	static int i =1;
}


    有五种情况必须初始化(加载、连接自然要在这之前进行)

    1.遇到new、getstatic、putstatic或invokestatic这四条字节码指令时

    2.使用java.lang.reflect包的方法对类进行反射调用的时候

    3.当初始化一个类时,如果父类还没有进行初始化,需要先触发其父类的初始化

    4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类

    5.当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,需要先初始化


    这五种称为对一个类的主动引用,除此之外的都不会触发初始化,称为被动引用。

    例:

    1.通过子类引用父类的静态字段,不会导致子类的初始化

    2.通过数组定义来引用类

    3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会初始化

类加载器的双亲委派模型:

    

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,自加载器才会尝试自己去加载。

    双亲委派模型组织类加载器的好处是,Java类随着它的类加载器一起具备了一种带有优先级的层级关系。

例如类java.lang.Object,它存放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。


分派分为静态分派(重载)和动态分派(重写),分派调用过程将会揭示多态性特征的一些最基本的体现。

Human man = new Man();

Human为变量的静态类型,Man为变量的实际类型。两者都在程序中发生一些变化,区别是静态类型是在编译器可知的,而实际类型变化的结果在运行期才可确定。

重载时是通过参数的静态类型而不是实际类型来判断的

public class Test {
    public static void main(String[] args) {
        Human man = new Man();
        Test test = new Test();
        test.sayHello(man);
    }

    public void sayHello (Human guy) {
        System.out.println("Human");
    }
    public void sayHello (Man guy) {
        System.out.println("Man");
    }
    public void sayHello (Woman guy) {
        System.out.println("Woman");
    }

}

abstract class Human {

}
class Man extends Human {

}
class Woman extends Human {

}

程序的输出结果为 :Human

Human man = new Man();





猜你喜欢

转载自blog.csdn.net/dear_mango/article/details/80667724