java进阶之类加载

目录

  • 一、类加载过程
    • 1. 加载
    • 2. 链接
    • 3. 初始化
  • 二、类加载器及加载机制
    • 1. 类加载器
    • 2. 类加载机制

一、类加载过程

java类加载过程主要分为一下三步:
java进阶之类加载_第1张图片

1. 加载

JVM把class文件字节码加载到内存中,并将这些静态数据转换成方法区的类型数据,并在堆中生成一个代表这个类的java.lang.Class对象。

方法区:存储已被虚拟机加载的类信息、常量、静态变量(详见JVM内存模型)

2. 链接

执行下面的校验、准备和解析(可选)步骤
a:校验:检查加载的class文件的正确性和安全性
b:准备:为静态变量分配存储空间并设置类变量初始值(默认值),类变量随类型信息存放在方法区中,生命周期很长,使用不当和容易造成内存泄漏。
c:解析:JVM将常量池内的符号引用转换为直接引用

3. 初始化

执行静态变量赋值和静态代码块

扩展:java对象初始化顺序:
父类的静态字段—>父类静态代码块—>子类静态字段—>子类静态代码块—>父类成员变量(非静态字段)—>父类非静态代码块(构造代码块)—>父类构造器—>子类成员变量—>子类非静态代码块(构造代码块)—>子类构造器

JVM初始化一个类包含如下几个步骤:

  • 假如这个类还没有被加载和连接,则程序先加载并连接该类。
  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
  • 假如类中有初始化语句,则系统依次执行这些初始化语句。所以JVM总是最先初始化java.lang.Object类。

类初始化的时机(主动引用时):

  • 创建类的实例时(new、反射、反序列化)。
  • 调用某个类的静态方法时。
  • 使用某个类或接口的静态字段或对该静态字段赋值时。
  • 使用反射来强制创建某个类或接口对应的java.lang.Class对象,如Class.forName(“className”)
  • 初始化某个类的子类时,此时该子类的所有父类都会被初始化。
  • 直接使用java.exe运行某个主类时。
  1. 对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。
  2. Class.forName()与Classloder.loaderClass()的区别:
    Class.forName()得到的Class是已经初始化完成的,而Classloder.loaderClass得到的Class是只加载还没有链接的

扩展:JDBC驱动的加载
在加载jdbc驱动时,通常使用如下代码:Class.forName(“com.mysql.jdbc.Driver”);
那是因为此代码会加载并初始化驱动类(执行静态代码块),在JDBC规范中明确要求这个驱动类必须向DriverManager注册自己,即任何一个JDBC Driver的 Driver类的代码都必须类似如下代码:

public class MyJDBCDriver implements Driver {
	static {
		DriverManager.registerDriver(new MyJDBCDriver());
	}
}

因此使用Class.forName将会实例化驱动类,并注册到DriverManager中

二、类加载器及加载机制

1. 类加载器

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

当JVM启动时,会形成有3个类加载器组成的初始类加载器层次结构:
java进阶之类加载_第2张图片
(1) Bootstrap ClassLoader: 根类(或叫启动、引导类加载器)加载器。
它负责加载Java的核心类(如String、System等)。它比较特殊,因为它是由原生C++代码实现的,并不是java.lang.ClassLoader的子类,所以开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

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

它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext)中JAR包的类,我们可以通过把自己开发的类打包成JAR文件放入扩展目录来为Java扩展核心类以外的新功能。

(3) System ClassLoader(或Application ClassLoader):系统类加载器。

它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader来获取系统类加载器

2. 类加载机制

(1) 全盘负责: 当一个类加载器加载某个Class时,该Class所依赖和引用的其它Class也将由该类加载器负责载入,除非显式的使用另外一个类加载器来载入。

(2) 双亲委派: 当一个类加载器收到了类加载请求,它会把这个请求委派给父(parent)类加载器去完成,依次递归,因此所有的加载请求最终都被传送到顶层的启动类加载器中。只有在父类加载器无法加载该类时子类才尝试从自己类的路径中加载该类。(注意:类加载器中的父子关系并不是类继承上的父子关系,而是类加载器实例之间的关系。)

(3) 缓存机制: 缓存机制会保证所有加载过的Class都会被缓存,当程序中需要使用某个类时,类加载器先从缓冲区中搜寻该类,若搜寻不到将读取该类的二进制数据,并转换成Class对象存入缓冲区中。这就是为什么修改了Class后需重启JVM才能生效的原因。

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

Java 9仍然保留了三层类加载器结构,不过为了支持模块系统,对它们做了一些调整

参考链接:
https://blog.csdn.net/cnahyz/article/details/82219210
https://blog.csdn.net/m0_38075425/article/details/81627349
https://www.cnblogs.com/xuxinstyle/p/9128874.html

你可能感兴趣的:(java进阶之设计与分析,java,jvm,编程语言,反射)