Java类加载机制
1.java类加载基本分为三部分:
1. 装载 2. 链接 1. 验证 2. 准备 3. 解析 3. 初始化
1. 加载:
加载就是通过指定的类全限定名,获取此类的二进制字节流,然后将此二进制字节流转化为方法区的数据结构,在内存中生成一个代表这个类的Class对象。
JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:
1.Bootstrap ClassLoader: 负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类 2. Extension ClassLoader: 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包 3. App ClassLoader: 负责记载classpath中指定的jar包及目录中class 4. Custom ClassLoader: 属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。 加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只被ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
2. 验证:
为了确保Class文件中的字节流符合虚拟机的要求,并且不会危害虚拟机的安全.
3. 准备:
1. 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。 2. 这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
4. 解析:
把类中的符号引用转换为直接引用。
5. 初始化:
1. 编译器会自动收集类中的所有静态变量(类变量)和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是根据语句在java代码中的顺序决定的。 2. 收集完成之后,会编译成java类的 static{} 方法,java虚拟机则会保证一个类的static{} 方法在多线程或者单线程环境中正确的执行,并且只执行一次。在执行的过程中,便完成了类变量的初始化。如果只有静态变量,编译器会默认生成一个static{} 3. 执行完类变量后,再执行实例变量,以及普通代码块。 4. 简单来说:初始化阶段是执行类构造器<clinit>()方法的过程。 5. 调用类构造器<clinit>()方法,也是对成员变量进行赋值的时候.
2.举例演示
1. 代码: public class Base { private String name = "hello"; public Base() { // System.out.println(this); // 关键是要认识到调用callName方法的是sub类型的对象 callName(); } public void callName(){ System.out.println(name); } public static void main(String[] args) { new Sub(); } } class Sub extends Base{ private String name="world"; public Sub(){ callName(); } public void callName(){ System.out.println(name); } } 2. 分析: 1. 加载:由加载器进行加载类,ClassLoader及其子类来完成。 2. 验证:由于是JVM自动生成的java类对象,并不存在安全问题。 3. 准备:为静态变量分配内存空间,及设置其为的默认属性值 4. 解析:把类中的符号引用转换为直接引用。 5. 初始化 : 1. 先执行父类,发现没有静态变量,也没有静态代码块。 2. 执行父类的构造器的同时,为父类的name赋值,执行callName()方法 3. 由于子类重写父类的构造器,所以调用子类的callName(),子类的构造器没有执行过,所以的name没有赋值过,是默认值。 4. 再执行子类构造器,这时name值已经被赋值过。 6. 答案: null world
关注公众号:熊猫Jay字节之旅,了解更多 AI 技巧 ~