java基础--类加载

一、字节码

字节码文件即.class文件,里面是这样的:

其中“cafe babe”被称为“魔数”,是 JVM 识别 .class 文件的标志。

所有java源文件(.java)编译完成后生成字节码文件(.class),交由JVM执行。JVM读取、加载.class文件的过程就是类加载。

二、类加载过程

类加载分为三个步骤:加载、连接(验证、准备、解析)、初始化。

1、加载loading

加载指的是将类的class文件读入到内存——将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

该过程由类加载器完成,类加载器通常由JVM提供,它是程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。

  • 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
  • 从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
  • 通过网络加载class文件。
  • 把一个Java源文件动态编译,并执行加载。

    类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

2、连接 linking

该阶段负责将类的二进制数据合并到JRE中,其又可分为三个阶段:

1)验证 verification

确保加载的类信息符合JVM规范,无安全方面的问题,并和其他类协调一致。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

2)准备 preparation

负责为类的静态变量分配内存,并设置默认初始值(对应数据类型的默认初始值,如 0、0L、null、false 等)。假设有如下代码:

public static int value = 8;

在该阶段value的值将根据其类型int初始化为 0。而将 value 赋值为8的动作在初始化阶段才会执行(调用<clinit()>方法,执行putstatic指令)。

3)解析 resolution

该阶段将常量池中的符号引用转化为直接引用。符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。直接引用:是指向目标的指针(内存地址),偏移量或者能够直接定位的句柄。

3、初始化 initialization

初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。换句话说,初始化阶段是执行类构造器方法的过程。调用类的初始化方法<clinit()>为静态变量赋予实际的值(例如将value赋值为8)、执行静态代码块。

类加载时机

1、创建类的实例,也就是new一个对象;

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

3、调用类的静态方法;

4、反射(Class.forName("User"));

5、初始化一个类的子类(会首先初始化子类的父类);

6、Java.exe 直接启动主类时。

除此之外,下面几种情形需要特别指出:

     对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。反之,如果final类型的静态Field的值不能在编译时确定下来,则必须等到运行时才可以确定该变量的值,如果通过该类来访问它的静态变量,则会导致该类被初始化。

三、类加载器

类加载器负责将class文件读入内存,并生成对应的java.lang.Class对象。一旦一个类被加载入JVM中,同一个类就不会被再次载入了。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。

Java9之前

当JVM启动时,会形成有3个类加载器组成的初始类加载器层次结构:(注意不是继承关系

(1).Bootstrap ClassLoader:根类(或叫启动、引导类加载器)加载器。它负责加载Java的核心类(如String、System等,$JAVA_HOME中jre/lib/rt.jar里所有的class)。它比较特殊,因为它是由原生C++代码实现的,并不是java.lang.ClassLoader的子类。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

(2).Extension ClassLoader:扩展类加载器。它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext)中JAR包的类,我们可以通过把自己开发的类打包成JAR文件放入扩展目录来为Java扩展核心类以外的新功能。由Java语言实现,父类加载器为根加载器(输出却为null)。

(3).System ClassLoader(或Application ClassLoader):系统类加载器。它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。

 

Java9之后

JDK 9保持三级分层类加载器架构以实现向后兼容。但是引入模块系统,加载类的方式有一些变化。新增Platform ClassLoader:平台类加载器,用于加载一些平台相关的模块,例如: java.activation 、 java.se 、 jdk.desktop 、 java.compiler 等,双亲是BootClassLoader,不再支持扩展机制。在JDK 9中,应用程序类加载器可以委托给平台类加载器以及引导类加载器;平台类加载器可以委托给引导类加载器和应用程序类加载器。 在JDK 9之前,扩展类加载器和应用程序类加载器都是java.net.URLClassLoader类的一个实例。 而在JDK 9中,平台类加载器(以前的扩展类加载器)和应用程序类加载器是内部JDK类的实例。 如果你的代码依赖于URLClassLoader类的特定方法,代码可能会在JDK 9中崩溃。

四、类加载机制

JVM的类加载机制主要有如下3种。

  • 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
  • 双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
  • 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

JDK 9中的类加载机制有所改变。 当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责哪个模块的加载器完成加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值