JVM--类加载

类加载

类的生命周期

加载

  • 通过一个类的权限定名来获取定义此类的二进制字节流:并未指明定义类的二进制字节流的存储形式(class文件、ZIP包)、来源(本地文件系统、内存或网络)以及获取方式(既可以从已有静态资源读取也可动态生成),因而就有了如下的多样可能性:1.>从ZIP包中读取,这是后来支持类加载器可从JAR、EAR、WAR等格式文件中加载class的基础;2.>从网络中获取字节流,我们熟知的Applet是这种场景的典型应用;3.>程序动态生成字节流,这种场景应用最多的就是动态代理,通过字节码技术动态生成代理类的二进制字节流;4.>由除了Java源文件之外的其他文件编译而成,如JSP文件、Scala源文件等。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表此类的java.lang.Class对象,作为对方法区中此类的各种数据的访问入口。
  • 加载阶段的注意点:数组类的加载比较特殊,它本身并不通过类加载器创建,而是由Java虚拟机直接创建,但数组类的元素类型(去掉所有维度后的类型,比如A[ ][ ]的元素类型,就是A)是由类加载器加载的。举例,对于类型org.sherlockyb.test.HelloWorld,定义一维数组类HelloWorld[] hws = new HelloWorld[8],虚拟机会直接创建名为“[Lorg.sherlockyb.test.HelloWorld”的数组类,并对其进行初始化。
  • 加载时机:虚拟机规范并未强制规定加载阶段具体什么时候开始,由虚拟机实现自由把握。就我们所熟知的HotSpot虚拟机来说,有两种情况:1.>预加载。虚拟机在启动时会预先加载rt.jar中的class文件,其中包括java.lang.*、java.util.*、java.io.*等运行时常用的类。2.>运行时加载。当虚拟机在运行过程中需要某个类时,如果该类的class未被加载则加载之。

连接

连接可分为三个阶段:验证、准备和解析
验证:

  • 文件格式验证:判断当前字节流是否符合class文件格式的规范。 如是否以class文件的魔数oxCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中常量的类型是否合法等等。校验的目的是保证字节流能正确地解析并存储于方法区内,通过验证后,会在方法区中存储,后面的校验动作都是基于方法区的存储结构进行,不再直接操作字节流。
  • 元数据校验:语义分析,判断其描述的信息是否符合Java语言的规范要求。如该类除了java.lang.Object之外,是否有其他父类;该类的父类是否继承了不允许被继承的final类等
  • 字节码验证:通过数据流和控制流分析,判断程序语义是否合法、符合逻辑。如保证跳转指令不会跳转到方法体以外的字节码指令上、方法体中的类型转换是有效的等。
  • 符号引用校验:发生在解析阶段将符号引用转为直接引用的时候,确保解析动作能正确执行。如符号引用中通过字符串描述的全限定名是否能找到对应类。

准备:

  • 准备阶段做的唯一一件事就是为类的静态变量分配内存,并将其初始化为[[默认值]]
  • jvm默认的初值是这样的:基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0,引用类型的默认值为null。常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100。

解析:

  • 这一阶段的任务就是把常量池中的符号引用转换为直接引用。那么什么是符号引用,什么又是直接引用呢?比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址, jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址

初始化(https://www.jianshu.com/p/791ac2d2f9f5)

为类的静态变量赋予程序设定的初始值。声明类变量时指定初始值和静态代码块为静态变量赋值。

  • 若该类还没有被加载和连接,则先加载并连接该类.
  • 若该类的直接父类没有被初始化,则先初始化其父类(接口没有此规则)。
  • 若该类有初始化语句(赋值语句和静态代码块),则按照代码中申明的顺序依次执行初始化语句。

编译器在编译Java源文件时,自动收集类中所有类变量的赋值操作和静态语句块中的语句(按照源码中声明先后顺序),将其合并产生方法,即类构造器(注意与实例构造器相区分)。该方法的执行过程遵循以下规则:

  • 虚拟机保证在子类的方法执行之前,会先执行父类的方法(若父类是接口,则忽略不执行),依次递归。
  • 方法并不是必须的。若一个类或接口中既没有类变量的赋值操作也没有静态语句块(接口没有此项),编译器可以不为它生成方法。
  • 虚拟机会保证方法在多线程环境中被正确地加锁、同步,确保同一时刻只会有一个线程去执行该方法。这也是单例模式其中一种实现方式(定义静态实例)的依据。

直接(主动)引用:

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码时,如果类没有被初始化,则先触发其初始化。从Java代码层面来讲,就是使用new关键字实例化对象、读取或设置类的静态字段(final修饰的常量字段除外)、调用静态方法的时候。
  • 使用java.lang.reflect包的方法对类进行反射调用时,如Class.forName(…)。
  • 当初始化一个类时,若其父类还未初始化,则先触发其父类的初始化(接口无此规则)。
  • 当虚拟机启动时,用户需指定一个主类(包含main方法),虚拟机会先初始化该类。
  • 对于REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄(使用JDK1.7的动态特性),若其对应的类还未初始化,则先触发其初始化。

被动引用:
除了主动都是被动引用:

  • 通过子类调用父类的静态字段,不会触发子类初始化
  • 通过数组定义来引用类,不会触发该类的初始化。例如A[] arr = new A[8] ,并不会触发A的初始化
  • 在类A中调用B的常量字段,不会触发B的初始化。因为此常量字段在编译阶段会存入调用类A的常量池中,本质上并没有直接引用到定义类B
public class Test {
	 
	public static void main(String[] args) {
		S.a=13;
	 }
}
class S extends P{
	static {
		System.out.println("初始化S类对象");
	}
}
class P{
	static  int a=12;
}

使用反射调用

卸载

当一个类被判定为无用类时,才可以被卸载。

  • 类的所有实例都已被回收。
  • 加载该类的ClassLoader已被回收。
  • 该类对应的java.lang.Class对象没有在任何地方被引用。

对于满足上述3个条件的无用类,虚拟机可以对其回收,但并不是必然的,是否回收可通过-Xnoclassgc参数控制。注意:在大量使用反射、动态代理等字节码框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代(特指HotSpot虚拟机)不会溢出。

类加载器

分类

在这里插入图片描述
启动类加载器(BootStrap ClassLoader):
是java类加载层次中最顶层的类加载器,负责加载jdk中的核心类库。由C++实现,不是classLoader的子类。
扩展类加载器(Extension ClassLoader):
负责加载java的扩展类库,比如lib/ext或者java.ext.dirs系统属性指定的目录中的jar包。父类加载器为null。
系统类加载器(App ClassLoader):
负责加载来自java命令的-classpath选项、java.class.path系统属性所指定的jar包和类路径。程序可以通过classLoader的静态方法getSystemClassLoader(),来获取系统类加载器。由java语言实现,父类加载器为ExtClassLoader
自定义的classLoader都必须继承自java.lang.ClassLoader类

java.lang.ClassLoader类

java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即java.lang.Class 类的一个实例。除此之外,ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
在这里插入图片描述

类加载器的代理模式

类加载器的代理模式-双亲委托机制:

  • 就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归 (本质上就是loadClass函数的递归调用)。因此,所有的加载请求最终都应该传送到顶层的启动类加载器中。如果父类加载器可以完成这个类加载请求,就成功返回;只有当父类加载器无法完成此加载请求时,子加载器才会尝试自己去加载。
  • 双亲委托机制是为了保证 Java 核心库的类型安全。这种机制就保证不会出现用户自己定义的 java.lang.Object 类的情况。
  • 并不是所有的类加载器都是双亲委托机制
  • tomcat 服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类。这与一般类加载器的顺序是相反的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值