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类源码解析
public class Launcher { private static URLStreamHandlerFactory factory = new Factory(); private static Launcher launcher = new Launcher(); // 启动类加载器加载路径 private static String bootClassPath = System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() { return launcher; } private ClassLoader loader; public Launcher() { // Create the extension class loader ClassLoader extcl; try { // 获取扩展类加载器 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } // Now create the class loader to use to launch the application try { // 获取应用类加载器 loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } // Also set the context class loader for the primordial thread. // 设置线程上下文类加载器为应用类加载器 Thread.currentThread().setContextClassLoader(loader); } /* * The class loader used for loading installed extensions. */ static class ExtClassLoader extends URLClassLoader { private static volatile ExtClassLoader instance = null; /** * create an ExtClassLoader. The ExtClassLoader is created * within a context that limits which files it can read */ public static ExtClassLoader getExtClassLoader() throws IOException { if (instance == null) { synchronized(ExtClassLoader.class) { if (instance == null) { instance = createExtClassLoader(); } } } return instance; } /** * 获取加载路径 */ private static File[] getExtDirs() { // 扩展类加载器加载路径 String s = System.getProperty("java.ext.dirs"); } } /** * The class loader used for loading from java.class.path. * runs in a restricted security context. */ static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException { // 应用类加载器加载路径 final String s = System.getProperty("java.class.path"); final File[] path = (s == null) ? new File[0] : getClassPath(s); return AccessController.doPrivileged( new PrivilegedAction<AppClassLoader>() { public AppClassLoader run() { URL[] urls = (s == null) ? new URL[0] : pathToURLs(path); return new AppClassLoader(urls, extcl); } }); } }
4. ClassLoader类源码解析
public abstract class ClassLoader { 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 { 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); // 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; } } // 自定义类加载器需要重写该方法 protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } }
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来加载类。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。
类加载传导规则:JVM 会选择当前类的类加载器来加载所有该类的引用的类。例如我们定义了 TestA 和 TestB 两个类,TestA 会引用 TestB,只要我们使用自定义的类加载器加载 TestA,那么在运行时,当 TestA 调用到 TestB 的时候, TestB 也会被 JVM 使用 TestA 的类加载器加载。依此类推,只要是 TestA 及其引用类关联的所有 jar 包的类都会被自定义类加载器加载。通过这种方式,我们只要让模块的 main 方法类使用不同的类加载器加载,那么每个模块的都会使用 main 方法类的类加载器加载的,这样就能让多个模块分别使用不同类加载器。这也是 OSGi 和 SofaArk 能够实现类隔离的核心原理。
2. spi加载原理
当第三方实现者提供了服务接口的一种实现之后,在jar包的 META-INF/services/ 目录里同时创建一个以服务接口命名的文件,该文件就是实现该服务接口的实现类。而当外部程序装配这个模块的时候,就能通过该jar包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
JDK官方提供了一个查找服务实现者的工具类:java.util.ServiceLoader
public final class ServiceLoader<S> implements Iterable<S> { // 加载spi接口实现类配置文件固定路径 private static final String PREFIX = "META-INF/services/"; /** * Creates a new service loader for the given service type, using the * current thread's {@linkplain java.lang.Thread#getContextClassLoader * context class loader}. * * <p> An invocation of this convenience method of the form * * <blockquote><pre> * ServiceLoader.load(<i>service</i>)</pre></blockquote> * * is equivalent to * * <blockquote><pre> * ServiceLoader.load(<i>service</i>, * Thread.currentThread().getContextClassLoader())</pre></blockquote> * * @param <S> the class of the service type * * @param service * The interface or abstract class representing the service * * @return A new service loader */ public static <S> ServiceLoader<S> load(Class<S> service) { // 线程上下文类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } }
3.示列代码
代码:
public interface IShout { void shout(); } public class Dog implements IShout { @Override public void shout() { System.out.println("wang wang"); } } public class Cat implements IShout { @Override public void shout() { System.out.println("miao miao"); } } public class Main { public static void main(String[] args) { ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class); for (IShout s : shouts) { s.shout(); } } }
配置:
4.MySQL驱动类加载
// 加载Class到AppClassLoader(系统类加载器),然后注册驱动类 //Class.forName("com.mysql.jdbc.Driver").newInstance(); String url = "jdbc:mysql://localhost:3306/testdb"; // 通过java库获取数据库连接 Connection conn = java.sql.DriverManager.getConnection(url, "name", "password"); public class DriverManager { static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { 。。。。。。。 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); /* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but it's * packaged as service and that service is there in classpath. */ try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } 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); } } } }
三.自定义动态类加载器
1.示例代码
public class DynamicClassLoad extends ClassLoader{ public static void main(String[] args) { Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() { @Override public void run() { try { DynamicClassLoad myClassLoad = new DynamicClassLoad(); Class clazz = myClassLoad.findClass("/Users/wangzhaoqing1/Desktop/MyTest.class"); Object obj = clazz.newInstance(); Method sayHello = clazz.getDeclaredMethod("sayHello"); sayHello.invoke(obj, null); } catch (Throwable e) { e.printStackTrace(); } } }, 1, 2, TimeUnit.SECONDS); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { File file = new File(name); try { byte[] bytes = FileUtils.readFileToByteArray(file); Class<?> c = this.defineClass(null, bytes, 0, bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } } // DynamicClassLoad启动后,修改本类重新编译 public class MyTest { public void sayHello(){ System.out.println("hello wzq 6666666666"); } }
作者:京东零售 王照清
来源:京东云开发者社区 转载请注明来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
LLM在text2sql上的应用 | 京东云技术团队
一、前言: 目前,大模型的一个热门应用方向text2sql它可以帮助用户快速生成想要查询的SQL语句。那对于用户来说,大部分简单的sql都是正确的,但对于一些复杂逻辑来说,需要用户在产出SQL的基础上进行简单修改,Text2SQL应用主要还是帮助用户去解决开发时间,减少开发成本。 Text to SQL: 简称Text2SQl,是将自然语言文本(Text)转换成结构化查询语言SQL的过程,属于自然语言处理-语义分析(Semantic Parsing)领域中的子任务。 它的目的可以简单概括为:“打破人与结构化数据之间的壁垒”,即普通用户可以通过自然语言描述完成复杂数据库的查询工作,得到想要的结果。 二、背景应用: 目前大家对T2S的做法大致分为两种, 一种是用现有的大模型来直接生成,例如ChatGPT、GPT-4模型,但是对于一些公司来说,数据是属于保密资产,这种方式相当于将自己公司的数据信息透漏给大模型,属于数据泄露行为; 另一种方式是利用开源的大模型做finetune,比如chatglm2-6b来做微调,这个也是目前我们在做的,同时开源的数据集也有很多,简单罗列如下: 数据集 数据集...
- 下一篇
SpringBoot自动配置原理解析 | 京东物流技术团队
1: 什么是SpringBoot自动配置 首先介绍一下什么是SpringBoot,SpringBoost是基于Spring框架开发出来的功能更强大的Java程序开发框架,其最主要的特点是:能使程序开发者快速搭建一套开发环境。SpringBoot能将主流的开发框架(例如SpringMVC,Dubbo,Mybatis,Redis等),做到像Maven导入Jar包一样的简洁快速,做到开箱即用。其中最关键的技术就是SpringBoot定制的各种Starter,通Maven引入Starter就能快速搭建开发环境。 2: SpringBoot Starter自动装配案例 在以前单独使用SpringMVC Web编程框架时,我们需要单独配置DispatcherServlet和Tomcat,使用SpringBoot之后,我们只需要引入SpringBoot-Starter-Web就能直接开始编写Controller等Web相关的代码,这就是SpringBoot为们提供的开箱即用的便捷能力,下面就以SpringBoot-Starter-Web来说明SpringBoot自动配置的关键原理 3: Spring...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- MySQL8.0.19开启GTID主从同步CentOS8
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装Docker,最新的服务器搭配容器使用
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Linux系统CentOS6、CentOS7手动修改IP地址