Java类加载机制详解 | 京东云技术团队

一.类加载器及双亲委派机制

类加载器加载类备注
启动类加载器(Bootstrap ClassLoader)JAVA_HOME/jre/lib无上级,无法直接访问 由jvm加载
拓展类加载器(Extension ClassLoader)JAVA_HOME/jre/lib/ext父加载器为 Bootstrap,显示为 null 。该类由Bootstrap加载
应用类加载器(Application ClassLoader)classpath父加载器上级为 Extension,该类由Bootstrap加载
自定义类加载器自定义路径父加载器为 Application,该类由Application ClassLoader加载

1.类加载器继承结构

2. 类加载器的核心方法

方法名说明
getParent()返回该类加载器的父类加载器
findClass(String name)查找名字为name的类,返回的结果是java.lang.Class类的实例
loadClass(String name)加载名为name的类,返回java.lang.Class类的实例
defineClass(String name,byte[] b,int off,int len)根据字节数组b中的数据转化成Java类,返回的结果是java.lang.Class类的实例

3. Launcher类源码解析

1public class Launcher { 2 private static URLStreamHandlerFactory factory = new Factory(); 3 private static Launcher launcher = new Launcher(); 4 // 启动类加载器加载路径 5 private static String bootClassPath = 6 System.getProperty("sun.boot.class.path"); 7 8 public static Launcher getLauncher() { 9 return launcher; 10 } 11 12 private ClassLoader loader; 13 14 public Launcher() { 15 // Create the extension class loader 16 ClassLoader extcl; 17 try { 18 // 获取扩展类加载器 19 extcl = ExtClassLoader.getExtClassLoader(); 20 } catch (IOException e) { 21 throw new InternalError( 22 "Could not create extension class loader", e); 23 } 24 25 // Now create the class loader to use to launch the application 26 try { 27 // 获取应用类加载器 28 loader = AppClassLoader.getAppClassLoader(extcl); 29 } catch (IOException e) { 30 throw new InternalError( 31 "Could not create application class loader", e); 32 } 33 34 // Also set the context class loader for the primordial thread. 35 // 设置线程上下文类加载器为应用类加载器 36 Thread.currentThread().setContextClassLoader(loader); 37 } 38 39 40 /* 41 * The class loader used for loading installed extensions. 42 */ 43 static class ExtClassLoader extends URLClassLoader { 44 45 46 private static volatile ExtClassLoader instance = null; 47 48 /** 49 * create an ExtClassLoader. The ExtClassLoader is created 50 * within a context that limits which files it can read 51 */ 52 public static ExtClassLoader getExtClassLoader() throws IOException 53 { 54 if (instance == null) { 55 synchronized(ExtClassLoader.class) { 56 if (instance == null) { 57 instance = createExtClassLoader(); 58 } 59 } 60 } 61 return instance; 62 } 63 /** 64 * 获取加载路径 65 */ 66 private static File[] getExtDirs() { 67 // 扩展类加载器加载路径 68 String s = System.getProperty("java.ext.dirs"); 69 } 70 71 } 72 73 /** 74 * The class loader used for loading from java.class.path. 75 * runs in a restricted security context. 76 */ 77 static class AppClassLoader extends URLClassLoader { 78 79 public static ClassLoader getAppClassLoader(final ClassLoader extcl) 80 throws IOException 81 { 82 // 应用类加载器加载路径 83 final String s = System.getProperty("java.class.path"); 84 final File[] path = (s == null) ? new File[0] : getClassPath(s); 85 return AccessController.doPrivileged( 86 new PrivilegedAction<AppClassLoader>() { 87 public AppClassLoader run() { 88 URL[] urls = 89 (s == null) ? new URL[0] : pathToURLs(path); 90 return new AppClassLoader(urls, extcl); 91 } 92 }); 93 } 94} 95 96 97 98 99 100 101

4. ClassLoader类源码解析

1public abstract class ClassLoader { 2 3 protected Class<?> loadClass(String name, boolean resolve) 4 throws ClassNotFoundException 5 { 6 synchronized (getClassLoadingLock(name)) { 7 // First, check if the class has already been loaded 8 // 从系统缓存中获取 9 Class<?> c = findLoadedClass(name); 10 if (c == null) { 11 long t0 = System.nanoTime(); 12 try { 13 // 委托父加载器加载 14 if (parent != null) { 15 c = parent.loadClass(name, false); 16 } else { 17 c = findBootstrapClassOrNull(name); 18 } 19 } catch (ClassNotFoundException e) { 20 // ClassNotFoundException thrown if class not found 21 // from the non-null parent class loader 22 } 23 24 if (c == null) { 25 // If still not found, then invoke findClass in order 26 // to find the class. 27 long t1 = System.nanoTime(); 28 // 自己加载,从指定路径 29 c = findClass(name); 30 31 // this is the defining class loader; record the stats 32 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 33 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 34 sun.misc.PerfCounter.getFindClasses().increment(); 35 } 36 } 37 if (resolve) { 38 resolveClass(c); 39 } 40 return c; 41 } 42 } 43 44 // 自定义类加载器需要重写该方法 45 protected Class<?> findClass(String name) throws ClassNotFoundException { 46 throw new ClassNotFoundException(name); 47 } 48 49} 50 51 52 53 54 55

5. 双亲委派机制优缺点

优点:

1、保证安全性,层级关系代表优先级,也就是所有类的加载,优先给启动类加载器,这样就保证了核心类库类

2、避免类的重复加载,如果父类加载器加载过了,子类加载器就没有必要再去加载了,确保一个类的全局唯一性

缺点:

检查类是否加载的委派过程是单向的, 这个方式虽然从结构上说比较清晰,使各个 ClassLoader 的职责非常明确, 但是同时会带来一个问题, 即顶层的ClassLoader 无法访问底层的ClassLoader 所加载的类

通常情况下, 启动类加载器中的类为系统核心类, 包括一些重要的系统接口,而在应用类加载器中, 为应用类。 按照这种模式, 应用类访问系统类自然是没有问题, 但是系统类访问应用类就会出现问题。

二.spi接口及线程上下文类加载器

1.spi接口定义及线程上下文加载的作用

Java提供了很多核心接口的定义,这些接口被称为SPI接口。(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。

这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

1 类加载传导规则:JVM 会选择当前类的类加载器来加载所有该类的引用的类。例如我们定义了 TestATestB 两个类,TestA 会引用 TestB,只要我们使用自定义的类加载器加载 TestA,那么在运行时,当 TestA 调用到 TestB 的时候, 2TestB 也会被 JVM 使用 TestA 的类加载器加载。依此类推,只要是 TestA 及其引用类关联的所有 jar 包的类都会被自定义类加载器加载。通过这种方式,我们只要让模块的 main 方法类使用不同的类加载器加载,那么每个模块的都会使用 main 3方法类的类加载器加载的,这样就能让多个模块分别使用不同类加载器。这也是 OSGiSofaArk 能够实现类隔离的核心原理。 4 5 6 7 8 9

2. spi加载原理

当第三方实现者提供了服务接口的一种实现之后,在jar包的 META-INF/services/ 目录里同时创建一个以服务接口命名的文件,该文件就是实现该服务接口的实现类。而当外部程序装配这个模块的时候,就能通过该jar包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

JDK官方提供了一个查找服务实现者的工具类:java.util.ServiceLoader

1public final class ServiceLoader<S> 2 implements Iterable<S> 3{ 4 5 // 加载spi接口实现类配置文件固定路径 6 private static final String PREFIX = "META-INF/services/"; 7 /** 8 * Creates a new service loader for the given service type, using the 9 * current thread's {@linkplain java.lang.Thread#getContextClassLoader 10 * context class loader}. 11 * 12 * <p> An invocation of this convenience method of the form 13 * 14 * <blockquote><pre> 15 * ServiceLoader.load(<i>service</i>)</pre></blockquote> 16 * 17 * is equivalent to 18 * 19 * <blockquote><pre> 20 * ServiceLoader.load(<i>service</i>, 21 * Thread.currentThread().getContextClassLoader())</pre></blockquote> 22 * 23 * @param <S> the class of the service type 24 * 25 * @param service 26 * The interface or abstract class representing the service 27 * 28 * @return A new service loader 29 */ 30 public static <S> ServiceLoader<S> load(Class<S> service) { 31 // 线程上下文类加载器 32 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 33 return ServiceLoader.load(service, cl); 34 } 35} 36 37 38 39 40 41

3.示列代码

代码:

1public interface IShout { 2 void shout(); 3} 4 5public class Dog implements IShout { 6 @Override 7 public void shout() { 8 System.out.println("wang wang"); 9 } 10} 11public class Cat implements IShout { 12 @Override 13 public void shout() { 14 System.out.println("miao miao"); 15 } 16} 17 18public class Main { 19 public static void main(String[] args) { 20 ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class); 21 for (IShout s : shouts) { 22 s.shout(); 23 } 24 } 25} 26 27 28 29 30 31 32

配置:

4.MySQL驱动类加载

1// 加载Class到AppClassLoader(系统类加载器),然后注册驱动类 2//Class.forName("com.mysql.jdbc.Driver").newInstance(); 3String url = "jdbc:mysql://localhost:3306/testdb"; 4// 通过java库获取数据库连接 5Connection conn = java.sql.DriverManager.getConnection(url, "name", "password"); 6 7 8 9public class DriverManager { 10 static { 11 loadInitialDrivers(); 12 println("JDBC DriverManager initialized"); 13 } 14private static void loadInitialDrivers() { 15 。。。。。。。 16 AccessController.doPrivileged(new PrivilegedAction<Void>() { 17 public Void run() { 18 19 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); 20 Iterator<Driver> driversIterator = loadedDrivers.iterator(); 21 22 /* Load these drivers, so that they can be instantiated. 23 * It may be the case that the driver class may not be there 24 * i.e. there may be a packaged driver with the service class 25 * as implementation of java.sql.Driver but the actual class 26 * may be missing. In that case a java.util.ServiceConfigurationError 27 * will be thrown at runtime by the VM trying to locate 28 * and load the service. 29 * 30 * Adding a try catch block to catch those runtime errors 31 * if driver not available in classpath but it's 32 * packaged as service and that service is there in classpath. 33 */ 34 try{ 35 while(driversIterator.hasNext()) { 36 driversIterator.next(); 37 } 38 } catch(Throwable t) { 39 // Do nothing 40 } 41 return null; 42 } 43 }); 44 45 println("DriverManager.initialize: jdbc.drivers = " + drivers); 46 47 if (drivers == null || drivers.equals("")) { 48 return; 49 } 50 String[] driversList = drivers.split(":"); 51 println("number of Drivers:" + driversList.length); 52 for (String aDriver : driversList) { 53 try { 54 println("DriverManager.Initialize: loading " + aDriver); 55 Class.forName(aDriver, true, 56 ClassLoader.getSystemClassLoader()); 57 } catch (Exception ex) { 58 println("DriverManager.Initialize: load failed: " + ex); 59 } 60 } 61 } 62} 63 64 65 66 67 68

三.自定义动态类加载器

1.示例代码

1public class DynamicClassLoad extends ClassLoader{ 2 3 public static void main(String[] args) { 4 5 Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { 6 @Override 7 public void run() { 8 try { 9 DynamicClassLoad myClassLoad = new DynamicClassLoad(); 10 Class clazz = myClassLoad.findClass("/Users/wangzhaoqing1/Desktop/MyTest.class"); 11 Object obj = clazz.newInstance(); 12 Method sayHello = clazz.getDeclaredMethod("sayHello"); 13 sayHello.invoke(obj, null); 14 } catch (Throwable e) { 15 e.printStackTrace(); 16 } 17 } 18 }, 1, 2, TimeUnit.SECONDS); 19 } 20 21 22 @Override 23 protected Class<?> findClass(String name) throws ClassNotFoundException { 24 File file = new File(name); 25 try { 26 byte[] bytes = FileUtils.readFileToByteArray(file); 27 Class<?> c = this.defineClass(null, bytes, 0, bytes.length); 28 return c; 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } 32 return super.findClass(name); 33 } 34} 35 36// DynamicClassLoad启动后,修改本类重新编译 37public class MyTest { 38 39 public void sayHello(){ 40 System.out.println("hello wzq 6666666666"); 41 } 42} 43 44 45 46 47 48

作者:京东零售 王照清

来源:京东云开发者社区 转载请注明来源

点赞
收藏

评论区

加载中...

相关推荐

java类的加载与加载器

java代码在计算机中经历的三个阶段:1.Source源代码阶段(代码还是在硬盘上,并没有进入内存)  Student.java通过javac编译Student.class字节码文件2.类加载器ClassLoader将字节码文件加载进入内存,成为Class类对象(成员变量Field\\fields、构造方法Const

Java高级篇——深入浅出Java类加载机制

类加载器简单讲,类加载器ClassLoader的功能就是负责将class文件加载到jvm内存。类加载器分类从虚拟机层面讲分为两大类型的类加载器,一是BootstrapClassloader即启动类加载器(C实现),它是虚拟机的一部分,二是其他类型类加载器(JAVA实现),在虚拟机外部,并全部继

JVM 类加载机制详解

一.类的生命周期           !(https://static.oschina.net/uploads/img/201803/29115109_9IwG.jpg)  分析:1)加载loading:查找和导入class文件    不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比

Java类加载机制

启动(Bootstrap)类加载器启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C语言实现的,是虚拟机自身的一部分,它负责将<JAVA\_HOME/lib路径下的核心类库或Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机

Java类加载机制的理解

算上大学,尽管接触Java已经有4年时间并对基本的API算得上熟练应用,但是依旧觉得自己对于Java的特性依然是一知半解。要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机制和JVM字节码这样的核心特性。今天我将记录一下我在新的学习路程中对Java类加载机制的理解。1.类加载机制类加载是一个将类合并到正在运

JVM(四)JVM的双亲委派模型

1、两种不同的类加载器  从JAVA虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),这个类加载器使用C语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java,lang.ClassLoader。