Java基础:Java类加载


Java源代码在编译成class文件后,class文件的描述信息加载到JVM才能运行和使用。

Java类加载时机

当有以下操作时,会加载Java类

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

类加载过程

在这里插入图片描述

1、加载:

  • 1、通过类的全限定名来获取类的二进制字节流(用户可操作,自定义类加载器(实现通过一个类的全限定名获取类的二进制字节流的动作放在JVM外部实现的模块))
  • 2、将这个字节流所代表的静态存储结构转化为在方法区的运行时数据结构。
  • 3、在内存中生成代表这个类的java.lang.class类对象,作为这个数据访问的入口。
    注:数组类本身不是通过类加载器创建,而是由JVM直接创建。

2、验证:

防止危害JVM安全,目的是确保class文件中字节流中包含的信息符合当前虚拟机的要求。主要四个方面的验证:

  • 1、文件格式验证:是否以模数开始,版本信息是否为JVM接受,常量池中是否有不支持的类型。
  • 2、元数据验证:进行语法分析,是否每个类都是父类,是否有语法错误,是否继承自final 。
  • 3、字节码验证:语义分析,通过数据流和控制流分析,确保语义是合法的。
  • 4、符号引用验证:是否能找到对应的类。发生在将符号引用转化为直接引用时,在解析中产生

3、准备

正式为类变量分配内存,在方法区中分配
注意:static + final修饰的变量在准备阶段之后就是用户指定的值。

4、解析

将符号引用转化为直接应用(可选择)包括类,接口,字段,方法的解析。

5、初始化

真正执行Java程序中的代码(字节码),是执行类构造器的过程,对类的静态变量和代码块执行初始化工作。static块的执行发生在“初始化”阶段。

  • 1、()方法是由于编译器自动收集类中所有类变量赋值,静态语句块合并产生的,顺序是语句在源文件中的顺序。
  • 2、方法与类的构造器不同,它不需要显示的调用父类的构造方法,因为JVM会保证在子类的clinit方法执行之前,父类已经执行了。所以JVM执行的clinit一定是Object类
  • 3、如果一个类或者接口中没有静态语句或者静态代码块,则可以没有clinit()方法。
  • 4、JVM会保证类的clinit方法加锁。
    注:静态语句块中只能访问定义在静态语句之前的变量,不能访问语句之后的,但可以为后边的变量赋值。

类加载器

类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或叫Application类加载器)。每种类加载器都有设定好从哪里加载类。
在这里插入图片描述

  • Bootstrap类加载器负责加载JAVA_HOME\lib\rt.jar中的JDK类文件,它是所有类加载器的父加载器。Boorstrap类加载器没有任何父类加载器。如果你调用String.class.getClassLoader(),会返回null,任何基于此的代码会抛出NullPointerException异常。Bootstrap类加载器被称为初始类加载器。
  • Extension类加载从jre\lib\ext目录下或者java.ext.dirs系统属性定义的目录下加载类。
  • System类加载器(又叫Application类加载器),负责从classpath环境变量中加载某些应用相关的类,有-classpath或者-cp命令行选项来定义,或者是JAR中的Manifest的classpath属性。Application类加载器是Extension类加载器的子加载器。
    我们还可以自定义类加载器,通过继承ClassLoader实现,一般是加载我们的自定义类。

类加载器的工作原理

  • 1、委托机制
    假设有一个应用需要的类叫做A.class,首先加载这个类的请求由Application类加载器委托给他的父类加载器Extension类加载器,然后再委托给Bootstrap类加载器。Bootstrap类加载器先看rt.jar中有没有这个类,因为并没有这个类,所以这个请求又回到Extension类加载器,它会查看jre/lib/ext目录下有没有这个类,如果这个类被Extension类加载器找到了,那么这个类被加载,而Application类加载器不会加载这个类;而如果Extension类加载器没有找到这个类,那么再由Application类加载器从classpath中寻找。
    注:classpath定义的是类文件的加载目录。而PATH定义的是可执行程序如javac,java等的执行路径。
  • 2、可见性机制
    根据可见性机制,子类加载器父类加载器加载过的类,而反之则不行
  • 3、单一性机制
    根据这个机制,父类加载器加载过的类不能被子加载器加载第二次。虽然重写违反委托和单一机制的类加载器是可能的,但是这样做并不可取。自己写的类加载器应该严格遵守三条机制。

类加载机制

双亲委派

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

工作原理:

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

优势

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

java.lang程序包已经存在于另一模块中:java.base。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值