您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java 类加载机制详解

时间:2023-10-25 15:05:13  来源:京东云开发者  作者:

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

 

Java 类加载机制详解

Java 类加载机制详解

1. 类加载器继承结构

Java 类加载机制详解

Java 类加载机制详解

2. 类加载器的核心方法

Java 类加载机制详解

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();

}

}

}

配置:

Java 类加载机制详解

4.MySQL 驱动类加载

Java 类加载机制详解

// 加载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.ServiceConfigurati

* 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");

}

}

 

作者:京东零售 王照清
来源:京东云开发者社区 转载请注明来源


Tags:Java 类   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Java 类加载机制详解
一。类加载器及双亲委派机制 1. 类加载器继承结构2. 类加载器的核心方法3. Launcher 类源码解析public class Launcher {private static URLStreamHandlerFactory factory =...【详细内容】
2023-10-25  Search: Java 类  点击:(97)  评论:(0)  加入收藏
Java 类的各种成员初始化顺序
Java类的各种成员初始化顺序如:父子类继承时的静态代码块,普通代码块,静态方法,构造方法,等先后顺序class B extends A , 然后 A 类也就是父类里面有静态代码块,普通代码块,静...【详细内容】
2022-06-09  Search: Java 类  点击:(306)  评论:(0)  加入收藏
你知道 Java 类是如何被加载的吗?
前言最近给一个非 Java 方向的朋友讲了下双亲委派模型,朋友让我写篇文章深度研究下JVM 的 ClassLoader,我确实也好久没写 JVM 相关的文章了,有点手痒痒,涂了皮炎平也抑制不住的...【详细内容】
2019-11-19  Search: Java 类  点击:(495)  评论:(0)  加入收藏
Java 类在 Tomcat 中是如何加载的?
在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加载。 比如JVM启动时,会通过不同的类加载器加载不同的类。当用户在自己的代码中,需要某些额外的类时,再通过加载机制加载到JVM中,并且存放一段时间,便...【详细内容】
2019-10-11  Search: Java 类  点击:(641)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(14)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(19)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(24)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(55)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(68)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(72)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(88)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(105)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(95)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(74)  评论:(0)  加入收藏
站内最新
站内热门
站内头条