jvm4-类加载机制

一、jvm类加载

1、概念

将类的.class文件中的二进制数据读入到内存中,具体是在方法区(元空间)创建一个java.lang.Class对象,用来封装类在方法区内的数据结构;

类的加载的最终产品是位于方法区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

2、JVM类加载机制

•全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
•父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
•缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

3、类加载阶段

3.1 加载

将类的字节码载入方法区(元空间)中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
_java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用(即我们常用的class对象
_super 即父类
_fields 即成员变量
_methods 即方法
_constants 即常量池
_class_loader 即类加载器
_vtable 虚方法表
_itable 接口方法表
注意:
1)如果这个类还有父类没有加载,先加载父类
2)类的静态变量在堆中(1.7以前是在方法区)
在这里插入图片描述

3.2 链接

3.2.1 验证
验证类是否符合 JVM规范,安全性检查
3.2.2 准备
为 static 变量分配空间,设置默认值

·1)static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
·2)如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
·3)如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成

3.2.3 解析
将常量池中的符号引用解析为直接引用

3.3 初始化

初始化即调用 ()V ,虚拟机会保证这个类的『构造方法』的线程安全
1)初始化时机
·main 方法所在的类,总会被首先初始化
·首次访问这个类的静态变量或静态方法时
·子类初始化,如果父类还没初始化,会引发
·子类访问父类的静态变量,只会触发父类的初始化
·Class.forName
·new 会导致初始化
2)不会初始化
·访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
·类对象.class 不会触发初始化
·创建该类的数组不会触发初始化
3) 典型应用 - 完成懒惰初始化单例模式(初始化线程安全有保障)

public final class Singleton {
	private Singleton() { }
	// 内部类中保存单例
	private static class LazyHolder {
		static final Singleton INSTANCE = new Singleton();
	}
	// 第一次调用 getInstance 方法,才会导致内部类加载和初始化其静态成员
	public static Singleton getInstance() {
		return LazyHolder.INSTANCE;
	}
}

二、类加载器

在这里插入图片描述

1、启动类加载器Bootstrap

2、扩展类加载器 Extension

3、应用类加载器 Application

3.1 双亲委派机制

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
在这里插入图片描述

3.2 委派作用

1)防止重复加载
2)保证核心.class不被篡改

3.3源码

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 1)首先检查这个classsh是否已经加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //2) c==null表示没有加载,如果有父类的加载器则让父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //3)如果父类的加载器为空 则说明递归到bootStrapClassloader了
                        //bootStrapClassloader比较特殊无法通过get获取
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {}
                if (c == null) {
                    //4)如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

4、线程上下文类加载器Thread Context ClassLoader

4.1 上下文加载器场景

Q: 越基础的类由越上层的加载器进行加载,如果基础类又要调用回用户的代码,那该怎么办?
解决方案:使用“线程上下文类加载器”

Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

4.2 spi

1)Java SPI就是提供这样的一个服务发现机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外。破坏了“双亲委派模型”
2)具体约定为:
当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。
该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
3)jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。
public static <S> ServiceLoader<S> load(Class<S> service) {
	    ClassLoader cl = Thread.currentThread().getContextClassLoader();
	    return ServiceLoader.load(service, cl);
}
	```
	
## 	5、自定义类加载器
### 5.1场景
	1)想加载非 classpath 随意路径中的类文件
	2)都是通过接口来使用实现,希望解耦时,常用在框架设计
	3)这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器

### 5.2步骤
	1)继承 ClassLoader 父类
	2)要遵从双亲委派机制,重写 findClass 方法
		注意不是重写 loadClass 方法,否则不会走双亲委派机制
	3)读取类文件的字节码
	4)调用父类的 defineClass 方法来加载类
	5)使用者调用该类加载器的 loadClass 方法


	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值