一文教你读懂JVM的类加载机制

- 加载
- 连接(验证,准备,解析)
- 初始化

I.类加载流程
1. 加载
- 根据类的全局限定名找到.class文件,生成对应的二进制字节流。
- 将静态存储结构转换为运行时数据结构,保存运行时数据结构到JVM内存方法区中。
- JVM创建java.lang.Class类型的对象,保存于堆(Heap)中。利用该对象,可以获取保存于方法区中的类信息,例如:类名称,父类名称,方法和变量等信息。
package com.demo; import java.lang.reflect.Field;
import java.lang.reflect.Method; public class ClassLoaderExample {
public static void main(String[] args) {
StringOp stringOp = new StringOp(); System.out.println("Class Name: " + stringOp.getClass().getName());
for(Method method: stringOp.getClass().getMethods()) {
System.out.println("Method Name: " + method.getName());
}
for (Field field: stringOp.getClass().getDeclaredFields()) {
System.out.println("Field Name: " + field.getName());
}
}
}
package com.demo;
public class StringOp {
private String displayName;
private String address;
public String getDisplayName() {
return displayName;
}
public String getAddress() {
return address;
}
}
output:
Class Name: com.demo.StringOp
Method Name: getAddress
Method Name: getDisplayName
Field Name: displayName
Field Name: address
StringOp stringOp1 = new StringOp();
StringOp stringOp2 = new StringOp();
System.out.println(stringOp1.getClass() == stringOp2.getClass());
//output: true
2. 连接
2.1 验证
- 文件格式:验证文件的格式是否符合规范,如果符合规范,则将对应的二进制字节流存储到JVM内存的方法区中;否则抛出java.lang.VerifyError异常。
- 元数据:对字节码的描述信息进行语义分析,确保符合Java语言规范。例如:是否有父类;是否继承了不允许继承的类(final修饰的类);如果是实体类实现接口,是否实现了所有的方法;等。。
- 字节码:验证程序语义是否合法,确保目标类的方法在被调用时不会影响JVM的正常运行。例如int类型的变量是否被当成String类型的变量等。
- 符号引用:目标类涉及到其他类的的引用时,根据引用类的全局限定名(例如:import com.demo.StringOp)能否找到对应的类;被引用类的字段和方法是否可被目标类访问(public, protected, package-private, private)。这里主要是确保后续目标类的解析步骤可以顺利完成。
2.2 准备
public class CustomClassLoader {
//加载CustomClassLoader类时,便会为var1变量分配内存
//准备阶段,var1赋值256
public static final int var1 = 256;
//加载CustomClassLoader类时,便会为var2变量分配内存
//准备阶段,var2赋值0, 初始化阶段赋值128
public static int var2 = 128;
//实例化一个CustomClassLoader对象时,便会为var1变量分配内存和赋值
public int var3 = 64;
}
|
数据类型
|
默认值
|
|
int
|
0
|
|
float
|
0.0f
|
|
long
|
0L
|
|
double
|
0.0d
|
|
short
|
(short)0
|
|
char
|
'\u0000'
|
|
byte
|
(byte)0
|
|
String
|
null
|
|
boolean
|
false
|
|
ArrayList
|
null
|
|
HashMap
|
null
|
2.3 解析
- 符号引用(Symbolic Reference):描述所引用目标的一组符号,使用该符号可以唯一标识到目标即可。比如引用一个类:com.demo.CustomClassLoader,这段字符串就是一个符号引用,并且引用的对象不一定事先加载到内存中。
- 直接引用(Direct Reference):直接指向目标的指针,相对偏移量或者一个能间接定位到目标的句柄。根据直接引用的定义,被引用的目标一定事先加载到了内存中。
3. 初始化
- <init>():对象构造器方法,用于初始化实例对象
- 实例对象的constructor(s)方法,和非静态变量的初始化;
- 执行new创建实例对象时使用。
- <clinit>():类构造器方法,用于初始化类
- 类的静态语句块和静态变量的初始化;
- 类加载的初始化阶段执行。
public class ClassLoaderExample {
private static final Logger logger = LoggerFactory.getLogger(ClassLoaderExample.class);//<clinit>
private String property = "custom"; //<init>
//<clinit>
static {
System.out.println("Static Initializing...");
}
//<init>
ClassLoaderExample() {
System.out.println("Instance Initializing...");
}
//<init>
ClassLoaderExample(String property) {
this.property = property;
System.out.println("Instance Initializing...");
}
}
Code:
0 aload_0 //将局部变量表中第一个引用加载到操作树栈
1 invokespecial #1 <java/lang/Object.<init>> //调用java.lang.Object的实例初始化方法
4 aload_0 //将局部变量表中第一个引用加载到操作树栈
5 ldc #2 <custom> //将常量custom从常量池第二个位置推送至栈顶
7 putfield #3 <com/kaiwu/ClassLoaderExample.property> //设置com.kaiwu.ClassLoaderExample实例对象的property字段值为custom
10 getstatic #4 <java/lang/System.out> //从java.lang.System类中获取静态字段out
13 ldc #5 <Instance Initializing...> //将常量Instance Initializing...从常量池第5个位置推送至栈顶
15 invokevirtual #6 <java/io/PrintStream.println> //调用java.io.PrintStream对象的println实例方法,打印栈顶的Instance Initializing...
18 return //返回
Code:
0 aload_0 //将局部变量表中第一个引用加载到操作树栈
1 invokespecial #1 <java/lang/Object.<init>> //调用java.lang.Object的实例初始化方法
4 aload_0 //将局部变量表中第一个引用加载到操作树栈
5 ldc #2 <custom> //将常量custom从常量池第二个位置推送至栈顶
7 putfield #3 <com/kaiwu/ClassLoaderExample.property> //将常量custom赋值给com.kaiwu.ClassLoaderExample实例对象的property字段
10 aload_0 //将局部变量表中第一个引用加载到操作树栈
11 aload_1 //将局部变量表中第二个引用加载到操作树栈
12 putfield #3 <com/kaiwu/ClassLoaderExample.property> //将入参property赋值给com.kaiwu.ClassLoaderExample实例对象的property字段
15 getstatic #4 <java/lang/System.out> //从java.lang.System类中获取静态字段out
18 ldc #5 <Instance Initializing...> //将常量Instance Initializing...从常量池第5个位置推送至栈顶
20 invokevirtual #6 <java/io/PrintStream.println> //调用java.io.PrintStream对象的println实例方法, 打印栈顶的Instance Initializing...
23 return //返回
Code:
0 ldc #7 <com/kaiwu/ClassLoaderExample> //将com.kaiwu.ClassLoaderEexample的class_info常量从常量池第七个位置推送至栈顶
2 invokestatic #8 <org/slf4j/LoggerFactory.getLogger> //从org.slf4j.LoggerFactory类中获取静态字段getLogger
5 putstatic #9 <com/kaiwu/ClassLoaderExample.logger> //设置com.kaiwu.ClassLoaderExample类的静态字段logger
8 getstatic #4 <java/lang/System.out> //从java.lang.System类中获取静态字段out
11 ldc #10 <Static Initializing...> //将常量Static Initializing...从常量池第10个位置推送至栈顶
13 invokevirtual #6 <java/io/PrintStream.println> //调用java.io.PrintStream对象的println实例方法, 打印栈顶的Static Initializing...
16 return //返回
II. 类加载器
1. 类加载器ClassLoader
public static void printClassLoader() {
// StringOP:自定义类
System.out.println("ClassLoader of StringOp: " + StringOp.class.getClassLoader());
// com.sun.javafx.binding.Logging:Java核心类扩展的类
System.out.println("ClassLoader of Logging: " + Logging.class.getClassLoader());
// java.lang.String: Java核心类
System.out.println("ClassLoader of String: " + String.class.getClassLoader());
}
output:
ClassLoader of StringOp: sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader of Logging: sun.misc.Launcher$ExtClassLoader@7c3df479
ClassLoader of String: null
- 启动类加载器:本地代码(C++语言)实现的类加载器,负责加载JDK内部类(通常是$JAVA_HOME/jre/lib/rt.jar和$JAVA_HOME/jre/lib目录中的其他核心类库)或者-Xbootclasspath选项指定的jar包到内存中。该加载器是JVM核心的一部分,以本机代码编写,开发者无法获得启动类加载器的引用,所以上述java.lang.String类的加载为null。此外,该类充当所有其他java.lang.Class Loader实例共同的父级(区别为是否为直接父级),它加载所有直接子级的java.lang.ClassLoader类(其他子类逐层由直接父级类加载器加载)。
- 扩展类加载器:启动类加载器的子级,由Java语言实现的,用来加载JDK扩展目录下核心类的扩展类(通常是$JAVA_HOME/lib/ext/*.jar)或者-Djava.ext.dir系统属性中指定的任何其他目录中存在的类到内存中。由sun.misc.Launcher$ExtClassLoader类实现,开发者可以直接使用扩展类加载器。
- 应用/系统类加载器:扩展类加载器的子级,负责将java -classpath/-cp($CLASSPATH)或者-Djava.class.path变量指定目录下类库加载到JVM内存中。由sun.misc.Launcher$AppClassLoader类实现,开发者可以直接使用系统类加载器。
2. 类加载器的类图关系




3. 双亲委派机制
- 使用双亲委派模式可以避免类的重复加载:当父级加载器已经加载了目标类,则子加载器没有必要再加载一次。
- 避免潜在的安全风险:启动类加载器是所有其他加载器的共同父级,所以java的核心类库不会被重复加载,意味着核心类库不会被随意篡改。例如我们自定义名为java.lang.String的类,通过双亲委派模式进行加载类,通过上述流程图,启动类加载器会发现目标类已经加载,直接返回核心类java.lang.String,而不会通过应用/系统类加载器加载自定义类java.lang.String。当然,一般而言我们是不可以加载全局限定名与核心类同名的自定义类,否则会抛出异常:java.lang.SecurityException: Prohibited package name: java.lang。
- 当加载器收到加载类的请求时,首先会根据该类的全局限定名查目标类是否已经被加载,如果加载则万事大吉;
- 如果没有加载,查看是否有父级加载器,如果有则将加载类的请求委托给父级加载器;
- 依次递归;
- 直到启动类加载器,如果在已加载的类中依旧找不到该类,则由启动类加载器开始尝试从所负责的目录下寻找目标类,如果找到则加载到JVM内存中;
- 如果找不到,则传输到子级加载器,从负责的目录下寻找并加载目标类;
- 依次递归;
- 直到请求的类加载器依旧找不到,则抛出java.lang.ClassNotFoundException异常。






一文教你读懂JVM的类加载机制的更多相关文章
- 一文教你读懂JVM类加载机制
Java运行程序又被称为WORA(Write Once Run Anywhere,在任何地方运行只需写入一次),意味着我们程序员小哥哥可以在任何一个系统上开发Java程序,但是却可以在所有系统上畅通运 ...
- 大白话谈JVM的类加载机制
前言 我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢. 如果懂得了JVM的运行原理和内存模型,像是一些JVM调优.垃圾回收机制等等的问题我们才 ...
- JVM内存结构 JVM的类加载机制
JVM内存结构: 1.java虚拟机栈:存放的是对象的引用(指针)和局部变量 2.程序计数器:每个线程都有一个程序计数器,跟踪代码运行到哪个位置了 3.堆:对象.数组 4.方法区:字节流(字节码文件) ...
- JVM之类加载机制
JVM之类加载机制 JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 类加载五部分 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这 ...
- JVM的类加载机制全面解析
什么是类加载机制 JVM把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型,这就是JVM的类加载机制. 如果你对Class文件的结 ...
- JVM学习——类加载机制(学习过程)
JVM--类加载机制 2020年02月07日14:49:19-开始学习JVM(Class Loader) 类加载机制 类加载器深入解析与阶段分解 在Java代码中,类型的加载.连接与初始化过程中都是在 ...
- JVM的类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 类加载的过程: 包括加载.链接(含验证.准备 ...
- 【JVM】类加载机制
原文:[深入Java虚拟机]之四:类加载机制 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 类加 ...
- 深入理解JVM(3)——类加载机制
1.类加载时机 类的整个生命周期包括了:加载( Loading ).验证( Verification ).准备( Preparation ).解析( Resolution ).初始化( Initial ...
随机推荐
- 下载com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
看别人都说在repo.maven.com下载,没想到竟然要登录 索性我直接在国内阿里云的镜像仓库下载好了,速度又快又方便 搜索aspectj 下载地址:https://maven.aliyun.com ...
- Coposition 详解
LifeCycle Hooks 在新版的生命周期函数,可以按需导入到组件中,且只能在setup()函数中使用. import { onMounted, onUnmounted } from 'vue' ...
- C++中tuple类型
tuple是C++11新标准里的类型.它是一个类似pair类型的模板.pair类型是每个成员变量各自可以是任意类型,但是只能有俩个成员,而tuple与pair不同的是它可以有任意数量的成员.但是每个确 ...
- SpringCloud组件
1.Hystrix 1.1.简介 Hystrix,英文意思是豪猪,全身是刺,看起来就不好惹,是一种保护机制. Hystrix也是Netflix公司的一款组件. 主页:https://github.co ...
- 【图像处理】使用OpenCV+Python进行图像处理入门教程(二)
这篇随笔介绍使用OpenCV进行图像处理的第二章 图像的运算,让我们踏上继续回顾OpenCV进行图像处理的奇妙之旅,不断地总结.回顾,以新的视角快速融入计算机视觉的奥秘世界. 2 图像的运算 复杂的 ...
- 剑指 Offer 52. 两个链表的第一个公共节点 + 链表 + 第一个公共结点 + 双指针
剑指 Offer 52. 两个链表的第一个公共节点 Offer_52 题目详情 题解分析 可以使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结 ...
- CCF(元素选择器:50分):字符串+模拟
元素选择器 201809-3 这里我只考虑了没有后代选择器的情况 #include<iostream> #include<cstdio> #include<cstring ...
- 【资源下载】Linux下的Hi3861一站式鸿蒙开发烧录(附工具)
下载附件 2021春节前夕,华为发布了 HUAWEI DevEco Device Tool 2.0 Beta1,整体提供了异常强大的功能.得知消息后,我在第一时间带着无比兴奋的心情下载尝鲜,但结果却是 ...
- sql之对top语句理解
起因 事情的起因是在群里和大佬讨论问题,讨论select top x后加*的问题,这时突发奇想,想搞清楚这个看起来在sql中略显突兀的语句结构,就有了这篇文 先看例子 select * from 表 ...
- TiDB在更新版本的时候初始化Prometheus的配置文件失败
一.背景是更换版本了之后,按照正常扩容节点也会报错. 我们安装的TiDB版本是v4.0.0,因为环境还在试用阶段,所以会经常增删节点.原因是我们违背官方说明,强行用机械盘上了,跑不过单机的mysql, ...