类加载机制:
(1)加载:将class字节码加载到内存中(二进制流文件),并将这些数据转换为方法区中的运行时数据(静态变量、静态代码块、常量池),在堆中生成一个Class类对象代表这个类(反射实现),作为方法区类数据的访问入口。
(2)链接:将java类的二进制代码合并到jvm的运行状态中。
- 验证:确保类信息符合jvm规范,没有安全问题。
- 准备:为类变量(static)分配内存并设置类初始变量。给它分区,此时的值为默认值,赋值将在初始化阶段。(这时候进行内存分配的仅包括类变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中)
- 解析:虚拟机常量池内的符号引用替换为直接引用。(如果有了直接引用,那么引用的目标必定已经在内存中存在)
(3)初始化:为类的静态变量赋初值
- 在这个阶段才是真正的执行类中的java代码,是类执行构造器(clinit)的阶段。在编译生成class文件时,编译器会产生两个方法加于class文件中,一个是类的初始化方法clinit, 另一个是实例的初始化方法init。
- clinit():类构造器clinit()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
<1>所以clinit只负责静态变量的赋值和静态块中的赋值。若是类中没有静态变量或者静态代码块则不产生clinit方法。
<2>只在类加载的时候执行一次,且必须先执行父类的clinit方法。 - init():实例构造器,主要作用是在类实例化过程中执行,执行内容包括成员变量初始化和代码块的执行。
<1>若类中无成员对象和代码块,不产生init()方法。
<2>每次对象实例化一次,init()加载一次。且必须先执行父类的init()方法。
(4)使用:这个没必要多说。
(5)卸载:
- 执行了System.exit()方法
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 由于操作系统出现错误而导致Java虚拟机进程终止
注意:加载、验证、 准备、.初始化、.卸载这几个步骤是顺序执行的,而解析在某些情况下可在初始化之后再开始。
类的引用:
主动引用(会初始化):
- new 一个对象
- 调用某个类的静态成员变量(出来final和常量)的静态成员方法。
- 使用java.lang.reflect包的方法对类进行反射调用。
- 启动程序所使用的main方法所在类
- 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
被动引用(无需初始化):
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。(多态?)
- 定义对象数组和集合,不会触发该类的初始化
- 类A引用类B的static final常量不会导致类B初始化。(常量在编译阶段就存入调用类的常量池中了)
类加载器的分类
(1)引导类加载器(bootstrap class loader) 它用来加载 Java 的核心库,是用原生代码(C语言)来实现的,并不继承自 java.lang.ClassLoader。(2)扩展类加载器(extensions class loader) 用来加载 Java 的扩展库,Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java类。
(3)应用程序类加载器(application class loader) 它根据 Java 应用的类路径来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
(4)自定义类加载器 开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
类加载器的代理模式:
代理模式即是将指定类的加载交给其他的类加载器。常用双亲委托机制。双亲委托机制:
首先判断该类型是否已经被加载
-->如果没有被加载,就委托给父类加载或者委派给引导类加载器加载
-->如果存在父类加载器,就委派给父类加载器加载
-->如果不存在父类加载器,就检查是否是由启动类加载器加载的类
-->如果不是就委派给引导类去加载
-->如果还是加载失败,则自己加载,无法加载则抛出异常ClassNotFoundException。
例如,用户定义了java.lang.String,那么加载这个类时最高级父类会首先加载,发现核心类中也有这个类,那么就加载了核心类库, 而自定义的类永远都不会加载。