希望长大对我而言,是可以做更多想做的事,而不是被迫做更多不想做的事...... 首页 类加载机制 丁D 学无止境 2019-02-14 20:15 2818已阅读 JVM 类加载 双亲委派机制 摘要代码编译后将 .java文件变成了 .class文件,class文件要加载到虚拟机中才能使用,虚拟机是怎么加载的呢? # 类加载机制 类的加载过程总的来说分为7个过程:**加载,验证,准备,解析,初始化,使用,卸载,**其中类的验证,准备,解析又称为**连接**阶段 ![alt](/upload/8f3b331d80f44bc1539b854f9845572.png ) java虚拟机规范并没有规定什么时候要进行**加载**阶段,但是规定了什么时候必须进行**初始化**阶段,故而初始化之前要进行加载,验证等阶段。 1,遇到**new**指令的时候,或调用**类静态方法**,又或者访问**类的静态属性**(被**final**修饰的字段除外,已经被放在常量池里面) 2,初始化子类的时候,发现父类还未初始化必须先初始化父类。 3,反射调用的时候 4,main方法所在的类,最先初始化。 例子1 ```java /** * 被动使用类字段演示一: 通过子类引用父类的静态字段,不会导致子类初始化 **/ class SuperClass { static { System.out.println("SuperClass init!"); } public static int value = 123; } class SubClass extends SuperClass { static { System.out.println("SubClass init!"); } } /** * 非主动使用类字段演示 **/ public class NotInitialization { public static void main(String[] args) { System.out.println(SubClass.value); } } ``` 该例子输出: ```js SuperClass init! 123 ``` 说明**通过子类引用父类的静态字段,不会导致子类初始化** 例子2 ```java /** * * 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。 **/ class ConstClass { static { System.out.println("ConstClass init!"); } public static final String HELLOWORLD = "hello world"; } /** * 非主动使用类字段演示 **/ public class NotInitialization1 { public static void main(String[] args) { System.out.println(ConstClass.HELLOWORLD); } } ``` 输出结果 没有输出“ConstClass init! ```java hello world ``` 说明**常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化** 接下来我们简单介绍下各个阶段: **加载阶段**要完成3个步骤 1,通过**全限定**名找到类,并将类变成二进制字节流 2,将这个字节流所代表的**静态存储结构**转化为方法区的**运行时数据结构** 3,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据 的访问入口(Class对象比较特殊,它虽然是对象,但是存放在方法区里面) **验证阶段** 主要验证字节流的信息是否符合java虚拟机的规范验证魔数,版本等信息 **准备阶段** 主要为**类变量**(static修饰)分配内存空间,并设置初始值。类变量是存在方法区的,所以分配内存空间是在方法区进行。 ```java public static int value=123; ``` 在该阶段value的值是0不是123,当在初始化阶段才会变成123。 ```java public static final int value=123; ``` 被final修饰在准备阶段就已经是123 **解析阶段** 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程 **初始化阶段** 初始化阶段是真正执行java代码的阶段。 在准备阶段虚拟机已经为类变量赋予初始值,在初始化阶段才赋予程序员制定的值。 初始化阶段是执行类构造器<clinit>()方法的过程 <clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问 ```java public class Test{ static{ i=0;//给变量赋值可以正常编译通过 System.out.print(i);//这句编译器会提示"非法向前引用" } static int i=1; } ``` 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。。。字段B的值将会是2而不是1 ```java static class Parent{ public static int A=1; static{ A=2; } } static class Sub extends Parent{ public static int B=A; } public static void main(String[]args){ System.out.println(Sub.B); } ``` **静态代码块 构造代码块 构造方法执行顺序** ```java class Parent1 { static { System.out.println("Parent--静态代码块"); } { System.out.println("Parent--构造代码块"); } public Parent1() { System.out.println("Parent--构造方法"); } } public class Child extends Parent1 { static { System.out.println("Child--静态代码块"); } { System.out.println("Child--构造代码块"); } public Child() { System.out.println("Child--构造方法"); } public static void main(String[] args) { new Child(); } } ``` 执行结果 ```java Parent--静态代码块 Child--静态代码块 Parent--构造代码块 Parent--构造方法 Child--构造代码块 Child--构造方法 ``` # 类加载器 java虚拟机将**加载阶段**中的“**通过一个类的全限定名来获取描述此类的二进制字节流**”这个动作的代码模块称为**“类加载器”**。 类加载器只用于实现类的加载动作。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。 这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方 法、isInstance()方法的返回结果 从虚拟机的角度类加载器有两种:**启动类的加载器,其他类的加载器** 从开发人员的角度类加载器有三种:**启动类的加载器,扩展加载器,应用程序类加载器** **启动类加载器**:负责将存放在**<JAVA_HOME>\lib**目录中的类库加载到虚拟机内存中 **扩展加载器**:负责将存放在**<JAVA_HOME>\lib\ext**目录中的类库加载到虚拟机内存中 **应用程序类加载器**:它负责加载用户类路径(**ClassPath**)上所指定的类库 **双亲委派机制** 类加载器之间如下图的这种层次关系,称为类加载器的**双亲委派模型**。 ![alt](/upload/b317215fc8170d1feaed7c124b739f7.png ) **双亲委派模型的工作过程**:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。 很赞哦! (18) 上一篇:java内存管理 下一篇:RocketMQ 目录 点击排行 Elasticsearch6.3.2之x-pack redis哨兵 2019-07-09 22:05 Redis+Twemproxy+HAProxy+Keepalived 2019-07-12 17:20 GC优化策略和相关实践案例 2019-10-10 10:54 JVM垃圾回收器 2019-10-10 10:23 标签云 Java Spring MVC Mybatis Ansible Elasticsearch Redis Hive Docker Kubernetes RocketMQ Jenkins Nginx 友情链接 郑晓博客 佛布朗斯基 凉风有信 MarkHoo's Blog 冰洛博客 南实博客 Rui | 丁D Java研发工程师 生活可以用「没办法」三个字概括。但别人的没办法是「腿长,没办法」、「长得好看,没办法」、「有才华,没办法」。而你的没办法,是真的没办法。 请作者喝咖啡