java类加载、类加载器

Java类加载
概述:
  • 类加载:就是把一份被javac编译过的class文本文件,通过加载,生成某种形式的class数据结构进入内存,程序可以调用这个数据结构来构造出object
  • 这个过程是在运行时进行的----------->java动态扩展性的根基

Java类生命周期

注: 解析部分是灵活的,可以在初始化前,(静态解析)。也可以在初始化之后(动态解析

一: 加载
  • 读取class文件,将其转化为某种静态数据结构存储在方法区内,并在堆中生成一个便于用户调用的java.lang.Class类型的对象的过程
  • 对文件格式验证 ----- 双亲委派机制

注: 这里读的class文件不一定指本地文件,泛指各种二进制流

例: 来自网络,来自数据库,甚至是即时生成的class

如: 动态代理技术 -----> 使用即时计算出来的class,然后实例化对象

二:连接
2.1、验证

对方法区中的class静态结构做验证

  1. 元数据验证
  2. 字节码验证

**概括:**简单说就是对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 系统类加载器

作用
  1. Bootstrap ClassLoader根类加载器,也被称为引导类加载器,负责Java核心类的加载。如:JDK中JRE的lib目录下的 rt.jar
  2. Extension ClassLoader 扩展类加载器。负责加载JRE的扩展目录中jar包的加载 JDK下JRE的lib目录下的ext目录
  3. 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
    }
}
类加载器—>总结
  1. 类加载过程中,除了读二进制流这个操作外,剩余逻辑都由jvm自己实现 -----> defineClass 是 final修饰

  2. 自定义内加载器时: 推荐使用 ClassLoader 下的 findClass 而不是直接重写loadClass 破坏了内部的双亲委派机制

    • 因为loadClass 中实现的就是双亲委派机制

提出问题: -----> 那为什么不把 loadClass 方法也设置为final修饰?

  • 双亲委派机制是 JDK1.2引入的特性
  • 1.2之前classLoader已经存在
  • 向下兼容

Tomcat JDBC … ---->破坏了双亲委派

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值