JVM类加载器

JVM类加载器,第1张

一、JVM内存结构 1. 简图

2. 详图

二、类加载器简介 1. 基本介绍
  • 类加载器子系统: 只负责从文件系统或网络中加载Class文件,class文件在开头有特定的文件标识
1. ClassLoader只负责class文件的加载,至于是否可以运行,交给Execution Engine决定
2.  加载的类信息,存放在一块称为方法区的内存空间
    2.1 类信息
    2.2 存放运行时常量的信息(字符串字面量和数字常量)

2. 角色
1. class file  存在于本地硬盘上,可以理解为设计师画在纸上的模版
   最终这个模版在执行的时候,会加载到JVM中,根据模版,创造出n个一摸一样的实例

2. class file 通过二进制流的方式加载到jvm中,被称为DNA元数据模版,放在方法区

3. .class--jvm--元数据模板: 类装载器就是一个快递员的角色


三、类加载 1. Loading 1.1 文件来源
  • 从本地文件系统中直接加载
  • 通过网络获取
  • 从zip压缩包中读取,后来转换为jar,war格式
  • 运行时计算生成:动态代理
  • 由其他文件生成:jsp
  • 从加密文件中获取,防止Class文件被反编译的保护,在读取时候需要解密
1.2 Loading过程
1. 通过一个类的全限定类名,获取定义该类的二进制字节流
2. 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构(元数据)
3. 在堆内存中,生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
package com.nike.erick.d04;


import java.lang.reflect.Field;

public class ErickService {

    public String name;
    public String address;

    public String getName() {
        return "erick";
    }

    public static void main(String[] args) {

        Class<ErickService> serviceClass = ErickService.class;
        Field[] fields = serviceClass.getFields();
        for (Field field : fields) {
            System.out.println(field.getName());
        }
    }
}
2. Linking 2.1 Verify
  • 确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全
  • 文件格式校验,元数据校验,字节码验证,符号引用验证
1. CA FE BA BE: 通过《二进制文件查看器》,发现所有的 .class文件都会以这个开头(魔数)
2.2 Prepare
  • 为类变量分配内存并且设置该类变量的默认初始值,即零值
  • 不包含final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示式初始化
  • 不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象一起分配到Java堆中
2.3 Resolve
  • 比如一个.class文件,在准备的时候,需要对其父类等也要做一些初始化的准备工作
3. Initialization 3.1 类构造器方法
1. 初始化阶段就是: 执行类构造器方法 <clinit>()的过程
  
a. 该方法不需要定义, 是javac编译器自动收集类中的信息,并进行合并
   类变量(static)的赋值动作   静态代码块中的语句

b. 执行顺序:从源文件由上而下

c. 如果不包含上面两种数据,则就不会有clint()

# 因为收集顺序
d. 如果类有父类, JVM保证子类的 <clinit() 在执行前, 父类的 <clinit> 先去执行

# 因为 是用来加载类变量的,因此只需要加载一次,并在元空间缓存(方法区)
# 类加载天然线程安全
e. 虚拟机保证,一个类的<clinit>()方法在多线程下被同步加锁
  

在IDEA中装  jclasslib来查看
  • 包含方法
  • 不包含方法
  • 子父类的方法加载顺序
  • 多线程下同步加锁
package com.nike.erick.d05;

public class MultiThread {
    public static void main(String[] args) {
        new Thread(() -> {
            Erick erick = new Erick();
        }).start();

        new Thread(() -> {
            Erick erick = new Erick();
        }).start();
    }
}


class Erick {
    static {
        if (true) {
            System.out.println("开始初始化该类");
            while(true){

            }
        }
    }
} 
3.2 类的构造器
1. <clinit>() 和 类的构造器  不是一回事
2. 构造器是虚拟机视角下的  <init>方法
  • 无参构造器

  • 带参构造器
四、类加载器分类
  • 按照JVM规范定义
1. 引导类加载器(Bootstrap ClassLoader)
  • 只负责加载系统的核心类库,用于提供JVM自身的类(如String类)
  • 使用c,c++来实现的, 并不继承ClassLoader,没有父类加载器
  • 嵌套在JVM 里面的
  • 负责加载平台类加载器和系统类加载器(因为他们也是对象啊),同时指定为他们的父类加载器
  • 出于安全考虑,只加载包名为 java javax sun开头的类
  • 一个JVM里面只会有一个,打印的时候为null
2. 自定义类加载器(User-Defined ClassLoader)
  • 派生于抽象类ClassLoader的类加载器
  • 并不是说程序员自定义的类加载器
  • 使用java语言实现的
  • 父类加载器为BootstrapClassLoader(父类的意思是:你是由我来加载的, 通过getParent)
2.1 PlatformClassLoader(平台类类加载器)
  • jdk.internal.loader.ClassLoaders$PlatformClassLoader来实现
  • 从java指定目录下加载jar包(不是核心库,用户文件如果放在该目录,也会被该类加载)
2.2 AppClassLoader(系统类类加载器)
  • jdk.internal.loader.ClassLoaders$AppClassLoader实现
  • 负责加载环境变量classpath或系统属性
  • 程序中默认类加载器(也就是用户写的类)
  • 通过ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader()获取到类加载器
2.3 用户自定义类加载器
  • java的日常开发中,类的加载几乎都是通过上面三种类加载器相互配合执行
  • 必要时候,我们还可以自定义类加载器
1. 隔离加载类
2. 扩展加载源:比如从磁盘中读取一个类的.class
3. 修改类的加载方式
4. 防止源码泄漏:比如编译的时候加密了,那么加载的时候就需要解密


## 1. 自定义的流程
1. 继承抽象类java.class.lang.ClassLoader类的方式
2. findClass(): 具体的逻辑

如果没有复杂需求,直接继承URLClassLoader即可
package com.nike.erick.d05;

public class MultiThread {
    /*从SystemClassLoader => PlatformClassLoader => BootStrapClassLoader*/
    public static void main(String[] args) {

        /*引导类 类加载器: Bootstrap class loader*/
        ClassLoader stringClassLoader = String.class.getClassLoader();
        System.out.println(stringClassLoader);

        /* 系统类类加载器: AppClassLoader*/
        /*jdk.internal.loader.ClassLoaders$AppClassLoader@2c13da15*/
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        /*平台类类加载器:PlatformClassLoader*/
        /*jdk.internal.loader.ClassLoaders$PlatformClassLoader@5a39699c*/
        ClassLoader platformClassLoader = systemClassLoader.getParent();
        System.out.println(platformClassLoader);

        /*null*/
        ClassLoader bootstrapClassLoader = platformClassLoader.getParent();
        System.out.println(bootstrapClassLoader);

        /*用户自定义的类 的类加载器
        1. jdk.internal.loader.ClassLoaders$AppClassLoader@2c13da15
        2. 系统类的类加载器,一个JVM中只会存在一个*/
        ClassLoader erickClassLoader = MultiThread.class.getClassLoader();
        System.out.println(erickClassLoader);
    }
}
五、双亲委派机制 1. 案列

2. 原理
  • JVM对Class文件采用 按需加载的方式,懒加载
  • 加载时候用的是双亲委派机制,即把请求交给父类处理

2.1 不能加载


3. 优点
  • 避免类被重复加载
  • 保护程序安全,防止核心API被随意篡改

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/721923.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存