java类加载机制和类加载器 发表于 2023-11-10 | 更新于 2024-11-07
| 阅读量:
什么是类加载机制 编译器将java源码编译为字节码后,虚拟机将字节码读入内存进行解析、运行的过程,称之为java虚拟机的类加载机制。 类加载机制中,使用类加载器完成类的加载过程。 类的生命周期在上文中详细描述过,这里不赘述。详见Java类加载过程详解
什么是类加载器? 通过一个类全限定名称来获取其二进制文件(.class)流的工具,被称为类加载器(classloader)
类加载器的分类 java支持如下四种类加载器
启动类/引导类加载器 Bootstrap ClassLoader
由C/C++实现,嵌套在JVM内部,java程序无法直接操作此类。通过它来加载java核心类库。如JAVA_HOME/jre/lib/rt.jar、resources.jar、sun.boot.class.path
路径下的包,为jvm运行提供所需要的包。 它并非继承自java.lang.ClassLoader
,没有父类加载器。它负责加载扩展类加载器
和应用程序类加载器
,并成为其父类加载器。 处于安全考虑,启动类只加载包名为java|javax|sun
开头的类。
扩展类加载器 Extension ClassLoader
由java语言编写,由sun.misc.Launcher$ExtClassLoader
实现,可以用java程序操作此类加载器。 它继承自java.lang.ClassLoader
,父类加载器为启动类加载器
。 从系统属性:java.ext.dirs
目录中加载类库, 从JDK安装目录:jre/lib/ext
目录下加载类库。 将包放在上述目录下,就会自动被加载。
应用程序类加载器 Application Classloader
由Java语言编写,由 sun.misc.Launcher$AppClassLoader
实现。 它继承自java.lang.ClassLoader
,父类加载器为启动类加载器 它负责加载环境变量classpath
或者系统属性java.class.path
指定路径下的类库 它是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。可以通过ClassLoader#getSystemClassLoader()
获取并操作这个加载器。
自定义加载器
上述三种类加载器无法满足开发需求时,用户可以自定义类加载器
自定义类加载器,需要继承抽象类java.lang.ClassLoader
,如果期望打破双亲委派机制,则重写loadClass
方法;如果不想打破双亲委派机制,则重写findClass
方法即可。
ClassLoader源码 ClassLoader
的源码如下:
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 public abstract class ClassLoader { private static native void registerNatives () ; static { registerNatives(); } private final ClassLoader parent; protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException (name); } protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c == null ) { try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }
通过如下方式可以验证上述所说的类加载器的加载路径:
1 2 3 System.out.println("boot:" + System.getProperty("sun.boot.class.path" )); System.out.println("ext:" + System.getProperty("java.ext.dirs" )); System.out.println("app:" + System.getProperty("java.class.path" ));
如何自定义类加载器 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 import java.io.IOException;import java.io.InputStream;public class ConsumerClassLoaderDemo extends ClassLoader { public static void main (String[] args) throws Exception { ClassLoader myClassLoader = new ConsumerClassLoader (); Object obj = myClassLoader.loadClass("com.lbs0912.java.demo.ConsumerClassLoaderDemo" ).newInstance(); ClassLoader classLoader = obj.getClass().getClassLoader(); while (null != classLoader) { System.out.println(classLoader); classLoader = classLoader.getParent(); } } } class ConsumerClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String classFile = name.substring(name.lastIndexOf("." ) + 1 ) + ".class" ; InputStream in = getClass().getResourceAsStream(classFile); if (null == in) { return super .loadClass(name); } byte [] bytes = new byte [in.available()]; in.read(bytes); return defineClass(name, bytes, 0 , bytes.length); } catch (IOException e) { throw new ClassNotFoundException (name); } } }
控制台输出如下:
1 2 3 4 com.lbs0912.java.demo.ConsumerClassLoader@266474c2 sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@63947c6b
java9 类加载机制的改动 java 9中有如下三种类加载器:
如何获取类加载器 ClassLoader
是抽象类,除启动类加载器外,其余类加载器均继承自ClassLoader
。
1 2 3 4 5 6 7 8 clazz.getClassLoader() Thread.currentThread().getContextClassLoader() ClassLoader.getSystemClassLoader() DriverManager.getCallerClassLoader()
类加载机制的特点 类加载机制通过ClassLoader完成类的加载,java类的确定由类和加载它的类加载器共同决定。它有如下三个特点:
双亲委派: 将请求交给父类处理的任务委派方式
负责依赖: 如果一个类加载器在加载某个类时,发现此类依赖其他几个类或接口,也会去尝试加载这些依赖项
缓存加载: 为提升加载效率,一旦某个类被类加载器加载,会缓存这个加载结果。 【类加载到方法区和即时编译存放的异同?】
疑问
反向委派的过程只能使用线程上下文类加载器么?
为什么启动类加载器无法加载SPI
的实现类?
引用 1. jvm类加载器,类加载机制详解,看这一篇就够了 2. 沙箱机制 3. 双亲委派机制 4. 如何打破双亲委派 5. 详解tomcat打破双亲委派