jvm技术专题针对于加载器与双亲委派机制分析和研究指南「入门篇」(代码片段)

浩宇の天尚 浩宇の天尚     2022-11-11     556

关键词:

任何足够先进的科技,都与魔法无异

加载器与双亲委派机制

  • 类加载器是怎么被创建出来的?
  • 什么是双亲委派机制?为什么要有这种机制?
  • Class实例和类加载器究竟是在Java Heap中,还是在方法区中?

类加载器: 可以实现通过一个类的全限定名称来获取描述此类的二进制字节流。实现这个动作的代码模块成为”类加载器“。

   通过自定义类加载器可以实现各种有趣而强大的功能更:OSGi,热部署,代码加密等。

类加载器的加载流程

启动类加载器

1.系统启动的时候,首先会通过由C++实现的启动类加载器,加载<JAVA_HOME>/lib目录下面的jar包,或者被-Xbootclasspath参数指定的路径并且被虚拟机识别的文件名的jar包。把相关Class加载到方法区中

2.这一步会加载关键的一个类:sun.misc.Launcher。这个类包含了两个静态内部类:

  • ExtClassLoader:扩展类加载器内部类,下面会讲;
  • AppClassLoader:应用程序类加载器内部类,下面会讲;

在加载到Launcher类完成后,会对该类进行初始化,初始化的过程中,会创建 ExtClassLoaderAppClassLoader,源码如下:

public Launcher() 
    ExtClassLoader extClassLoader;
    try 
      extClassLoader = ExtClassLoader.getExtClassLoader();
     catch (IOException iOException) 
      throw new InternalError("Could not create extension class loader", iOException);
    
    try 
      this.loader = AppClassLoader.getAppClassLoader(extClassLoader);
     catch (IOException iOException) 
      throw new InternalError("Could not create application class loader", iOException);
    
    Thread.currentThread().setContextClassLoader(this.loader);
    ...

由于启动类加载器是由C++实现的,所以在Java代码里面是访问不到启动类加载器的,如果尝试通过String.class.getClassLoader()获取启动类的引用,会返回null;

问题:

  • 启动类加载器,扩展类加载器和应用类加载器都是由谁加载的?
    • 启动类加载器是JVM的内部实现,在JVM申请好内存之后,由JVM创建这个启动类加载器
    • 扩展类加载器和应用程序类加载器是由启动类加载器加载进来的;

说说以下代码输出什么:

 public static void main(String[] args) 
     System.out.println("加载当前类的加载器:" + TestClassLoader.class.getClassLoader());
        System.out.println("加载应用程序类加载器的加载器"
                         + TestClassLoader.class.getClassLoader().getClass().getClassLoader());
        System.out.println("String类的启动类加载器" + String.class.getClassLoader());
   

类加载器的双亲委派机制

双亲委派机制原理

双亲委派模型在JDK1.2之后被引入,并广泛使用,这不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。我们也可以覆盖对应的方式,实现自己的加载模型。

类加载器的双亲委派机制如下:

  • 一个类加载器收到了类加载请求,不会自己立刻尝试加载类,而是把请求委托给父加载器去完成,每一层都是如此,所有的家在请求最终都传递到最顶层的类加载器进行处理;
  • 如果父加载器不存在了,那么尝试判断有没有被启动类加载器加载;
  • 如果的确没有被加载,则再自己尝试加载。

为什么要有这么复杂的双亲委派机制?

如果没有这种机制,我们就可以篡改启动类加载器中需要的类了,如,修自己编写一个java.lang.Object用自己的类加载器进行加载,系统中就会存在多个Object类,这样Java类型体系最基本的行为也就无法保证了。

双亲委派机制处理流程

 JDK中默认的双亲委派处理流程是怎么的呢?接下来我们看看代码,以下是java.lang.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 
                        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;
        
    

转成流程图,即是:

如山图所以,总是先会尝试让父类加载器先加载,其次判断启动类加载器是否已经加载了,最后才尝试从当前类加载器加载。转换为更清晰的模型如下:

双亲委派模型具有以下特点:

  • 可见性原则:
    • 应用类加载器可以读取到扩展类加载器和启动类加载器加载进来的Class的;
    • 扩展类加载器可以读取到由启动类加载器加载进来的Class的;
  • 唯一性:
    • 类是唯一的,没有重复的类;

类加载器和Class实例的题外话


启动类加载器,扩展类加载器,应用程序类加载器,他们分别管理者各自方法区里的一个区块。


方法区里面主要存储的是类的运行时数据结构,这个类的在方法区中的各种数据结构信息通过类的Class实例进行访问。

如下图:

  • 类加载器:负责管理各个存储区的类信息,如加载和卸载类信息;
  • Class实例:负责对接外部需求,如果外部有人想查看里面的类信息,则需要通过Class实例来获取;

另外,方法区里面,启动类加载器类信息对扩展两类加载器类信息可见,而前面两者的类信息又对应用程序类加载器类信息可见。

其他非双亲委派模型的案例

JDK 1.0遗留问题

JDK1.0已经存在了ClassLoader类,但是当时还没有双亲委派机制,用户为了自定义类加载器,需要重新loadClass()方法,而我们知道,在JDK1.2以后,loadClass里面就是双亲委派机制的实现代码,此时,要实现自定义类加载器,需要重新findClass()类即可。如果重新了loadClass()方法,也就意味着不再遵循双亲委派模型了。

线程上下文类加载器

Tomcat中的类加载器

我们知道Tomcat目录结构中有以下目录:

  • /common/: 该目录下的类库可被Tomcat和所有的WebApp共同使用;

  • /server/: 该目录下的类库可被Tomcat使用,但对所有的WebApp不可见;

  • /shared/: 该目录下的类库可被所有的WebApp共同使用,但对Tomcat自己不可见;

  • 另外Web应用程序还有自身的类库,放在/WebApp/WEB-INF目录中:这里面的类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

  • 为了实现以上各个目录的类库可见性效果,Tomat提供了如下的自定义类加载器:

现在如下场景:

我们发现Tomcat下面有若干个webapp,每个webapp都用到了spring,于是我们把spring的jar包放到了shared目录中。


于是问题出现了:由于spring的jar包是由Shared类加载器加载的,假设我们要使用SpringContext的getBean方法,获取webapp中的Bean,如果是按照双亲委派模型,就会有问题了,因为webapp中的Java类是对SharedClassLoader不可见的:

Spring中的线程上下文类加载器

为了解决这个问题,Spring使用了线程上下文类加载器,即从ThreadLocal中获取到当前线程的上下文类加载器,来加载所有的类库和类。

Spring中的bean类加载器

ApplicationContext中有一个beanClassLoader字段,这个是bean的类加载器,在prepareBeanFactory()方法中做了初始化:

beanFactory.setBeanClassLoader(getClassLoader());

getClassLoader方法如下:

	@Override
	@Nullable
	public ClassLoader getClassLoader() 
		return (this.classLoader != null ? this.classLoader : 
				    ClassUtils.getDefaultClassLoader());
	

ClassUtils.getDefaultClassLoader()方法:

	@Nullable
	public static ClassLoader getDefaultClassLoader() 
		ClassLoader cl = null;
		try 
			cl = Thread.currentThread().getContextClassLoader();
		
		catch (Throwable ex) 
			// Cannot access thread context ClassLoader - falling back...
		
		if (cl == null) 
			// No thread context class loader -> use class loader of this class.
			cl = ClassUtils.class.getClassLoader();
			if (cl == null) 
				// getClassLoader() returning null indicates the bootstrap ClassLoader
				try 
					cl = ClassLoader.getSystemClassLoader();
				
				catch (Throwable ex) 
					// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
				
			
		
		return cl;
	

可以发现,这里最终取了当前线程上下文中的ClassLoader。

加载Bean

来看看Spring加载Class的代码。这里我们直接找到实例化Singletons的方法跟进去找需要关注的代码:

我们发现在加载Bean Class的时候调用了这个方法:

AbstractBeanFactory:

ClassLoader beanClassLoader = getBeanClassLoader();

也就是用到了ApplicationContext中的beanClassLoader,线程上下文类加载器来加载Bean Class实例。

总结

Spring作为一个第三方类库,可能被任何的ClassLoader加载,所以最灵活的方式是直接使用上下文类加载器。

jvm专题四:类加载子系统双亲委派机制

2.双亲委派机制 2.1双亲委派机制工作原理 2.1.1原理 Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存,生成class对象。 加载某个类的class文件时,Java虚拟机... 查看详情

jvm类加载器与双亲委派模型

1.类加载器我们知道,虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?这时候就需要双亲委派... 查看详情

jvm类加载器与双亲委派模型

1.类加载器我们知道,虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?这时候就需要双亲委派... 查看详情

jvm技术专题针对于java类加载器系统研究指南「入门篇」(代码片段)

任何足够先进的科技,都与魔法无异前提1.针对于JVM(Java虚拟机)相关的类加载器子系统部分,主要是虚拟机赐予我们最贴近底层控制程序执行的部分,JVM采用的双亲委托的类加载机制,进行加载相关的Cla... 查看详情

jvm类加载器与双亲委派模型(代码片段)

(7)URLClassLoader类前面说到,ClassLoader这个顶级父类只是定义好了双亲委派模型的工作机制;但是ClassLoader是个抽象类,无法直接创建对象,所以需要由继承它的子类完成创建对象的任务。子类需要自己实... 查看详情

jvm类加载机制详解类加载器与双亲委派模型

...://www.cnblogs.com/panchanggui/p/9952524.html  在上一篇JVM类加载机制详解(一)JVM类加载过程中说到,类加载机制的第一个阶段加载做的工作有:1、通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流(Class文件... 查看详情

深入理解jvm类加载器与双亲委派模型

...rticle/details/51332866,http://www.cnblogs.com/lanxuezaipiao/p/4138511.html加载类的开放性类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因。在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义 查看详情

jvm类加载器:双亲委派机制沙箱安全机制(代码片段)

...文件,编译完成后,需要加载到内存。    我们针对不同的class文件,会相应用到不同 查看详情

jvm技术专题针对于class字节码的文件分析和研究指南「进阶篇」(代码片段)

任何足够先进的科技,都与魔法无异承接上篇:【JVM技术专题】针对于Class字节码的文件分析和研究指南「入门篇」字段表集合字段表集合用于描述接口或者类中声明的变量,这里的数据为:0000。fields_count字段计数器fi... 查看详情

[五]类加载机制双亲委派机制底层代码实现原理源码分析java类加载双亲委派机制是如何实现的

...nbsp;Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下 我们先从启动类说起 有一个Launcher类  sun.misc.Launcher; &nbs... 查看详情

jvm技术专题针对于class字节码的文件分析和研究指南「入门篇」(代码片段)

任何足够先进的科技,都与魔法无异背景介绍Java源代码被编译为Class文件之后,Class文件结构是JVM加载Class,实例化对象,和进行方法调用的重要依据.每个Class文件都是由8字节为单位的字节流组成,所有的16位... 查看详情

jvm类加载器与双亲委派模型(代码片段)

(1)动态加载   类加载器是Java语言的一个创新,也是Java语言流行的重要原因之一。它使得Java类可以被动态加载到Java虚拟机中并执行。类加载器从JDK1.0就出现了,最初是为了满足JavaApplet的需要而开发出来的。J... 查看详情

双亲委派机制(代码片段)

...节码文件。然后由我们得ClassLoader负责将这些class文件给加载到JVM中去执行。JVM中提供了三层的ClassLoader:BootstrapclassLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。ExtClassLoader:主要负责加载jre/lib/ext目... 查看详情

jvm技术专题重塑你对类加载机制的认识「分析篇」(代码片段)

📕每日一句极限就是为了超越而存在的,如何挑战自己的极限,只能苦练!📕为什么又要写类加载器?为什么有些一篇相关与对类加载器的文章?个人觉得之前的侧重点在于ClassLoader本身,以及双... 查看详情

jvm--双亲委派机制

要了解双亲委派机制得先了解个概念:类加载器:“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块成为... 查看详情

jvm加载类的过程,双亲委派机制中的方法

JVM加载类的过程:1)JVM中类的整个生命周期:  加载=》验证=》准备=》解析=》初始化=》使用=》卸载      1.1、加载类的加载阶段,主要是获取定义此类的二进制字节流,并将这个字节流所代表的静态存储结... 查看详情

jdbc是如何打破双亲委派模式的(代码片段)

...的classpath下,驱动类会自动被加载。这种自动加载的技术被称为SPI(ServicePrividerInterface)[3],SPI可以简单理解为:为了解耦,从配置里获取某个接口的具体实现类。各个数据库也都更新支持 查看详情

双亲委派机制

1.1定义JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器... 查看详情