类加载阶段
加载
- 将类的字节码载入到方法区中,内部采用C 的instanceKlass描述java类,他的重要field有:
- java_mirror即java的类镜像,例如对String来说,就是String.class,作用是吧klass暴露给java使用
- _super即父类
- _field即成员变量
- _method即方法
- constants即常量池
- _class_loader即类加载器
- vtable虚方法表
- _itable接口方法表
- 如果这个类还有父类没有加载,先加载父类
- 加载和链接可能是交替运行的
注意
- instabceKlass这样的【元数据】是存储在方法区(1.8后的元空间内),但_java_mirror是存储在堆中
链接
验证:验证类是否符合JVM规范,安全性检查。用UE等支持二进制的编辑器修改HelloWord.class的魔数(CA FE BA BE),在控制台运行
准备:为static变量分配空间,设置默认值
- static变量在JDK7之前存储于instanceKlass末尾,从JDK7开始,存储于_java_mirror末尾
- static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
- 如果static变量是final的基本类型,以及字符创常量,那么编译阶段值就确定了,赋值在准备阶段完成
- 如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
解析:将常量池中的符号引用解析为直接引用
初始化
()V方法 初始化即调用()v,虚拟机会保证这个类的【构造方法】的线程安全 发生的时机 概括的说,类初始化是【懒惰的】
- main方法所在的类,总会被首先初始化
- 首次访问这个类的静态变量或静态方法时
- 子类初始化,如果父类还没初始化,会引发
- 子类访问父类的静态变量,只会触发父类的初始化
- Class.forName
- new会导致初始化
不会导致类初始化的情况
- 访问类的static final 静态变量(基本类型和字符串)不会触发初始化
- 类对象.class不会触发初始化
- 创建该类的数组不会触发初始化
- 类加载器的loadClass方法
- Class.forName的参数2为false时
public class Load3 {
static {
System.out.println("main init");
}
public static void main(String[] args) throws ClassNotFoundException {
// 静态final变量(基本类型和字符串)不会触发初始化
System.out.println(B.b);
// 类对象.class不会触发初始化
System.out.println(B.class);
// 创建类的数组不会触发初始化
System.out.println(new B[0]);
// 不会初始化B,但会加载A B
ClassLoader c1 = Thread.currentThread().getContextClassLoader();
c1.loadClass("cn.itcast.load.B");
// 不会初始化B,但会加载A B
ClassLoader c2 = Thread.currentThread().getContextClassLoader();
Class.forName("cn.itcast.load.B", false, c2);
// 首次访问这个类的静态变量或静态方法时
System.out.println(A.a);
// 子类初始化,如果父类还没初始化,会引发
System.out.println(B.c);
// 子类访问父类静态变量,只触发父类初始化
System.out.println(B.a);
// 会初始化类B,并先初始化类A
Class.forName("cn.itcast.load.B");
}
}
class A {
static int a = 0;
static {
System.out.println("a init");
}
}
class B extends A {
final static double b = 5.0;
static boolean c = false;
static {
System.out.println("b init");
}
}
懒加载且线程安全的单例模式
代码语言:javascript复制public class Load9 {
public static void main(String[] args) {
// 只调用test方法是不会触发LazyHolder的加载
Singleton.test();
// 此时才会加载LazyHolder
Singleton.getInstance();
}
}
class Singleton {
private Singleton() {
}
public static void test() {
System.out.println("test");
}
private static class LazyHolder {
private static final Singleton SINGLETON = new Singleton();
static {
System.out.println("lazy holder init");
}
}
public static Singleton getInstance() {
return LazyHolder.SINGLETON;
}
}
类加载器
以JDK8为例:
名称 | 加载哪的类 | 说明 |
---|---|---|
Bootstrap ClassLoader | JAVA_HOME/jre/lib | 无法直接访问 |
Extension ClassLoader | JAVA_HOME/jre/lib/ext | 上级为Bootstrap,显示为null |
Application ClassLoader | classpath | 上级为Extension |
自定义类加载器 | 自定义 | 上级为Application |
验证Extension :jar -cvf my.jar com/learn/G.class 打为jar包,放至JAVA_HOME/jre/lib/ext
代码语言:javascript复制D:JavaProjectlearnclassloadoutproductionclassload>jar -cvf my2.jar comlearnG.class
已添加清单
正在添加: com/learn/LoaderTest.class(输入 = 872) (输出 = 486)(压缩了 44%)
代码语言:javascript复制public class Loader {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> aClass = Class.forName("com.learn.G");
System.out.println(aClass.getClassLoader());// 输出:sun.misc.Launcher$ExtClassLoader@135fbaa4
}
}
双亲委派模式
所谓的双亲委派,就是指调用类加载器的loadClass方法时,查找类的规则 ClassLoader.loadClass()方法源码
代码语言:javascript复制protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1.检查该类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
// 没加载
long t0 = System.nanoTime();
try {
if (parent != null) {
// 2. 如果有上级,委派上级loadClass
c = parent.loadClass(name, false);
} else {
// 3. 如果没有上级了(ExtClassLoader),则委派BootstrapClassLoader
// 就是去JAVA_HOME/jre/lib找
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
// 4. 每一层都找不到,调用findClass方法(每个类加载器自己扩展)来加载
c = findClass(name);
// 记录耗时
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
线程上下文类加载器
我们在使用JDBC时,都需要加载Driver驱动,不知道你注意到没有,不写
代码语言:javascript复制Class.forName("com,mysql.jdbc.Driver")
也是可以让com.mysql.jdbc.Driver正确加载的,你知道是怎么做的吗? 让我们追踪一下源码:
代码语言:javascript复制public class DriverManager {
// 注册驱动集合
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
// 初始化驱动
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
先不看别的,看看DriverManager的类加载器
代码语言:javascript复制System.out.println(DriverManager.class.getClassLoader()); // 输出为null,说明是Bootstrap加载的
打印null,表示它的类加载器是Bootstrap ClassLoader,会到JAVA_HOME/jre/lib下搜索类,但JAVA_HOME/jre/lib下显然没有mysql-connertor-java.jar包,这样问题就来了,在DriverManager的静态代码块中,怎么能正确加载com.mysql.jdbc.Driver呢?
继续看loadInitialDrivers()方法:
代码语言:javascript复制private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// 1)使用 ServiceLoader 机制加载驱动,即 SPI
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 此处Iterator实例数据来源其实是在ServiceLoader的内部类LazyIterator中
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " drivers);
// 2)使用 jdbc.drivers 定义的驱动名加载驱动
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " aDriver);
// 3)ClassLoader.getSystemClassLoader()其实就是应用程序类加载器 Application ClassLoader
// 此处相当于打破了双亲委派模式的类加载机制
// DriverManager理应使用Bootstrap类加载器加载,而这里实际使用的是Application ClassLoader
// 因为Bootstrap压根加载不到mysql的驱动类
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " ex);
}
}
}
注意3)处,此处相当于打破了双亲委派模式的类加载机制,DriverManager作为JAVA_HOME/jre/lib下的jar包,理应使用Bootstrap ClassLoader加载,而这里实际使用的是Application ClassLoader,因为Bootstrap压根加载不到mysql的驱动类,只能由Application ClassLoader来加载,所以某些情况,jdk要打破双亲委派机制才能完成某些类的加载。 再看1)他就是大名鼎鼎的Service Provider Interface(SPI) 约定如下,在jar包的META_INF/services包下,以接口全限定名为文件名,文件内容就是实现类名称
如上图,这样就可以使用
代码语言:javascript复制ServiceLoader<Driver> allImpls = ServiceLoader.load(Driver.class);
Iterator<Driver> iter = allImpls.iterator();
while (iter.hasNext()) {
iter.next();
}
获取到com.mysql.jdbc.Driver和FabricMySQLDriver实现类,体现的是【面向接口编程 解耦】的思想 接着看ServiceLoader.load方法:
代码语言:javascript复制public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
线程上下文类加载器是当前线程使用的类加载器,默认就是应用程序类加载器,它内部有是由Class.forName调用了线程上下文类加载器完成类加载,具体代码在ServiceLoader的内部类LazyIterator中:
代码语言:javascript复制private class LazyIterator
implements Iterator<S>
{
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " cn " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " cn " not a subtype");
}
try {
S p = service.cast(c.newInstance());
// 缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " cn " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
自定义类加载器
问问自己,什么时候需要自定义类加载器
- 想加载非classpath随意路径中的类文件
- 都是通过接口来使用实现,希望解耦时,常用在框架设计
- 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于tomcat容器
步骤
- 继承ClassLoader父类
- 要遵从双亲委派机制,重写findClass方法 a. 注意不是重写loadClass方法,否则不会走双亲委派机制
- 读取类文件的字节码
- 调用父类的defineClass方法来加载类
- 使用者调用该类加载器的loadClass方法
public class CustomClassLoader extends ClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
CustomClassLoader classLoader = new CustomClassLoader();
classLoader.loadClass("MapImpl1");
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = "D:\" name ".class";
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Files.copy(Paths.get(path), os);
byte[] bytes = os.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类文件未找到:" name, e);
}
}
}