类加载阶段
加载
- 将类的字节码载入到方法区中,内部采用C++的instanceKlass描述java类,他的重要field有:
- java_mirror即java的类镜像,例如对String来说,就是String.class,作用是吧klass暴露给java使用
- _super即父类
- _field即成员变量
- _method即方法
- constants即常量池
- _class_loader即类加载器
- vtable虚方法表
- _itable接口方法表
- 如果这个类还有父类没有加载,先加载父类
- 加载和链接可能是交替运行的
注意
链接
验证:验证类是否符合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时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class Load3 { static { System.out.println("main init"); }
public static void main(String[] args) throws ClassNotFoundException { System.out.println(B.b); System.out.println(B.class); System.out.println(new B[0]); ClassLoader c1 = Thread.currentThread().getContextClassLoader(); c1.loadClass("cn.itcast.load.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); 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"); } }
|
懒加载且线程安全的单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class Load9 { public static void main(String[] args) { Singleton.test(); 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
1 2 3
| D:\JavaProject\learn\classload\out\production\classload>jar -cvf my2.jar com\learn\G.class 已添加清单 正在添加: com/learn/LoaderTest.class(输入 = 872) (输出 = 486)(压缩了 44%)
|
1 2 3 4 5 6 7
| public class Loader {
public static void main(String[] args) throws ClassNotFoundException { Class<?> aClass = Class.forName("com.learn.G"); System.out.println(aClass.getClassLoader()); } }
|
双亲委派模式
所谓的双亲委派,就是指调用类加载器的loadClass方法时,查找类的规则
ClassLoader.loadClass()方法源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { 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) { }
if (c == null) { long t1 = System.nanoTime(); 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驱动,不知道你注意到没有,不写
1
| Class.forName("com,mysql.jdbc.Driver")
|
也是可以让com.mysql.jdbc.Driver正确加载的,你知道是怎么做的吗?
让我们追踪一下源码:
1 2 3 4 5 6 7 8 9 10 11
| public class DriverManager {
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
|
先不看别的,看看DriverManager的类加载器
1
| System.out.println(DriverManager.class.getClassLoader());
|
打印null,表示它的类加载器是Bootstrap ClassLoader,会到JAVA_HOME/jre/lib下搜索类,但JAVA_HOME/jre/lib下显然没有mysql-connertor-java.jar包,这样问题就来了,在DriverManager的静态代码块中,怎么能正确加载com.mysql.jdbc.Driver呢?
继续看loadInitialDrivers()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| 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; }
AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { } return null; } });
println("DriverManager.initialize: jdbc.drivers = " + 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); 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包下,以接口全限定名为文件名,文件内容就是实现类名称
如上图,这样就可以使用
1 2 3 4 5
| ServiceLoader<Driver> allImpls = ServiceLoader.load(Driver.class); Iterator<Driver> iter = allImpls.iterator(); while (iter.hasNext()) { iter.next(); }
|
获取到com.mysql.jdbc.Driver和FabricMySQLDriver实现类,体现的是【面向接口编程+解耦】的思想
接着看ServiceLoader.load方法:
1 2 3 4 5
| public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
|
线程上下文类加载器是当前线程使用的类加载器,默认就是应用程序类加载器,它内部有是由Class.forName调用了线程上下文类加载器完成类加载,具体代码在ServiceLoader的内部类LazyIterator中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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(); }
|
自定义类加载器
问问自己,什么时候需要自定义类加载器
- 想加载非classpath随意路径中的类文件
- 都是通过接口来使用实现,希望解耦时,常用在框架设计
- 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于tomcat容器
步骤
- 继承ClassLoader父类
- 要遵从双亲委派机制,重写findClass方法
a. 注意不是重写loadClass方法,否则不会走双亲委派机制
- 读取类文件的字节码
- 调用父类的defineClass方法来加载类
- 使用者调用该类加载器的loadClass方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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); } } }
|