文章目录
Java类加载
概述:
- 类加载:就是把一份被javac编译过的class文本文件,通过加载,生成某种形式的class数据结构进入内存,程序可以调用这个数据结构来构造出object
- 这个过程是在运行时进行的----------->java动态扩展性的根基
Java类生命周期
注: 解析部分是灵活的,可以在初始化前,(静态解析)。也可以在初始化之后(动态解析)
一: 加载
- 读取class文件,将其转化为某种静态数据结构存储在方法区内,并在堆中生成一个便于用户调用的java.lang.Class类型的对象的过程
- 对文件格式验证 ----- 双亲委派机制
注: 这里读的class文件不一定指本地文件,泛指各种二进制流
例: 来自网络,来自数据库,甚至是即时生成的class
如: 动态代理技术 -----> 使用即时计算出来的class,然后实例化对象
二:连接
2.1、验证
对方法区中的class静态结构做验证
- 元数据验证
- 字节码验证
**概括:**简单说就是对class静态结构进行语法和语义上的分析,保证其不会产生危害虚拟机的行为
2.2、准备
负责为类的静态成员分配内存,并设置默认初始化值
为类变量赋予默认值,类变量如果被final修饰则直接赋予定义的值(只为类变量赋值)
2.2.1、方法区扩展
虚拟机内存规范中定义了方法区这种抽象概念
HotSpot 这种虚拟机在JDK8之前使用了 永久代 这种具体的实现方式来实现方法区
JDK8及之后,弃用"永久代"这种实现方式,采用 元空间 这种直接内存取代
方法区 是抽象概念,元空间和永久代是具体的实现
注:在JDK8之前,类的元信息、常量池、静态变量都存储在永久代这种具体实现中
JDK8及之后:常量池、静态变量被移除“方法区” 转移到堆中,
元信息依旧保留在方法区内------>具体的存储方式改为了 元空间
2.3、解析 (静态解析、动态解析)
将符号引用转化为直接引用
符号引用: A类被编译为class文件,如果A类中引用了B类,那么编译阶段A是不知道B有没有被编译,而且此时B也一定没有加载,那么A怎么才能找到这个B呢?此时在A的class文件中,将使用一个字符串S来代表B的地址,这个S就叫做符号引用
***直接引用:***在运行时,如果A发生了类的加载,到了解析阶段发现B没有加载,将会触发B类加载,将B加载到虚拟机中,此时A中对B的符号引用换为B的实际地址—>直接引用,真正的调用B
注: Java通过“后期绑定”的方式来实现多态
后期绑定是怎么实现?----> 通过解析阶段的动态解析
**静态解析:**如果A调用的B 是一个具体的实现类,这样称为静态解析,因为解释的目标类型很明确
**动态解析:**假如上层Java代码使用了多态,这里B如果是一个抽象类或者接口,那么B的具体实现就不明确–>这样就不能明确用哪个实现类的直接引用.------->**等到运行过程中发生调用,此时虚拟机调用栈中将会得到具体的类型信息,这时再进行解析,**就能明确其直接引用.----->为甚么解析阶段可能会发生在初始化之后,—>动态解析,实现了后期绑定
连接完成—意味着将一个java类成功引入到自己的程序中
三:初始化
- 执行类构造器()方法的过程
此时判断代码中,是否存在主动的资源初始化操作–如果有,执行
白话:为类变量赋值,为成员变量赋零值,执行静态代码块
java类加载器
概述
负责将.class 文件加载到内存中,并为之生成对应的class对象
分类
Bootstrap ClassLoader 根类加载器
Extension ClassLoader 扩展类加载器
Sysetm ClassLoader 系统类加载器
作用
- Bootstrap ClassLoader根类加载器,也被称为引导类加载器,负责Java核心类的加载。如:JDK中JRE的lib目录下的 rt.jar
- Extension ClassLoader 扩展类加载器。负责加载JRE的扩展目录中jar包的加载 JDK下JRE的lib目录下的ext目录
- Sysetm ClassLoader 系统类加载器,负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
特殊: User ClassLoader 自定义ClassLoader 只需要继承java.lang.ClassLoader 允许加载任意来源的字节码二进制流
上面的三个类加载器,只能加载本地字节码。自定义的可以加载任意来源
真因为此,所以那些动态代理,等技术才能实现
问题
遇到限定名一样的类,这么多类加载器会不会混乱?
- 双亲委派机制解决
- 父亲不能加载,儿子才尝试加载 (不是父类和子类,他们之间是逻辑关系)
- 说白了就是一层一层向上找,上面加载过相同的,下面就不在加载
不同的类加载器,除了读取二进制流的动作和范围不一样,后续的加载逻辑是否也不一样?
认为:除了Bootstrap ClassLoader,所有的非Bootstrap ClassLoader 都继承了 java.lang.Classloader,都由这个类的defineClass进行后续处理
jvm规范:每个类加载器都有属于自己的命名空间
破坏双亲委派
- 破坏双亲委派---->继承ClassLoader ---->重写loadClass 方法
package com;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
InputStream inputStream = this.getClass().getResourceAsStream(fileName);
if (inputStream == null){
return super.loadClass(name);
}
byte[] b = new byte[inputStream.available()];
inputStream.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException(name);
}
}
}
class Test{
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader();
Class<?> c1 = classLoader.loadClass("com.TestA");
Object o = c1.newInstance();
System.out.println(o.getClass()); //class com.TestA
System.out.println(o instanceof com.TestA); //false
}
}
类加载器—>总结
-
类加载过程中,除了读二进制流这个操作外,剩余逻辑都由jvm自己实现 -----> defineClass 是 final修饰
-
自定义内加载器时: 推荐使用 ClassLoader 下的 findClass 而不是直接重写loadClass 破坏了内部的双亲委派机制
- 因为loadClass 中实现的就是双亲委派机制
提出问题: -----> 那为什么不把 loadClass 方法也设置为final修饰?
- 双亲委派机制是 JDK1.2引入的特性
- 1.2之前classLoader已经存在
- 向下兼容
Tomcat JDBC … ---->破坏了双亲委派