博客
关于我
JVM原理-类加载机制
阅读量:171 次
发布时间:2019-02-28

本文共 4486 字,大约阅读时间需要 14 分钟。

类加载机制深入理解

一、概要

本次主要认知类加载的机制、详细介绍类加载器、类加载的过程以及类加载过程中的双亲委托原则。

二、类加载机制概述

在介绍类的加载机制之前,我们先看一下类加载机制在Java程序运行期间处于一个什么样的环节。从上图可以看出,Java文件通过Java编译器,编译成了字节码文件,接下来类加载器将这些字节码文件加载到虚拟机内存。但是哪些字节码文件在哪些时机需要加载到内存中,也就是类的加载时机是啥?

1. 类加载器的启动时机

类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它。如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

2. 类加载器的类型

JVM中类加载器主要有BootStrapClassLoader(引导类加载器)、ExtClassLoader(扩展类加载器)、AppClassLoader(应用类加载器)、CustomClassLoader(用户自定义类加载器)。

三、ClassLoader源码分析

双亲委托原则流程

双亲委托原则是Java类加载机制的重要组成部分。它决定了当一个类加载器无法找到所需类时,该如何递归查找。具体流程如下:

首先,自定义的加载器,先查找本地是否已经加载,如果没有加载,再利用递归,委托父类加载器检查是否已经加载,层层判断一直到Bootstrap ClassLoader;

其次,如果通过层层查找,发现都没有加载,再从顶层开始,判断各个加载器扫描的目录里面查找目标类:

  • BootStrap ClassLoader扫描%JAVA_HOME%\jre\lib中加载
  • ExtClassLoader扫描%JAVA_HOME%\jre\lib\ext中加载
  • AppClassLoader扫描%ClASSPATH%加载
  • 自定义类加载器,可以自定义扫描路径,自定义类加载器示例:
package com.jason.util.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoader extends ClassLoader {
private String mLibPath;
public MyClassLoader(String path) {
mLibPath = path;
}
protected Class
findClass(String name) throws ClassNotFoundException {
String fileName = getFileName(name);
File file = new File(mLibPath, fileName);
try {
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
while ((len = is.read()) != -1) {
bos.write(len);
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
private String getFileName(String name) {
int index = name.lastIndexOf('.');
if (index == -1) {
return name + ".class";
} else {
return name.substring(index) + ".class";
}
}
}
自定义类加载器使用示例:
package com.jason.util.classloader;
import java.lang.reflect.Method;
public class TestClassLoader {
public static void main(String[] args) {
MyClassLoader diskLoader = new MyClassLoader("D:\\");
try {
Class c = diskLoader.loadClass("TestHello");
if (c != null) {
try {
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("hello", null);
method.invoke(obj, null);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

四、类加载的过程

类加载过程分为五个阶段:加载、验证、准备、解析、初始化。

1. 加载阶段

加载阶段是类加载机制的第一个过程。在加载阶段,虚拟机主要完成三件事:

  • 通过一个类的全限定名来获取其定义的二进制字节流
  • 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
  • 在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口
  • 相对于类加载的其他阶段而言,加载阶段是可控性最强的阶段,因为程序员可以使用系统的类加载器加载,还可以使用自己的类加载器加载。我们在最后一部分会详细介绍这个类加载器。在这里我们只需要知道类加载器的作用就是上面虚拟机需要完成的三件事,仅此而已。

    2. 验证阶段

    验证的主要作用就是确保被加载的类的正确性。也是连接阶段的第一步。说白了也就是我们加载好的.class文件不能对我们的虚拟机有危害,所以先检测验证一下。他主要是完成四个阶段的验证:

  • 文件格式的验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证
  • 对整个类加载机制而言,验证阶段是一个很重要但是非必需的阶段,如果我们的代码能够确保没有问题,那么我们就没有必要去验证,毕竟验证需要花费一定的的时间。当然我们可以使用-Xverfity:none来关闭大部分的验证。

    3. 准备阶段

    准备阶段主要为类变量分配内存并设置初始值。这些内存都在方法区分配。在这个阶段我们只需要注意两点就好了,也就是类变量和初始值两个关键词:

  • 类变量(static)会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到Java堆中
  • 这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值。比如public static int value = 1;在这里准备阶段过后的value值为0,而不是1。赋值为1的动作在初始化阶段。
  • 4. 解析阶段

    解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程。符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好,就好比在班级中,老师可以用张三来代表你,也可以用你的学号来代表你,但无论任何方式这些都只是一个代号(符号),这个代号指向你(符号引用)

    直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

    5. 初始化阶段

    这是类加载机制的最后一步。在这个阶段,Java程序代码才开始真正执行。我们知道,在准备阶段已经为类变量赋过一次值。在初始化阶段,主要为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

  • 声明类变量是指定初始值
  • 使用静态代码块为类变量指定初始值
  • JVM初始化步骤:

  • 假如这个类还没有被加载和连接,则程序先加载并连接该类
  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类
  • 假如类中有初始化语句,则系统依次执行这些初始化语句
  • 类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • 创建类的实例,也就是new的方式
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如 Class.forName("com.shengsiyuan.Test"))
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类(JavaTest),直接使用java.exe命令来运行某个主类
  • 转载地址:http://visn.baihongyu.com/

    你可能感兴趣的文章
    Memcached:Node.js 高性能缓存解决方案
    查看>>
    memcache、redis原理对比
    查看>>
    memset初始化高维数组为-1/0
    查看>>
    Metasploit CGI网关接口渗透测试实战
    查看>>
    Metasploit Web服务器渗透测试实战
    查看>>
    MFC模态对话框和非模态对话框
    查看>>
    Moment.js常见用法总结
    查看>>
    MongoDB出现Error parsing command line: unrecognised option ‘--fork‘ 的解决方法
    查看>>
    mxGraph改变图形大小重置overlay位置
    查看>>
    MongoDB可视化客户端管理工具之NoSQLbooster4mongo
    查看>>
    Mongodb学习总结(1)——常用NoSql数据库比较
    查看>>
    MongoDB学习笔记(8)--索引及优化索引
    查看>>
    mongodb定时备份数据库
    查看>>
    mppt算法详解-ChatGPT4o作答
    查看>>
    mpvue的使用(一)必要的开发环境
    查看>>
    MQ 重复消费如何解决?
    查看>>
    mqtt broker服务端
    查看>>
    MQTT 保留消息
    查看>>
    MQTT 持久会话与 Clean Session 详解
    查看>>
    MQTT介绍及与其他协议的比较
    查看>>