jvm类加载
类加载流程
加载:
- 通过一个类的全限定名来定义此类的二进制字节流
- 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证:
- 验证class字节流,确保class文件中的字节流包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当做代码运行后不会危害虚拟机自身的安全;
准备:
- 正式为类中定义的变量(即静态变量,static修饰的变量)分配内存并设置类变量的初始值的阶段;
解析:
- 解析阶段是java虚拟机将常量池内的符号引用替换为直接引用的过程;
类加载器
启动(引导)类加载器(Bootstrap Class Loader): 负责加载jre的lib目录下的核心类库,比如rt.jar,charsets.jar等
扩展类加载器(Extension Class Loader): 负责加载jre的lib目录下的ext扩展目录中的jar类包;
应用程序类加载器(Application ClassLoader): 负责加载Classpath路径下的类包;主要加载自己写的类
自定义加载器: 加载自定义路径下的类包;
示例:
public class TestJDKClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassLoader.getParent();
System.out.println("the appClassLoader:"+appClassLoader);
System.out.println("the extClassLoader:"+extClassLoader);
System.out.println("the bootstrapLoader:"+bootstrapLoader);
System.out.println();
System.out.println("bootstrapLoader 加载以下文件:");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}
System.out.println();
System.out.println("extClassLoader 加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the appClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
the extClassLoader:sun.misc.Launcher$ExtClassLoader@7ea987ac
the bootstrapLoader:null
bootstrapLoader 加载以下文件:
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_181/jre/classes
extClassLoader 加载以下文件:
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
appClassLoader加载以下文件:
....部分省略
C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;
G:\IdeaProjects\java-study\class-loader\target\classes;
D:\Users\hello\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\193.6911.18\lib\idea_rt.jar
- appClassLoader的parent是extClassLoader;extClassLoader的parent为null;
- 各个类加载器加载的jar很多都是重复的;那么如何避免重复加载的呢?
双亲委派模型
下面是ClassLoader的loadClass方法,双亲委派模型就是通过下面的代码来实现的;
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 { // 使用BootstrapClassLoader来加载
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); // 调用自己的findClass来查找;这个方法为空,自定义加载器重写;
// 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;
}
}
为什么设计双亲委派模型
- 沙箱安全机制,Java核心类库交由指定类加载器加载,防止核心类库被篡改;
- 避免重复加载,当父加载器已经加载了,就不用在加载了;
自定义类加载器
从上面的流程图可以看出,自定义类加载器主要有两步;
- 继承ClassLoader;
- 重写findClass方法;
下面按照上面的步骤自定义一个类加载器:
ClassLoader classLoader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = name.replaceAll("\\.", "/");
try (FileInputStream is = new FileInputStream("H:/test/" + path + ".class")) {
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
这就是个简单的类加载器了;注意双亲委派模型的流程,这里需要注意的是最后是否会调用到findClass方法;
破坏双亲委派模型
过双亲委派模型并不是一个具有强制性约束的模型,而是 Java 设计者推荐给开发者们的类加载器实现方式。在 Java 的世界中大部分的类加载器都遵循这个模型,但也有例外的情况;
这里只是提一下,具体需要看《深入Java虚拟机jvm高级特性与最佳实践》或者网上的博客;