类加载发生在虚拟机之外
虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行校验、转换解析、和初始化,最终形成可以被java虚拟机直接使用的java类型,这就是类加载机制。
类加载机制发生在java程序运行阶段
当JVM第一次主动引用类的时候,加载该类。但当是被动引用的时候并不会引发类的加载操作。也就是说,JVM对于类的加载,并不是一口气直接加载所有的类,只是在用到某个类的时候,才对此类进行加载。
主动引用
遇到new 、getstatic、putstatic、invokestatic字节码指令 new:实例化对象getstatic、putstatic:读取或者设置一个类的静态字段(被final修饰的除外,因为已经被放入了常量池)调用一个类的静态方法 对类进行反射调用的时候,如果类没有初始化,则需要先初始化。当初始化一个类,但其父类没有初始化,此时需要首先初始化父类。 接口却不同,接口初始化时,不需要提前对父类进行初始化,只有真正使用父接口的时候才初始化。 当虚拟机启动,执行一个主类(包含main方法的类),虚拟机需要先对主类进行初始化。JDK 1.7 动态语言支持:一个 java.lang.invoke.MethodHandle 的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic。被动引用
通过子类引用父类静态字段,只会引发父类的初始化,不会引发子类的初始化。
package com.wangye.并发; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; class parentClass{ static { System.out.println("我是父类"); } static int value = 10; } class sonClass extends parentClass { static { System.out.println("我是子类"); } } public class CountExample { public static void main(String[] args) { System.out.println(sonClass.value); } } /* 我是父类 10 */通过数组定义来引用类,不会发生初始化
package com.wangye.并发; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; class parentClass{ static { System.out.println("我是父类"); } static int value = 10; } class sonClass extends parentClass { static { System.out.println("我是子类"); } } public class CountExample { public static void main(String[] args) { parentClass [] parents = new parentClass[10]; Arrays [] arrays = new Arrays[10]; } }常量被定义后,如果用ClassName.常量名不会引发类的初始化
因为常量在编译期间已经进入常量池
package com.wangye.并发; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; class parentClass{ static { System.out.println("我是父类"); } static final int value = 10; } public class CountExample { public static void main(String[] args) { System.out.println(parentClass.value); } }只有发生5种主动引用的时候,类才会被初始化。也就是说类初始化调入内存是lazy-load,并不是所有的类全部初始化调入内存。
类加载主要分为加载、连接、初始化三个阶段。
加载过程
通过一个类的全限定名来获取定义此类的二进制字节流将字节流中的数据转换为方法区的运行时数据结构(因为方法区存储类的相关信息)在内存中生成一个代表该类的Class对象,,作为方法区这个类的各种数据的访问入口(反射)数组的加载和非数组的加载
非数组类的加载
java中的类加载器 启动类加载器扩展类加载器APP加载器 用户自己定义的类加载器数组类的加载
数组不通过类加载器加载,而是由java虚拟机直接创建HotSpot虚拟机中,class对象虽然是对象,但是他存储在方法区,而不是在heap。
两个类相等
两个类来源于同一个Class文件被同一个虚拟机加载被同一个类加载器加载必须满足以上三个条件类加载器的分类
启动类加载器扩展类加载应用程序类加载器用户自定义类加载器只有启动类加载器是C++编写的 属于虚拟机内部,其余三个都是java编写,独立于虚拟机外部,并且全部继承ClassLoader.类加载器之间的关系
我们可以简单的把上下级之间的加载器理解为父子关系,但是这种父子关系并不是通过继承方式实现的,而是通过组合方式。可以查看源码。双亲委派机制
工作原理
当前类加载器收到类加载的请求后,先不自己尝试加载类,而是先将请求类委派给父类加载器,如果父类还是无法处理,那就继续向上委托,直到启动类加载器。当父类加载器失败之后,才会交由当前类加载器进行加载因为安全性更高一点,子类加载器是没法加载父类加载器加载的类的,父类加载过的类不需要重复加载,这样防止恶意代码冒充java核心库,来兴风作浪。源码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }