一,类加载机制
从源代码文件到物理设备执行程序的过程:
(1) 加载
将class文件(.class)字节码内容加载到内存中(由ClassLoader负责),并将这些静态数据转换成方法区中的运行时数据结构,在堆Heap中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口.
这个过程结束后,在方法区中存储加载进来的类的一些数据,在堆中存储一个对象实例.
(2) 链接
将Java类的二进制代码(就是JDK为我们提供的各种写好的类)合并到JVM的运行状态中.
- 验证:确保加载的类信息符合JVM规范
- 准备:为类变量(static变量)分配内存并设置类变量初始值,这些内存都在方法区中进行分配
- 解析:虚拟机常量池内的符号引用替换为直接引用
(3) 初始化
- 执行类构造器<clinit>()方法,该方法是由编译器自动收集类中的所有类变量的赋值语句和静态语句块(static)中的语句合并产生的.
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则先要发出其父类的初始化.
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁和同步.
- 当访问类的静态域时,只有真正声明这个域的类才会被初始化.(ref 主动引用,被动引用)
注意:当类被加载进内存之后,就不会被二次加载.即,类只加载一次.
拓展:
- 类的主动引用(发生类的初始化)
-new 一个对象
-调用类的静态成员(final 常量除外)
-使用reflect包的方法对类进行反射调用
-初始化一个类时,如果父类没有被初始化,则先初始化父类 - 类的被动引用(不发生类的初始化)
-访问静态域,只有真正声明这个field的类才被初始化(比如,通过子类引用父类的静态field,不会导致子类初始化)
-通过数组定义类引用,不会出发此类的初始化
-引用常量不会触发此类的初始化(因为常量在编译阶段就存入类的常量池了)
二,类加载器
类缓存,标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载一段时间.不过,JVM GC可以回收这些Class对象.
- 引导(Bootstrap)类加载器。由原生代码(如C语言)编写,不继承自
java.lang.ClassLoader
。负责加载核心Java库[5],存储在<JAVA_HOME>/jre/lib
目录中。- 扩展(Extensions)类加载器。用来在
<JAVA_HOME>/jre/lib/ext
,[6]或java.ext.dirs
中指明的目录中加载 Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该类由sun.misc.Launcher$ExtClassLoader
实现。- Apps类加载器(也称系统类加载器)。根据 Java应用程序的类路径(
java.class.path
或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader
实现。
类加载器的加载机制:
双亲委派机制的工作流程:
1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
好处:这种机制保证了不会出现用户自定义rt.jar中类的情况,譬如用户自定义了java.lang.String,那么用户自定义的这个类由于和rt.jar中的类重名而不会被加载进内存.
三,自定义类加载器
//未完待续...
参考: