精华内容
下载资源
问答
  • Java的类加载机制:加载,连接,初始化。JAVA类加载器: Bootstrap ClassLoader : 根类加载器, Extension ClassLoader: 扩展类加载器, System ClassLoader : 系统类加载器, Java反射
  • 详解JAVA类加载机制

    2020-08-19 02:46:07
    主要介绍了JAVA类加载机制的相关知识,文中代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
  • jvm 类加载机制 双亲委派模型 文档 jvm 类加载机制 双亲委派模型 文档
  • 本篇文章是对php自动加载机制进行了详细的分析介绍,需要的朋友参考下
  • 主要介绍了微信小程序加载机制及运行机制图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 今天小编就为大家分享一篇关于Linux内核设备驱动之Linux内核模块加载机制笔记整理,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • java类加载机制

    2018-10-31 13:40:11
    java 类加载机制,课程笔记。
  • jvm 系列 ( 一:java 类的加载机制加载机制的奥妙 1 什么是类的加载 类的加载指的是将类的 .class 文件中的二进制数据读入到存中将其放在运行时数据区 的方法区 然后在堆区创建一个 java.lang.Class 对象 用来...
  • Spring中ApplicationContext加载机制
  • Flink中的类加载机制

    千次阅读 2020-10-13 08:49:23
    Flink中的类加载机制Flink中的类加载机制Flink中的类加载配置项类图关系parent-first类加载child-first类加载如何处理类冲突呢 Flink中的类加载机制 Flink中的类加载配置项 Flink中关于类加载有以下配置选项 ...

    Flink中的类加载机制

    Flink中的类加载配置项

    Flink中关于类加载有以下配置选项

    配置项默认值说明
    classloader.resolve-order“child-first”类加载顺序。child-first优先从Flink任务(jar包)中加载类,parent-first优先从Flink集群加载类。
    classloader.parent-first-patterns.default“java.; scala.; org.apache.flink.; com.esotericsoftware.kryo; org.apache.hadoop.;javax.annotation.; org.slf4j;org.apache.log4j; org.apache.logging; org.apache.commons.logging; ch.qos.logback; org.xml; javax.xml; org.apache.xerces; org.w3c”优先从Flink集群加载的类,以分号分隔的类前缀匹配
    classloader.parent-first-patterns.additional(none)额外需要优先从Flink集群加载的类
    classloader.fail-on-metaspace-oom-errortrue如果尝试加载用户代码类时抛出“OutOfMemoryError:Metaspace”,则Flink JVM进程失败。

    类图关系

    在这里插入图片描述

    FlinkUserCodeClassLoader是一个继承了URLClassLoader的抽象类,主要是约束了其子类加载器在进行类加载的一些行为。

    /**
     * This class loader accepts a custom handler if an exception occurs in {@link #loadClass(String, boolean)}.
     */
    public abstract class FlinkUserCodeClassLoader extends URLClassLoader {
    	
      // 加载类出现异常时的handler,默认不作处理
      public static final Consumer<Throwable> NOOP_EXCEPTION_HANDLER = classLoadingException -> {};
    
      // 指定一个类加载异常处理handler
    	private final Consumer<Throwable> classLoadingExceptionHandler;
    
    	protected FlinkUserCodeClassLoader(URL[] urls, ClassLoader parent) {
    		this(urls, parent, NOOP_EXCEPTION_HANDLER);
    	}
    
    	protected FlinkUserCodeClassLoader(
    			URL[] urls,
    			ClassLoader parent,
    			Consumer<Throwable> classLoadingExceptionHandler) {
    		super(urls, parent);
    		this.classLoadingExceptionHandler = classLoadingExceptionHandler;
    	}
    
      /**
       * 实现FlinkUserCodeClassLoader的子类不需要重写loadClass方法,只需要重写loadClassWithoutExceptionHandling方法。
       * loadClassWithoutExceptionHandling只负责类加载的具体实现,
       * 加载类时出现的异常会被捕获并交由类加载异常处理handler进行处理,并再次向上抛
       *
       */
    	@Override
    	protected final Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    		try {
    			return loadClassWithoutExceptionHandling(name, resolve);
    		} catch (Throwable classLoadingException) {
    			classLoadingExceptionHandler.accept(classLoadingException);
    			throw classLoadingException;
    		}
    	}
    
    	/**
    	 * Same as {@link #loadClass(String, boolean)} but without exception handling.
    	 *
    	 * <p>Extending concrete class loaders should implement this instead of {@link #loadClass(String, boolean)}.
    	 */
    	protected Class<?> loadClassWithoutExceptionHandling(String name, boolean resolve) throws ClassNotFoundException {
    		return super.loadClass(name, resolve);
    	}
    }
    

    parent-first类加载

    parent-first的含义就是优先从Flink集群加载类,如果没有该类就从用户的jar包中加载类。

    ParentFirstClassLoader类加载器的实现特别简,说白了就是一个有FlinkUserCodeClassLoader特性的URLClassLoader,通过指定的URL加载类文件查找对应的类。

    /**
     * Regular URLClassLoader that first loads from the parent and only after that from the URLs.
     */
    static class ParentFirstClassLoader extends FlinkUserCodeClassLoader {
    
       ParentFirstClassLoader(URL[] urls, ClassLoader parent, Consumer<Throwable> classLoadingExceptionHandler) {
          super(urls, parent, classLoadingExceptionHandler);
       }
    }
    

    那么,ParentFirstClassLoader是如何保证同一个Flink的类,优先加载集群的而非用户jar包中的?其实这里是利用Java的类加载机制——双亲委派模型。

    在这里插入图片描述

    我们知道用户提交的Flink任务运行于TaskManager进程内,TaskManager运行时的类加载器是Application ClassLoader(用户程序运行时的默认类加载器),当我们使用自定义类加载器加载需要的类时,会自底向上逐一查找,如果ParentFirstClassLoader中有这个类就再次从Application ClassLoader中查找这个类,如果Application ClassLoader中也有这个类,就会继续从Extension ClassLoader中查找,直到父加载器加载不到这个类时,才会使用当前类加载器加载这个类。这样就巧妙的利用双亲委派模型实现了parent-first的类加载机制。

    child-first类加载

    接着我们来看child-first是如何打破双亲委派机制的。

    不少人应该都用过Tomcat,Tomcat中可以部署多个Java程序,每个Java程序都包含了各自不同版本的jar包,但是他们却能各自正常的工作。同样地,同一个Flink集群也需要运行各种各样的Flink任务,而且如果用户使用的某个jar和Flink集群的jar版本不一致,需要优先加载用户的jar怎么办?Flink的ChildFirstClassLoader类加载提供了优先从用户jar包加载类的机制。

    /**
     * A variant of the URLClassLoader that first loads from the URLs and only after that from the parent.
     *
     * <p>{@link #getResourceAsStream(String)} uses {@link #getResource(String)} internally so we
     * don't override that.
     */
    public final class ChildFirstClassLoader extends FlinkUserCodeClassLoader {
    
    	/**
    	 * 总是从父加载器加载的类,如以org.apache.flink开头的类等,这样所有任务都使用同一版本的link基础库
    	 * The classes that should always go through the parent ClassLoader. This is relevant
    	 * for Flink classes, for example, to avoid loading Flink classes that cross the
    	 * user-code/system-code barrier in the user-code ClassLoader.
    	 */
    	private final String[] alwaysParentFirstPatterns;
    
    	/** 构造方法省略 **/
    
    	@Override
    	protected synchronized Class<?> loadClassWithoutExceptionHandling(
    			String name,
    			boolean resolve) throws ClassNotFoundException {
    
        // 如果发现类已经加载过,就不再重复加载
    		// First, check if the class has already been loaded
    		Class<?> c = findLoadedClass(name);
    
    		if (c == null) {
          // 总是从父加载器加载的类交由父加载器加载,ChildFirstClassLoader跳过加载这些类
    			// check whether the class should go parent-first
    			for (String alwaysParentFirstPattern : alwaysParentFirstPatterns) {
    				if (name.startsWith(alwaysParentFirstPattern)) {
    					return super.loadClassWithoutExceptionHandling(name, resolve);
    				}
    			}
    
    			try {
            // 查找到类之后直接返回
    				// check the URLs
    				c = findClass(name);
    			} catch (ClassNotFoundException e) {
    				// let URLClassLoader do it, which will eventually call the parent
            // 如果当前类加载其中找不到这个类那只能交由父类加载器从Flink集群环境中查找了
    				c = super.loadClassWithoutExceptionHandling(name, resolve);
    			}
    		}
    
    		if (resolve) {
    			resolveClass(c);
    		}
    
    		return c;
    	}
    
      /** 下面两个方法都是优先从子类中获取资源文件 **/
    	@Override
    	public URL getResource(String name) {
    		...
    	}
    
    	@Override
    	public Enumeration<URL> getResources(String name) throws IOException {
    		...
    	}
    }
    

    这里alwaysParentFirstPatterns就是classloader.parent-first-patterns.defaultclassloader.parent-first-patterns.additional配置的类前缀集合,即这两个配置项中的类就算使用child-first类加载方式,也会从父加载器中加载,而非用户jar包中加载。这也是为什么Flink官网的例子中,flink相关的jar在pom引入的时候scope都采用了provided,既减少了用户jar包的大小,又能在集群上正常运行,最主要的是如果集群更新了版本,程序可以直接享受更新后的功能,而不用重新打包。

    类冲突处理

    上面介绍了Flink中的类加载机制,实际开发中可能会因为类加载出现ClassNotFound或者NoSuchMethodError和NoSuchFieldError等异常。这种情况可能是因为你的jar包中的版本和集群的版本不一致,优先加载了集群的class。在不改动集群默认配置的情况下,同时也防止对其他任务的影响,可以考虑对项目中引入的冲突类作shade。如com.typesafe的类发生冲突,maven项目加入如下配置:

    <plugin>
    				<groupId>org.apache.maven.plugins</groupId>
    				<artifactId>maven-shade-plugin</artifactId>
    				<executions>
    					<execution>
    						<phase>package</phase>
    						<goals>
    							<goal>shade</goal>
    						</goals>
    						<configuration>
    							
    							<relocations>
    								<relocation>
    									<pattern>com.typesafe</pattern>
    									<shadedPattern>com.mycompany.com.typesafe</shadedPattern>
    								</relocation>
    							</relocations>
    							
    						</configuration>
    					</execution>
    				</executions>
    			</plugin>
    

    项目打包后,就会自动把引入和源码中相关的com.typesafe开头的类修改为com.mycompany.com.typesafe,由于类的路径不同,类加载器在加载用户jar中的类时就不会与集群中的其他版本冲突。

    展开全文
  • 加载机制及反射

    2018-01-30 11:14:23
    加载机制及反射 类加载机制及反射
  • java加载机制.png

    2021-08-29 10:24:49
    1.java源文件是怎么编译成class文件的 2.类的生命周期 3.java类加载机制 4.类的加载 5.类的加载过程 6.类是怎么被初始化的? .....
  • Java类加载机制

    万次阅读 多人点赞 2019-03-02 10:21:04
    Java类加载机制 类加载的时机 隐式加载 new 创建类的实例, 显式加载:loaderClass,forName等 访问类的静态变量,或者为静态变量赋值 调用类的静态方法 使用反射方式创建某个类或者接口对象的Class对象。 初始化某个...

    Java类加载机制

    类加载的时机

    • 隐式加载 new 创建类的实例,
    • 显式加载:loaderClass,forName等
    • 访问类的静态变量,或者为静态变量赋值
    • 调用类的静态方法
    • 使用反射方式创建某个类或者接口对象的Class对象。
    • 初始化某个类的子类
    • 直接使用java.exe命令来运行某个主类

    类加载的过程

    我们编写的java文件都是保存着业务逻辑代码。java编译器将 .java 文件编译成扩展名为 .class 的文件。.class 文件中保存着java转换后,虚拟机将要执行的指令。当需要某个类的时候,java虚拟机会加载 .class 文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程被称为类的加载。

    在这里插入图片描述

    加载

    类加载过程的一个阶段,ClassLoader通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个class对象。

    验证

    目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身的安全,主要包括四种验证:文件格式的验证,元数据的验证,字节码验证,符号引用验证。

    准备

    为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值,(如static int i = 5 这里只是将 i 赋值为0,在初始化的阶段再把 i 赋值为5),这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。

    解析

    这里主要的任务是把常量池中的符号引用替换成直接引用

    初始化

    这里是类记载的最后阶段,如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值,成员变量也将被初始化)

    类记载器的任务是根据类的全限定名来读取此类的二进制字节流到 JVM 中,然后转换成一个与目标类对象的java.lang.Class 对象的实例,在java 虚拟机提供三种类加载器,引导类加载器,扩展类加载器,系统类加载器。

    forName和loaderClass区别

    • Class.forName()得到的class是已经初始化完成的。
    • Classloader.loaderClass得到的class是还没有链接(验证,准备,解析三个过程被称为链接)的。

    双亲委派

    双亲委派模式要求除了顶层的启动类加载器之外,其余的类加载器都应该有自己的父类加载器,但是在双亲委派模式中父子关系采取的并不是继承的关系,而是采用组合关系来复用父类加载器的相关代码。

    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 { // ExtClassLoader没有继承BootStrapClassLoader
                        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();
                    // AppClassLoader去我们项目中查找是否有这个文件,如有加载进来
                    // 没有就到用户自定义ClassLoader中加载。如果没有就抛出异常
                    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;
        }
    }
    

    工作原理

    如果一个类收到了类加载的请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最后到达顶层的启动类加载器,如果弗雷能够完成类的加载任务,就会成功返回,倘若父类加载器无法完成任务,子类加载器才会尝试自己去加载,这就是双亲委派模式。就是每个儿子都很懒,遇到类加载的活都给它爸爸干,直到爸爸说我也做不来的时候,儿子才会想办法自己去加载。

    优势

    采用双亲委派模式的好处就是Java类随着它的类加载器一起具备一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类的时候,就没有必要子类加载器(ClassLoader)再加载一次。其次是考虑到安全因素,Java核心API中定义类型不会被随意替换,假设通过网路传递一个名为java.lang.Integer的类,通过双亲委派的的模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字类,发现该类已经被加载,并不会重新加载网络传递过来的java.lang.Integer.而之际返回已经加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在calsspath路径下自定义一个名为java.lang.SingInteger?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器,最终会通过系统类加载器加载该类,但是这样做是不允许的,因为java.lang是核心的API包,需要访问权限,强制加载将会报出如下异常。

    java.lang.SecurityException:Prohibited package name: java.lang
    

    类与类加载器

    • 在JVM中标识两个Class对象,是否是同一个对象存在的两个必要条件
    • 类的完整类名必须一致,包括包名。
    • 加载这个ClassLoader(指ClassLoader实例对象)必须相同。

    双亲委派模式的破坏者:线程上下文类加载器

    在这里插入图片描述
    在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。

    线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例

    对象的创建过程

    当虚拟机遇到一个new的指令的时候,首先去检查这个指令是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析和初始化过。如果没有则执行相应初始化的过程。在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需要的内存的大小在类加载完成后便可以完成确定内存分配完成以后,虚拟机需要将分配的内存空间都初始化为零值,保证了对象的实例字段在Java代码中可以不赋予初值就直接使用,程序能访问到这些字段的数据类型对应的零值。再接下来对象需要进行必要的设置,这个对象是哪个类的实例,如何才能找到这个类的元数据信息,如何找到对象的哈希码,对象的GC分带年龄。

    • Java堆如果是规整的采取:指针碰撞,
    • Java堆如果不是规整的话:空闲列表,在内存中直接分配一个足够大的内存空间划分给对象。
    • 对象创建是非常平凡的,在多线程的程序中会产生线程安全的问题,所以解决这个问题有两种方式
    • 使用CSA配上失败重试的方式来保证原子性
    • 内存分配动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一个小块的内存成为本地分配缓冲,TLAB,哪个线程需要分配内存就在哪个线程的TALB上分配,只有在TALB用完之后才会重新分配新的TALB的时候才会同步锁定。

    对象的内存布局

    对象的内存布局一般分为三个部分:对象头,示例数据,对齐填充

    对象头中存放着对象自身的运行时数据,如哈希码,GC分带年龄,锁状态标志,偏向线程ID,线程持有的锁。

    对象头另外一部分还有类型指针,对象指向它类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个java数组,那在对象头中还必须用一块用于记录数组长度的数据。因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小。

    对象的访问定位方式

    句柄和直接指针

    • 如果使用句柄的话,要在java堆中开辟一个句柄池,用来存放句柄地址,句柄地址中包含对象实例数据(堆)和类型数据(方法区)各自的地址信息。
    • 是用句柄的好处就是引用中存储的是稳定的句柄地址,当被移动时只会修改句柄中的实例数据指针,而引用地址不会被改变。
    • 使用直接指针访问方式的最大好处就是速度更快,它节省了一次访问指针定位的时间开销,引用直接指向存放实例数据的堆内存,在该内存中存放着指向方法区的类型数据地址。
    展开全文
  • Android 热修复框架 (基于类加载机制的代码修复)
  • 面试必问的 JVM 类加载机制,你懂了吗?

    千次阅读 多人点赞 2021-08-14 14:49:36
    JVM 类加载机制高频面试题

    前言

    本次带来 JVM 的另一块重要内容,类加载机制,不废话,直接开怼。

    正文

    1、类加载的过程。

    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中验证、准备、解析3个部分统称为连接。

    1)加载

    “类加载”过程的一个阶段,在加载阶段,虚拟机需要完成以下3件事情:

    • 通过一个类的全限定名来获取定义此类的二进制字节流。
    • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

    2)验证

    连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。从整体上看,验证阶段大致上会完成下面4个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

    3)准备

    该阶段是正式为类变量(static修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这里所说的初始值“通常情况”下是数据类型的零值,下表列出了Java中所有基本数据类型的零值。

    4)解析

    该阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7类符号引用进行。

    5)初始化

    到了初始化阶段,才真正开始执行类中定义的Java程序代码。在准备阶段,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序制定的主观计划去初始化类变量和其他资源。

    我们也可以从另外一种更直接的形式来表达:初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>() 不是程序员在 Java 代码中直接编写的方法,而是由 Javac 编译器自动生成的。

    <clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。

    我之前还写过一篇关于初始化的面试题: 一道有意思的“初始化”面试题,有兴趣的同学可以看一看。

    2、Java 虚拟机中有哪些类加载器?

    从 Java 虚拟机的角度来讲,只存在两种不同的类加载器:

    一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;

    另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

    从Java开发人员的角度来看,绝大部分Java程序都会使用到以下3种系统提供的类加载器。

    1)启动类加载器(Bootstrap ClassLoader):

    这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。

    2)扩展类加载器(Extension ClassLoader):

    这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

    3)应用程序类加载器(Application ClassLoader):

    这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    我们的应用程序都是由这3种类加载器互相配合进行加载的,如果有必要,还可以加入自己定义的类加载器。这些类加载器之间的关系一般如图所示。

    3、什么是双亲委派模型?

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

    类加载的源码如下:

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 1、检查请求的类是否已经被加载过了
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        // 2、将类加载请求先委托给父类加载器
                        if (parent != null) {
                            // 父类加载器不为空时,委托给父类加载进行加载
                            c = parent.loadClass(name, false);
                        } else {
                            // 父类加载器为空,则代表当前是Bootstrap,从Bootstrap中加载类
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // 如果父类加载器抛出ClassNotFoundException
                        // 说明父类加载器无法完成加载请求
                    }
    
                    if (c == null) {
                        // 3、在父类加载器无法加载的时候,再调用本身的findClass方法来进行类加载
                        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;
            }
        }

    4、为什么使用双亲委派模式?

    1)使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。

    2)如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,Java 类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。

    5、有哪些场景破坏了双亲委派模型?

     目前比较常见的场景主要有:

    1)线程上下文类加载器,典型的:JDBC 使用线程上下文类加载器加载 Driver 实现类

    2)Tomcat 的多 Web 应用程序

    3)OSGI 实现模块化热部署

    6、为什么要破坏双亲委派模型?

    原因其实很简单,就是使用双亲委派模型无法满足需求了,因此只能破坏它,这边以面试常问的 Tomcat 为例。

    我们知道 Tomcat 容器可以同时部署多个 Web 应用程序,多个 Web 应用程序很容易存在依赖同一个 jar 包,但是版本不一样的情况。例如应用1和应用2都依赖了 spring ,应用1使用的 3.2.* 版本,而应用2使用的是 4.3.* 版本。

    如果遵循双亲委派模型,这个时候使用哪个版本了?

    其实使用哪个版本都不行,很容易出现兼容性问题。因此,Tomcat 只能选择破坏双亲委派模型。

    7、如何破坏双亲委派模型?

    破坏双亲委派模型的思路都比较类似,这边以面试中常问到的 Tomcat 为例。

    其实原理非常简单,我们可以看到上面的类加载方法源码(loadClass)的方法修饰符是 protected,因此我们只需以下几步就能破坏双亲委派模型。

    1)继承 ClassLoader,Tomcat 中的 WebappClassLoader 继承 ClassLoader 的子类 URLClassLoader。

    2)重写 loadClass 方法,实现自己的逻辑,不要每次都先委托给父类加载,例如可以先在本地加载,这样就破坏了双亲委派模型了。

    8、Tomcat 的类加载器?

    Tomcat 的类加载器如下图所示:

    1)Bootstrap ClassLoader:可以看到上图中缺少了 Extension ClassLoader,在 Tomcat 中 Extension ClassLoader 被集成到了 Bootstrap ClassLoader 里面。

    2)System ClassLoader 就是 Application ClassLoader:Tomcat 中的系统类加载器不会加载 CLASSPATH 环境变量的内容,而是从以下资源库构建 System 类加载器。

    • $CATALINA_HOME/bin/bootstrap.jar,包含用于初始化Tomcat服务器的 main() 方法,以及它所依赖的类加载器实现类。
    • $CATALINA_BASE/bin/tomcat-juli.jar 或 $CATALINA_HOME/bin/tomcat-juli.jar,日志实现类。
    • 如果 $CATALINA_BASE/bin 中存在 tomcat-juli.jar,则使用它来代替 $CATALINA_HOME/bin中的那个。
    • $CATALINA_HOME/bin/commons-daemon.jar

    3)Common ClassLoader:从名字也看出来来了,主要包含一些通用的类,这些类对 Tomcat 内部类和所有 Web 应用程序都可见。

    该类加载器搜索的位置由 $CATALINA_BASE/conf/catalina.properties 中的 common.loader 属性定义,默认设置将按照顺序搜索以下位置。

    • $CATALINA_BASE/lib 中未打包的类和资源
    • $CATALINA_BASE/lib 目录下的JAR 文件
    • $CATALINA_HOME/lib 中未打包的类和资源
    • $CATALINA_HOME/lib 目录下的JAR文件

    4)WebappX ClassLoader:Tomcat 为每个部署的 Web 应用程序创建一个单独的类加载器,这样保证了不同应用之间是隔离的,类和资源对其他 Web 应用是不可见的。加载的路径如下:

    • Web应用的 /WEB-INF/classes 目录下的所有未打包的类和资源
    • Web应用的 /WEB-INF/lib 目录下的 JAR 文件中的类和资源

    9、Tomcat 的类加载过程?

    Tomcat 的类加载过程,也就是 WebappClassLoaderBase#loadClass 的逻辑如下。

    1)首先本地缓存 resourceEntries,如果已经被加载过则直接返回缓存中的数据。

    2)检查 JVM 是否已经加载过该类,如果是则直接返回。

    3) 检查要加载的类是否是 Java SE 的类,如果是则使用 BootStrap 类加载器加载该类,以防止 webapp 的类覆盖了 Java SE 的类。

    例如你写了一个 java.lang.String 类,放在当前应用的 /WEB-INF/classes 中,如果没有此步骤的保证,那么之后项目中使用的 String 类都是你自己定义的,而不是 rt.jar 下面的,可能会导致很多隐患。

    4)针对委托属性 delegate 显示设置为 true、或者一些特殊的类(javax、org 包下的部分类),使用双亲委派模式加载,只有很少部分使用双亲委派模型来加载。

    5)尝试从本地加载类,如果步骤5中加载失败也会走到本步骤,这边打破了双亲委派模型,优先从本地进行加载。

    7)走到这,代表步骤6加载失败,如果之前不是使用双亲委派模式,则在这边会委托给父类加载器来尝试加载。

    8)走到这边代表所有的尝试都加载失败,抛出 ClassNotFoundException。

    10、JDBC 使用线程上下文类加载器的原理

    JDBC 功能相关的基础类是由 Java 统一定义的,在 rt.jar 里面,例如 DriverManager,也就是由 Bootstrap ClassLoader 来加载,而 JDBC 的实现类是在各厂商的实现 jar 包里,例如 MySQL 是在 mysql-connector-java 里,oracle、sqlserver 也会有各自的实现 jar。

    此时需要 JDBC 的基础类调用其他厂商实现并部署在应用程序的 ClassPath 下的 JDBC 服务提供接口(SPI,Service Provider Interface)的代码。当类A调用类B时,此时类B是由类A的类加载器来负责加载,而 JDBC 的基础类都是由 Bootstrap ClassLoader 来加载,但是 Bootstrap ClassLoader 是不认识也不会去加载这些厂商实现的代码的。

    因此,Java 提供了线程上下文类加载器,允许通过 Thread#setContextClassLoader/Thread#getContextClassLoader() 来设置和获取当前线程的上下文类加载器。如果创建线程时没有设置,则会继承父线程的,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器(Application ClassLoader)。

    综上,JDBC 可以通过线程上下文类加载器,来实现父类加载器“委托”子类加载器完成类加载的行为,这个就明显不遵守双亲委派模型了,不过这也是双亲委派模型自身的缺陷导致的。

    最后

    我是囧辉,一个坚持分享原创技术干货的程序员

    展开全文
  • 反射机制和类加载机制学习总结 Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并...
  • Java和SpringBoot类加载机制

    千次阅读 2020-08-07 14:39:55
    SPI使用案例二、Java类加载机制1、双亲委派模型2. 双亲委派模型缺陷3. 使用线程上下文类加载器(ContextClassLoader)加载4. 使用类加载器加载资源文件,比如jar包三、spring中SPI机制实现Springboot中的类SPI扩展...

    一、SPI介绍

    1. SPI机制

    (1)SPI思想

    • SPI的全名为Service Provider Interface.这个是针对厂商或者插件的。
    • SPI的思想:系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块(mysql、oracle、db2等)的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制

    (2)SPI约定

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

    2. SPI使用案例

    • common-logging apache最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通过读取该文件的内容找到日志提工商实现类。只要我们的日志实现里包含了这个文件,并在文件里制定 LogFactory工厂接口的实现类即可。

    • mysql对jdbc扩展

    在这里插入图片描述

    • 自定义示例

    定义接口SpiServcie,接口实现类 SpiImpl1、SpiImpl2

    在这里插入图片描述

    二、Java类加载机制

    • java中的类加载器负载加载来自文件系统、网络或者其他来源的类文件。jvm的类加载器默认使用的是双亲委派模式。三种默认的类加载器Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(Application ClassLoader)每一个中类加载器都确定了从哪一些位置加载文件。于此同时我们也可以通过继承java.lang.classloader实现自己的类加载器。
    • 启动(Bootstrap)类加载器:负责将 Java_Home/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
    • 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器
    • 应用程序(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器

    除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k0p6qF8a-1596782355206)(http://doc.szkunton.com/uploads/tec-share/images/m_5bf780378849012d16086262d738d066_r.png)]

    1、双亲委派模型

    • 原理:当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。

    具体:根据双亲委派模式,在加载类文件的时候,子类加载器首先将加载请求委托给它的父加载器,父加载器会检测自己是否已经加载过类,如果已经加载则加载过程结束,如果没有加载的话则请求继续向上传递直Bootstrap ClassLoader。如果请求向上委托过程中,如果始终没有检测到该类已经加载,则Bootstrap ClassLoader开始尝试从其对应路劲中加载该类文件,如果失败则由子类加载器继续尝试加载,直至发起加载请求的子加载器为止。

    • 采用双亲委派模式可以保证类型加载的安全性,不管是哪个加载器加载这个类,最终都是委托给顶层的BootstrapClassLoader来加载的,只有父类无法加载自己猜尝试加载,这样就可以保证任何的类加载器最终得到的都是同样一个Object对象。
    protected Class<?> loadClass(String name, boolean resolve) {
        synchronized (getClassLoadingLock(name)) {
        // 首先,检查该类是否已经被加载,如果从JVM缓存中找到该类,则直接返回
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 遵循双亲委派的模型,首先会通过递归从父加载器开始找,
                // 直到父类加载器是BootstrapClassLoader为止
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {}
            if (c == null) {
                // 如果还找不到,尝试通过findClass方法去寻找
                // findClass是留给开发者自己实现的,也就是说
                // 自定义类加载器时,重写此方法即可
               c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
        }
    }
    

    2. 双亲委派模型缺陷

    • 在双亲委派模型中,子类加载器可以使用父类加载器已经加载的类,而父类加载器无法使用子类加载器已经加载的。这就导致了双亲委派模型并不能解决所有的类加载器问题。
    • 案例:Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等,这些SPI的接口由核心类库提供,却由第三方实现,这样就存在一个问题:SPI 的接口是 Java 核心库的一部分,是由BootstrapClassLoader加载的;SPI实现的Java类一般是由AppClassLoader来加载的。BootstrapClassLoader是无法找到 SPI 的实现类的,因为它只加载Java的核心库。它也不能代理给AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题

    3. 使用线程上下文类加载器(ContextClassLoader)加载

    • 如果不做任何的设置,Java应用的线程的上下文类加载器默认就是AppClassLoader。在核心类库使用SPI接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到SPI实现的类。线程上下文类加载器在很多SPI的实现中都会用到。
    • 通常我们可以通过Thread.currentThread().getClassLoader()和Thread.currentThread().getContextClassLoader()获取线程上下文类加载器。

    4. 使用类加载器加载资源文件,比如jar包

    类加载器除了加载class外,还有一个非常重要功能,就是加载资源,它可以从jar包中读取任何资源文件,比如,ClassLoader.getResources(String name)方法就是用于读取jar包中的资源文件

    //获取资源的方法
    public Enumeration<URL> getResources(String name) throws IOException {
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);
        return new CompoundEnumeration<>(tmp);
    }
    

    它的逻辑其实跟类加载的逻辑是一样的,首先判断父类加载器是否为空,不为空则委托父类加载器执行资源查找任务,直到BootstrapClassLoader,最后才轮到自己查找。而不同的类加载器负责扫描不同路径下的jar包,就如同加载class一样,最后会扫描所有的jar包,找到符合条件的资源文件。

    // 使用线程上下文类加载器加载资源
    public static void main(String[] args) throws Exception{
        // Array.class的完整路径
        String name = "java/sql/Array.class";
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            System.out.println(url.toString());
        }
    }
    

    三、spring中SPI机制实现

    Springboot中的类SPI扩展机制

    在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。

    Spring Factories实现原理

    spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。上面的两个方法的关键都是从指定的ClassLoader中获取spring.factories文件,并解析得到类名列表,具体代码如下

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    // spring.factories文件的格式为:key=value1,value2,value3
    // 从所有的jar包中找到META-INF/spring.factories文件
    // 然后从文件中解析出key=factoryClass类名称的所有value值
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        // 取得资源文件的URL
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        // 遍历所有的URL
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            // 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            // 组装数据,并返回
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    

    从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。

    也就是说我们可以在自己的jar中配置spring.factories文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:com.xxx.interface=com.xxx.classname如果一个接口希望配置多个实现类,可以使用‘,’进行分割。

    示例:spring-boot包spring.factories支持的接口

    # PropertySource Loaders
    org.springframework.boot.env.PropertySourceLoader=\
    org.springframework.boot.env.PropertiesPropertySourceLoader,\
    org.springframework.boot.env.YamlPropertySourceLoader
    
    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=\
    org.springframework.boot.context.event.EventPublishingRunListener
    
    # Error Reporters
    org.springframework.boot.SpringBootExceptionReporter=\
    org.springframework.boot.diagnostics.FailureAnalyzers
    
    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.ClearCachesApplicationListener,\
    org.springframework.boot.builder.ParentContextCloserApplicationListener,\
    org.springframework.boot.context.FileEncodingApplicationListener,\
    org.springframework.boot.context.config.AnsiOutputApplicationListener,\
    org.springframework.boot.context.config.ConfigFileApplicationListener,\
    org.springframework.boot.context.config.DelegatingApplicationListener,\
    org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
    org.springframework.boot.context.logging.LoggingApplicationListener,\
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
    
    # Environment Post Processors
    org.springframework.boot.env.EnvironmentPostProcessor=\
    org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
    org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
    org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
    
    # Failure Analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=\
    org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
    org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
    
    # FailureAnalysisReporters
    org.springframework.boot.diagnostics.FailureAnalysisReporter=\
    org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
    
    
    展开全文
  • title: 类加载机制(一):简述类加载过程 date: 2019-03-13 10:06:10 categories: Java虚拟机 tags: 类加载机制加载机制(一):简述类加载过程 引言 Java源文件经过编译之后,生成了一连串的16...
  • java类加载机制.xmind

    2020-06-09 12:33:51
    该文件是JVM中关于类加载机制的知识整理的思维导图,包括类加载机制概述、类加载的生命周期、加载时机、加载过程、类加载、类的初始化和实例化等几个大方面进行了讲解,其中类加载中还对JVM三种预定义类加载器进行了...
  • jvm之java类加载机制和类加载器(ClassLoader)的详解

    万次阅读 多人点赞 2018-08-13 15:05:46
    当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。 一...
  • 面试官,不要再问我“Java虚拟机类加载机制”了

    万次阅读 多人点赞 2019-10-27 16:28:39
    关于Java虚拟机类加载机制往往有两方面的面试题:根据程序判断输出结果和讲讲虚拟机类加载机制的流程。其实这两类题本质上都是考察面试者对Java虚拟机类加载机制的了解。 面试题试水 现在有这样一道判断程序输出结果...
  • 该虚拟机类加载机制的脑图中包含了类加载的时机,类加载的过程,以及类加载器,对这三个方面的介绍举例说明的非常详细,后期继续会对内容进行跟新
  • 性能优化|全面剖析类加载机制

    千次阅读 2020-10-22 22:30:33
    性能优化|全面剖析类加载机制
  • 深入理解java虚拟机——类加载机制 定义   类加载机制指的是:虚拟机将描述类的数据从class文件加载到内存中,对加载的数据进行验证,解析,初始化,最后得到虚拟机认可后转化为直接可以使用的java类型的过程   ...
  • JDK源码JVM类加载机制

    千次阅读 热门讨论 2021-03-31 14:22:56
    JVM类加载机制 首先我们的java小程序demo,经过编译后变成.class文件,他是如何加载到内存的将.class文件 内存中有两大对象:1.类的字节码对象,只有一份在内存。2.类对象会有多份 文章目录JVM类加载机制前言一、...
  • Java类加载机制浅析

    2021-01-20 02:52:15
    所谓类加载机制就是 虚拟机把Class文件加载到内存 并对数据进行校验,转换解析和初始化 形成可以虚拟机直接使用的Java类型,即java.lang.Class   1、装载(Load)  查找和导入class文件 (1)通过一个类的全限定名
  • 2、类加载机制 3、类执行机制 我们这里主要介绍编译和类加载这两种机制。 一、源码编译 代码编译由JAVA源码编译器来完成。主要是将源码编译成字节码文件(class文件)。字节码文件格式主要分为两部分:常量池和方法...
  • 当用户在自己的代码中,需要某些额外的类时,再通过加载机制加载到JVM中,并且存放一段时间,便于频繁使用。  因此使用哪种类加载器、在什么位置加载类都是JVM中重要的知识。 JVM类加载  JVM类加载采用 父类...
  • Java 类加载机制详解

    2020-12-22 19:37:55
     二、类的加载机制  在Java中,采用双亲委派机制来实现类的加载。那什么是双亲委派机制?在Java Doc中有这样一段描述:  The ClassLoader class uses a delegation model to search for classes and resources

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 702,686
精华内容 281,074
关键字:

加载机制