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

    2020-08-19 02:46:07
    主要介绍了JAVA类加载机制的相关知识,文中代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
  • jvm 类加载机制 双亲委派模型 文档 jvm 类加载机制 双亲委派模型 文档
  • 面试必问 Java类加载机制和类加载器

    千次阅读 多人点赞 2020-06-15 18:49:48
    目前,只要是Java的面试,类加载机制一定会被问到。写这篇博客,供小伙伴们参考。

    1. 类加载机制

    所谓类加载机制就是JVM虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成虚拟机可以直接使用的Java类型,即Java.lang.Class。

    2. 类加载的过程

    类加载的过程主要有装载(Load)、链接(Link)、初始化(Initialize)

    2.1 装载(Load)

    类的装载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类装载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
    在这里插入图片描述
    类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

    加载.class文件的方式

    • 从本地系统中直接加载
    • 通过网络下载.class文件
    • 从zip,jar等归档文件中加载.class文件
    • 从专有数据库中提取.class文件
    • 将Java源文件动态编译为.class文件

    2.2 链接(Link)

    链接这一过程又可以分为验证(Validate)、准备(Prepare)、解析(Resolve)三个阶段

    • 验证(Validate)
      保证被加载类的正确性。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

    • 准备(Prepare)
      为类的静态变量分配内存,并将其初始化为默认值

    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意

    1. 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
    2. 这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

    假设一个类变量的定义为:public static int value = 3;
    那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。

    这里还需要注意以下几点

    1. 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。
    2. 对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。
    3. 对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
    4. 如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值
    5. 如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。假设上面的类变量value被定义为: public static final int value = 3;,编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。我们可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中
    • 解析(Resolve)
      解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

    2.3 初始化

    对类的静态变量,静态代码块执行初始化操作。准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析,到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

    JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

    1. 声明类变量是指定初始值,也就是直接给类别量一个值
    2. 使用静态代码块为类变量指定初始值

    初始化,主要是执行类的类构造器< clinit>()方法,JVM会将类中的静态代码块和静态变量的赋值语句放在该方法里面。

    JVM初始化步骤

    1、假如这个类还没有被加载和链接,则程序先加载并链接该类

    2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

    3、假如类中有初始化语句,则系统依次执行这些初始化语句

    类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

    – 创建类的实例,也就是new的方式

    – 访问某个类或接口的静态变量,或者对该静态变量赋值

    – 调用类的静态方法

    – 反射(如Class.forName(“com.shengsiyuan.Test”))

    – 初始化某个类的子类,则其父类也会被初始化

    – Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

    3. clinit方法

    类初始化方法clinit:JVM通过Classloader进行类型加载时,如果在加载时需要进行类的初始化操作时,则会调用类的初始化方法。 clinit方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句中可以赋值,但是不能访问。

    1. clinit方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成clinit方法。
    2. 接口中不能使用静态语句块,但仍然有类变量(final static)初始化的赋值操作,因此接口与类一样会生成clinit方法。但是接口与类不同的是:执行接口的clinit方法不需要先执行父接口的clinit方法,只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的clinit方法。
    3. 虚拟机会保证一个类的clinit方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的clinit方法,其他线程都需要阻塞等待,直到活动线程执行clinit方法完毕。如果在一个类的clinit方法中有耗时很长的操作,那就可能造成多个线程阻塞,在实际应用中这种阻塞往往是很隐蔽的。

    说到 clinit方法,就不得不说一下对象实例化方法init。

    对象实例化方法init:Java对象在被创建时,会进行实例化操作,给成员变量赋值。该部分操作封装在init方法中,并且子类的init方法中会首先对父类init方法的调用。

    clinit 方法和init 方法的区别

    • init和clinit方法执行时机不同
    • init是对象构造器方法,也就是说在程序执行new 一个对象调用该对象类的 constructor 方法时才会执行init方法,而clinit是类构造器方法,也就是在jvm进行类加载—–验证—-解析—–初始化,中的初始化阶段jvm会调用clinit方法。
    • init和clinit方法执行目的不同
    • init是instance实例构造器,对非静态变量解析初始化,而clinit是class类构造器对静态变量,静态代码块进行初始化
    • clinit 和init方法的数量不同
    • 编译器最多只为一个类生成一个clinit方法,如果类中没有静态成员或者代码块的话,就不会有clint方法。而init方法,类中一个构造函数就对应一个init方法

    4. 类加载器

    类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载到JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例kl负责加载,则该Person类对应的Class对象在JVM中表示为(Person.pg.kl)。这意味着两个类加载器加载的同名类:(Person.pg.kl)和(Person.pg.kl2)是不同的、它们所加载的类也是完全不同、互不兼容的。

    JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:

    • 启动类加载器(Bootstrap ClassLoader):负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是由C++实现的,没有对应的Java对象,因此在Java中只能用null代替。

    • 扩展类加载器(Extension ClassLoader):负责加载java平台中扩展功能的一些jar包,包括JDK/jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。,开发者可以直接使用扩展类加载器。

    • 应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    • 自定义类加载器 Custom ClassLoader: 通过继承java.lang.ClassLoader根据自身需要自定义ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

    5. 双亲委派模型

    几种类加载器的层次关系如下图所示
    在这里插入图片描述

    这种层次关系称为类加载器的双亲委派模型。我们把每一层上面的类加载器叫做当前层类加载器的父加载器,当然,它们之间的父子关系并不是通过继承关系来实现的,而是使用组合关系来复用父加载器中的代码。该模型在JDK1.2期间被引入并广泛应用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者们推荐给开发者的一种类的加载器实现方式。

    双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

    双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

    参考:

    如有不足之处,欢迎指正,谢谢!

    展开全文
  • 主要给大家介绍了关于JVM类加载机制及类缓存问题的处理方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
  • title: 类加载机制(一):简述类加载过程 date: 2019-03-13 10:06:10 categories: Java虚拟机 tags: 类加载机制 类加载机制(一):简述类加载过程 引言 Java源文件经过编译之后,生成了一连串的16...

    title: 类加载机制(一):简述类加载过程
    date: 2019-03-13 10:06:10
    categories:

    • Java虚拟机
      tags:
    • 类加载机制

    类加载机制(一):简述类加载过程

    引言

    Java源文件经过编译之后,生成了一连串的16进制的字节码,即以.class结尾的文件,而这些描述了Java程序各种信息的字节码,还必须要加载到Java虚拟机之后,才能被运行及使用,而加载class文件的过程就是jvm的类加载机制。

    类加载过程

    与大多数语言一样,Java类型也需要加载、连接与初始化,而不同的是,在Java代码中,类型的这些过程都是在程序运行期所完成的。这个加载过程分为加载、连接(验证、准备、解析)、初始化,整个过程简单来说:

    加载:查找并加载类的二进制数据,即加载class文件

    连接:

    ----验证:确保被加载的类的正确性

    ----准备:为类的静态变量分配内存,并将其设置为默认值

    ----解析:将类中的符号引用转换为直接引用

    初始化:顺序执行静态代码,即为类的静态变量赋予正确的初始值以及执行静态代码块

    类的加载

    类的加载指的是将类的class文件中的二进制数据从硬盘读取到内存中,将其放置在运行时数据区的方法区(虚拟机规范并未要求,但hotspot虚拟机将其放置在虚拟机中)内,然后在内存中创建唯一一个类的class对象,用以封装类在方法区中的数据结构,无论new出多少个对象,最终对应的class对象只有一个。

    类的加载方式

    加载.class文件的方式

    ----从本地文件系统中加载

    ----通过网络下载.class文件

    ----从jar,war等归档文件中加载.class文件

    ----从专有数据库中提取.class文件

    ----将Java源文件动态编译为.class文件

    编写一个MyCat类,添加虚拟机参数-XX:+TraceClassLoading参数,运行代码,可以看出,MyCat类被虚拟机从本地文件系统加载到内存中。

    jvm虚拟机参数:都以-XX:开始
    	-XX:+<option> 表示开启option选项 ,
    	-XX:-<option> 表示关闭option选项,
    	-XX:<option>=<value>,表示将option值设置为value
    
    	-XX:+TraceClassLoading
    		有时候我们需要监控系统中哪些类被加载进来,什么样的类加载的比较频繁,什么样的类加载的比较少,可以使用这个参数来配置打印出程序执行过程中类的加载信息。
    
    类加载器

    类的加载是通过类加载器来加载的,虚拟机中有两种类型的加载器。

    Java虚拟机自带的加载器

    ----根类加载器(Bootstrap

    ----扩展类加载器(Extension

    ----系统(应用)加载器(System

    用户自定义的类加载器

    ----java.lang.classLoader的子类

    ----用户可以定制类的加载方式

    后续请看我的关于类加载器的文章
    类加载机制(三):类的加载与类加载器
    类加载机制(四):解析ClassLoader
    类加载机制(五):自定义类加载器与深入双亲委托机制
    类加载机制(六):getSystemClassLoader与Launcher
    类加载机制(七):线程上下文类加载器

    类的连接

    类被加载后,就进入连接阶段。连接就是将已经加载到内存中的类的二进制数据合并到虚拟机的运行时环境中。

    类的验证

    类的验证的内容

    ----类文件的结构检查

    ----语义检查

    ----字节码验证

    ----二进制兼容性的验证

    类的准备

    将静态变量设置为默认值,而实例变量将会在对象实例化时随着对象一起分配到Java堆中去。

    类的解析

    将类中的符号引用转换为直接引用,(摘自深入理解JAVA虚拟机)。

    符号引用:

    符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

    直接引用:

    直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。有了直接引用,那引用的目标必定已经在内存中存在。

    类的初始化

    到了初始化阶段,才真正开始执行类中定义的Java代码,在初始化阶段,就会为类变量赋予正确的初始值。

    后续请看我的关于类的初始化的文章。

    类加载机制(二):类的初始化

    展开全文
  • 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中的类时就不会与集群中的其他版本冲突。

    展开全文
  • 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堆中开辟一个句柄池,用来存放句柄地址,句柄地址中包含对象实例数据(堆)和类型数据(方法区)各自的地址信息。
    • 是用句柄的好处就是引用中存储的是稳定的句柄地址,当被移动时只会修改句柄中的实例数据指针,而引用地址不会被改变。
    • 使用直接指针访问方式的最大好处就是速度更快,它节省了一次访问指针定位的时间开销,引用直接指向存放实例数据的堆内存,在该内存中存放着指向方法区的类型数据地址。
    展开全文
  • jvm之java类加载机制和类加载器(ClassLoader)的详解

    万次阅读 多人点赞 2018-08-13 15:05:46
    当程序主动使用某个类时,如果该类还未被加载到内存中,则...如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。 一、类加载过程 1.加载 加载指的是将类的class文件...
  • 类加载机制及反射

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

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

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

    千次阅读 多人点赞 2021-08-14 14:49:36
    JVM 类加载机制高频面试题
  • 主要介绍了Java类加载器和类加载机制,结合实例形式分析了java类加载器与类加载机制原理、实现方法及相关操作技巧,需要的朋友可以参考下
  • 反射机制和类加载机制学习总结 Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并...
  • Android 热修复框架 (基于类加载机制的代码修复)
  • 面试官,不要再问我“Java虚拟机类加载机制”了

    万次阅读 多人点赞 2019-10-27 16:28:39
    关于Java虚拟机类加载机制往往有两方面的面试题:根据程序判断输出结果和讲讲虚拟机类加载机制的流程。其实这两类题本质上都是考察面试者对Java虚拟机类加载机制的了解。 面试题试水 现在有这样一道判断程序输出结果...
  • 该虚拟机类加载机制的脑图中包含了类加载的时机,类加载的过程,以及类加载器,对这三个方面的介绍举例说明的非常详细,后期继续会对内容进行跟新
  • 当用户在自己的代码中,需要某些额外的类时,再通过加载机制加载到JVM中,并且存放一段时间,便于频繁使用。  因此使用哪种类加载器、在什么位置加载类都是JVM中重要的知识。 JVM类加载  JVM类加载采用 父类...
  • 深入理解java虚拟机——类加载机制 定义   类加载机制指的是:虚拟机将描述类的数据从class文件加载到内存中,对加载的数据进行验证,解析,初始化,最后得到虚拟机认可后转化为直接可以使用的java类型的过程   ...
  • 主要介绍了Java类加载机制实现流程及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • jvm类加载机制学习基础(一)

    千次阅读 2020-12-25 21:06:12
    一.类加载机制理解 二.什么情况下会触发类的加载? 三.类加载会经历哪些阶段? 四. 类加载时类成员的加载顺序 五. 类加载器的双亲委派模型
  • JVM类加载机制详解

    2020-08-31 08:55:22
    本文主要介绍了JVM类加载机制的相关知识,具有很好的参考价值,下面跟着小编一起来看下吧
  • Tomcat的类加载机制

    2019-01-30 23:20:17
    关于Java的类加载机制推荐这一篇:深入探讨Java类加载机制 本文转载自:https://www.cnblogs.com/aspirant/p/8991830.html Tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),...
  • 性能优化|全面剖析类加载机制

    千次阅读 2020-10-22 22:30:33
    性能优化|全面剖析类加载机制
  • Java类加载机制浅析

    2021-01-20 02:52:15
    所谓类加载机制就是 虚拟机把Class文件加载到内存 并对数据进行校验,转换解析和初始化 形成可以虚拟机直接使用的Java类型,即java.lang.Class   1、装载(Load)  查找和导入class文件 (1)通过一个类的全限定名
  • JDK源码JVM类加载机制

    千次阅读 热门讨论 2021-03-31 14:22:56
    JVM类加载机制 首先我们的java小程序demo,经过编译后变成.class文件,他是如何加载到内存的将.class文件 内存中有两大对象:1.类的字节码对象,只有一份在内存。2.类对象会有多份 文章目录JVM类加载机制前言一、...
  • 2、类加载机制 3、类执行机制 我们这里主要介绍编译和类加载这两种机制。 一、源码编译 代码编译由JAVA源码编译器来完成。主要是将源码编译成字节码文件(class文件)。字节码文件格式主要分为两部分:常量池和方法...
  • java类加载机制详解

    千次阅读 2017-03-25 19:35:10
     引子:编程过程中,大家肯定遇到过java.lang.ClassNotFoundException错误,特别是当我们自定义类加载器进行类的加载或者利用java的反射机制获取Class对象时。  虚拟机整体架构的第一部分就是类装载器,Java类加...
  • Java虚拟机加载类的全过程包括:加载、验证、准备、解析、初始化。验证、准备、解析叫连接过程。今天我们讲解析。 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在class文件中以CONSTANT_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 525,531
精华内容 210,212
关键字:

类加载机制