一、类的加载过程
类的加载过程主要分为三个步骤:1)装载load、2)链接link、3)初始化Initial
1)装载(load):查找并加载类的二进制数据
2)链接(link):主要分为三个步骤
验证:确保被加载的类的二进制数据是合法正确的。由编译器生成的class文件肯定是符合JVM字节码格式的。也有可能黑客自己生成.class文件来作其他用途。
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:将类的符号引用转为直接引用
3)初始化(Initial):为类的静态变量赋予正确值
准备阶段和初始化阶段的不同点:private int static i=10; 准备阶段是将为i变量分配一个整形的内存,初始化阶段再给其赋值10。
二、类的初始化
类的初始化的主要流程:
a.如果这个类还没有被装载、链接,先装载链接
b.如果这个类存在直接父类,并且这个类还没有被初始化,就初始化直接父类,在一个类加载器中类只能加载一次
c.加入类中存在初始化语句,如static变量或static代码块,就依次执行初始化语句
类在什么时候去初始化:
a)创建一个类的时候,也即new一个对象
b)java 反射获取一个类的时候Class.forName("....")
c)访问某个类或接口的静态变量,或者对该静态变量赋值
d)调用类的静态方法
e)初始化一个类的子类,会先初始化父类
f)jvm启动时标明的启动类
三、类的加载
类的加载是指将类的二进制文件.class文件加载到内存中,放置在运行时的方法区,然后在堆内创建这个类的java.lang.Class对象,用来封装类在方法区内的对象。类加载的最终产品是位于堆内的class对象。class对象封装了在方法区内的数据结构,并且提供了供访问这些数据结构的接口
加载类的方式:
1)从本地系统直接加载
2)通过网络下载.class文件
3)将java源文件编译为.class文件
4)从zip/jar等归档文件中加载
四、类加载器
jvm的类加载器是通过ClassLoader及其子类来完成,jvm提供三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader、AppClassLoader。另外还可以自定义ClassLoader 如tomcat、jboss等都有自定义ClassLoader
1)Bootstrap ClassLoader
负责加载$JAVA_HOME里面jre/lib/rt.jar的所有class,由c++实现不是ClassLoader的子类
2)Extension ClassLoader
负责加载$JAVA_HOME中扩展功能的一些jar
3)App ClassLoader
负责加载classpath指向的jar包及目录下的class文件
4)Custom ClassLoader
自定义类加载
由于一个类只能加载一次,类加载器在加载过程会检查类是否被加载,自底向上检查,custom classloader-->app classloader-->extension classloader -->bootstrap classloader.但是类的加载是自顶向下的过程加载,也即由上层逐层加载此类
总结:
java在编译时不需类加载器,编译阶段只是将java代码编译成为jvm可识别的字节码class文件,在运行过程中才会调用类加载器。classLoarder,顾名思义即类加载器,负责将class文件加载到内存中。所使用的策略是双亲委派模型。JVM提供三个ClassLoarder,即Bootstrap ClassLoarder、Extension ClassLoarder、App ClassLoarder。各个加载的位置不一样。对于ExtensionClassLoader和AppClassLoarder有一个共同的父类ClassLoader,代码:
public abstract class ClassLoader {
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve);
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 {
//当父加载器不存在的时候会尝试使用BootStrapClassLoader作为父类
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
}
//c为null则证明父加载器没有加载到,进而使用子类本身的加载策略`findClass()`方法
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;
}
}
由上可知,类加载在第一次加载时先查看是否已经加载过此类了,如果没有,尝试用父加载器去加载,如果父加载器没有加载到,则用子类本身的加载策略。
那么综上所述,什么是双亲模型:
使用parent加载器加载---》如果parent不存在使用BootstrapClassLoader加载---》如果加载不到则使用子类本身的加载器。那么双亲委派模型就是parent委托对象与BootsrapClassLoader最顶端的加载器。另外需要注意的地方是,BootsrapClassLoader本身不是java对象,是C++实现的JVM加载器。更通俗的来说,classLoader就是parent与BootsrapClassLoader,只是当parent不存在时用BootsrapClassLoader而已。
为什么要用委派模型:
回答这个问题需要先回答jvm是如何判断两个类是同一个类,也即类名包括所在的包相等,且是同一个类加载器加载的,那么两个对象才相等。对于Object类加载,由于其都是由父加载器先加载,所以能保证所有的子类都是由同一个ClassLoader加载。也就能保证对象相等。简单来说,类加载器由优先级高的加载器先加载,再由优先级低的加载器加载。
java如何查看已被类加载器加载的类:
ClassLoader有一个private字段classes,这是一个vector,包含了这个类加载器已经加载了的类的class对象
Field f=ClassLoader.class.getDeclaredField("classes");
f.setAccessible(true);
f.get(某个类加载器);
比如
Vector classes=(Vectoe)f.get(ClassLoader.getSystemClassLoader());
这样就取得了这个类加载器当前已加载的类。
如何打破双亲委派机制? 双亲委派模型只是JVM的规范要求,实际上自己的自定义ClassLoader尊不遵循这个规范完全按照自己的业务需求来定。重写LoaderClass方法,将需要特殊对待的类优先处理,非特殊范围内的类调用super()即可。