JVM类加载机制

一、类的加载过程(从磁盘到内存的过程)

1、整体过程概述

我们首先以一个类举例,一个类的加载首先是通过某个main函数启动程序时,通过类加载器把主类加载到jvm中,如:

package com.gaorufeng.jvm;public class Main {public static final int initData = 123;public static User user = new User();public int compute() {int a  = 1;int b = 2;int c = (a + b) * 10;return c;}public static void main(String[] args) {Main main = new Main();main.compute();}
}

当我们点击运行时,编译器会使用javac命令把java文件编译成class文件,通过java命令执行过程如下:

2、调用loadClass()方法具体过程

在调用loadClass()方法进行类加载时,有以下几个步骤:加载、验证、准备、解析、初始化、使用、卸载,具体说明如下:

  • 加载:获取class字节码文件的二进制字节流,使用到的类时才会加载;将磁盘文件静态结构载入内存方法区转换为运行时数据结构<类信息>;将载入后的类信息进行组装,在堆空间中生成类信息(class),作为数据入口
  • 验证:验证文件格式/元数据/字节码/符号引用等
  • 准备:根据验证后的类信息初始化类结构变量,给静态变量分配内存,并赋予默认值(比如int默认是0,boolean默认false,对象默认null)
  • 解析:常量池符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用(比如一个类有很多实现,当真的调用时才知道)
  • 初始化:给类的静态变量初始化为指定的值,执行静态代码块,执行类构造器<clinit>()生成class对象放入堆中

下面用一个图形象化一点类加载过程:

 类被加载到方法区中后主要包含:运行时常量池、类型信息、字段信息、方法信息、类加载的引用、对应class实例的引用等信息

类加载的引用:当前类到类加载实例的引用

对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的class类型的对象实例放到堆中(Heap)中,作为开发人员访问方法区中类定义的入口和切入点

注意,主类在运行过程中如果使用到其它类,会逐步加载这些类jar包或war包里的类不是一次性全部加载,是使用时才加载

public class TestDynamicLoad {static {System.out.println("load TestDynamicLoad===============>");}public static void main(String[] args) {new A();System.out.println("load space======================>");B b = null; // B没有真正的用到}
}class A {static {System.out.println("load A=========================>");}public A() {System.out.println("initial A====================>");}
}class B {static {System.out.println("load B=============================>");}public B() {System.out.println("initial B=====================>");}
}

打印结果:

load TestDynamicLoad===============>
load A=========================>
initial A====================>
load space======================>

二、类加载器和双亲委派机制

1、类加载器

1)、类加载器概述

上面的类加载过程主要是通过类加载器来实现,Java里有如下几种类加载器

  • 引导类加载器:负责加载支撑jvm运行的位于jre的lib目录下的核心类库,比如rt.jar、charset.jar等
  • 扩展类加载器:负责加载支撑jvm运行的位于jre的lib目录下的ext扩展目录中的jar类包
  • 应用程序类加载器:负责加载ClassPath路径下的类包,通常是程序员自己写的类
  • 自定义类加载器:负责加载用户自定义路径下的类包

类加载器信息打印:

public class TestJDKClassLoader {public static void main(String[] args) {System.out.println(String.class.getClassLoader());System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());System.out.println();ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();ClassLoader extClassLoader = appClassLoader.getParent();ClassLoader bootstrapClassLoader = extClassLoader.getParent();System.out.println("the bootstrapClassLoader==>" + bootstrapClassLoader);System.out.println("the extClassLoader==>" + extClassLoader);System.out.println("the appClassLoader==>" + appClassLoader);System.out.println();System.out.println("bootstrapClassLoader加载以下文件:");URL[] urLs = Launcher.getBootstrapClassPath().getURLs();for (int i = 0; i < urLs.length; i++) {System.out.println(urLs[i]);}System.out.println();System.out.println("extClassLoader加载以下文件:");System.out.println(System.getProperty("java.ext.dirs"));System.out.println();System.out.println("appClassLoader加载以下文件:");System.out.println(System.getProperty("java.class.path"));}
}

 打印结果(引导类加载器是由c++实现,所以打印出来是null,其它类加载器均显示正常):

null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the bootstrapClassLoader==>null
the extClassLoader==>sun.misc.Launcher$ExtClassLoader@72ea2f77
the appClassLoader==>sun.misc.Launcher$AppClassLoader@18b4aac2

bootstrapClassLoader加载以下文件:
file:/D:/jdk/jdk1.8.0_271/jre/lib/resources.jar
file:/D:/jdk/jdk1.8.0_271/jre/lib/rt.jar
file:/D:/jdk/jdk1.8.0_271/jre/lib/sunrsasign.jar
file:/D:/jdk/jdk1.8.0_271/jre/lib/jsse.jar
file:/D:/jdk/jdk1.8.0_271/jre/lib/jce.jar
file:/D:/jdk/jdk1.8.0_271/jre/lib/charsets.jar
file:/D:/jdk/jdk1.8.0_271/jre/lib/jfr.jar
file:/D:/jdk/jdk1.8.0_271/jre/classes

extClassLoader加载以下文件:
D:\jdk\jdk1.8.0_271\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

appClassLoader加载以下文件:
D:\jdk\jdk1.8.0_271\jre\lib\charsets.jar;D:\jdk\jdk1.8.0_271\jre\lib\deploy.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\access-bridge-64.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\cldrdata.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\dnsns.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\jaccess.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\jfxrt.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\localedata.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\nashorn.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\sunec.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\sunjce_provider.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\sunmscapi.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\sunpkcs11.jar;D:\jdk\jdk1.8.0_271\jre\lib\ext\zipfs.jar;D:\jdk\jdk1.8.0_271\jre\lib\javaws.jar;D:\jdk\jdk1.8.0_271\jre\lib\jce.jar;D:\jdk\jdk1.8.0_271\jre\lib\jfr.jar;D:\jdk\jdk1.8.0_271\jre\lib\jfxswt.jar;D:\jdk\jdk1.8.0_271\jre\lib\jsse.jar;D:\jdk\jdk1.8.0_271\jre\lib\management-agent.jar;D:\jdk\jdk1.8.0_271\jre\lib\plugin.jar;D:\jdk\jdk1.8.0_271\jre\lib\resources.jar;D:\jdk\jdk1.8.0_271\jre\lib\rt.jar;D:\selfworkspace\javaworkspace\jvm-classload\target\classes;D:\maven\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;D:\idea\ideaIU-2021.1.3.win\lib\idea_rt.jar

2)、类加载器初始化过程

参考类运行加载全过程图可知其中会创建jvm启动器实例sun.misc.Launcher。sun.misc.Launcher初始化使用了单例模式设计,保证一个jvm虚拟机内只有一个sun.misc.Launcher实例。

在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用程序类加载器)。

jvm默认使用了Launcher和getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序

public Launcher() {Launcher.ExtClassLoader var1;try {// 构造扩展类加载器,在构造的过程中将其父加载器设置为nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {// 构造应用程序类加载器,在构造的过程中将其父加载器设置为ExtClassLoader// Launcher的loader属性就是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");//…此处省略不相关代码}

2、双亲委派机制 

 这里类加载其实就有双亲委派机制,加载某个类时,首先类加载本身不会去加载,会一层层向上委托给它的父加载器寻找目标类,直到委托给引导类加载还未找到,再有引导类加载器和扩展类加载器加载(一般这两个都加载自己的类,你如果写个包路径相同的类,那你这个类根本就不会被加载到,因为已经被引导类加载器和扩展类加载器加载返回了),最后在AppClassLoader去加载;通常情况下都是通过AppClassLoader去加载,首先看下类加载器源码:AppClassLoader的loadClass()方法最终会调用父类ClassLoader的loadClass()方法,该方法的逻辑大致如下:

  1. 首先检查一下指定名称的类是否已经被加载过了,如果加载过了,就不需要再加载,直接返回
  2. 如果此类没有加载过,那么再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name,false);)或者是调用bootstrap类加载器来加载
  3. 如果父加载器及bootstrap类加载器都没找到指定的类,那么调用当前类加载器的findClass()方法来完成类的加载
// ClassLoader的loadClass()方法,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded// 检查当前类是否已经加载了该类Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {// 如果当前加载器父加载器不为空 则委托父加载器加载该类c = parent.loadClass(name, false);} else {// 如果当前加载器父加载器为空 则委托引导类加载器加载该类c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 都会调用URLClassLoader的findClass()方法在加载器的类路径里查找并加载该类c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

3、为什么要设计双亲委派机制

  • 沙箱安全机制安全机制:程序员写一个和jdk相同包路径的类不会被加载,保证核心api不会被篡改
  • 避免重复加载:父加载器已经加载过的直接返回,不往下找子加载器加载,保证了加载类的唯一性

举例说明:

package java.lang;public class String {public static void main(String[] args) {System.out.println("test");}
}

 运行结果:

错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

原因:String类是jdk核心类库,是在引导类加载器就已经被加载了,返回的是核心类库的String类,所以是没有main()方法的

4、全盘委托机制

“全盘负责”是指当一个ClassLoader装载一个类时,除非显示的使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入

3、自定义类加载器

自定义类加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass(),默认实现是空方法,所以我们自定义类加器主要重写findClass()方法

User1类不能放在我们项目类路径下,否则就会被应用程序类加载器加载直接返回,自定义类加载器就在不到了

public class User1
{private int id;private String name;public User1() {}public User1(int id, String name){this.id = id;this.name = name;}public int getId(){return this.id;}public void setId(int id){this.id = id;}public String getName(){return this.name;}public void setName(String name){this.name = name;}public void sout(){System.out.println("自定义类加载器加载=================>");}
}
public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classpath;public MyClassLoader(String classpath) {this.classpath = classpath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");int available = fileInputStream.available();byte[] bytes = new byte[available];fileInputStream.read(bytes);fileInputStream.close();return bytes;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] bytes = loadByte(name);return defineClass(name, bytes, 0, bytes.length);} catch (Exception e) {throw new ClassNotFoundException();}}}public static void main(String[] args) throws Exception {MyClassLoader myClassLoader = new MyClassLoader("E:");Class<?> aClass = myClassLoader.loadClass("com.gaorufeng.jvm.User1");Object obj = aClass.newInstance();Method method = aClass.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(aClass.getClassLoader().getClass().getName());}
}

打印结果:

自定义类加载器加载=================>
com.gaorufeng.jvm.MyClassLoaderTest$MyClassLoader

为什么说自定义类加载器的父加载器是应用程序类加载器?

我们继承ClassLoader在创建MyClassLoader是肯定会调用ClassLoader类的构造方法,如下所示,getSystemClassLoader()方法中initSystemClassLoader()方法把scl设置成AppClassLoader

 设置父加载器的构造方法

 再看getSystemClassLoader()方法返回的是什么

 

4、打破双亲委派机制

java核心包是不允许我们去加载的,上面例子找不到main()方法是因为加载核心类库的String类,这次我们自定义加载类去加载我们自己的同包名String类

public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classpath;public MyClassLoader(String classpath) {this.classpath = classpath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");int available = fileInputStream.available();byte[] bytes = new byte[available];fileInputStream.read(bytes);fileInputStream.close();return bytes;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] bytes = loadByte(name);return defineClass(name, bytes, 0, bytes.length);} catch (Exception e) {e.printStackTrace();}return null;}@Overrideprotected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}if (resolve) {resolveClass(c);}return c;}}}public static void main(String[] args) throws Exception {MyClassLoader myClassLoader = new MyClassLoader("E:/test");Class<?> aClass = myClassLoader.loadClass("java.lang.String");Object obj = aClass.newInstance();Method method = aClass.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(aClass.getClassLoader().getClass().getName());}
}

打印结果:

java.lang.SecurityException: Prohibited package name: java.lang
    at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
    at com.gaorufeng.jvm.MyClassLoaderTest$MyClassLoader.findClass(MyClassLoaderTest.java:29)
    at com.gaorufeng.jvm.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:47)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at com.gaorufeng.jvm.MyClassLoaderTest.main(MyClassLoaderTest.java:64)
Exception in thread "main" java.lang.NullPointerException
    at com.gaorufeng.jvm.MyClassLoaderTest.main(MyClassLoaderTest.java:66)

其中defindClass()方法会调用preDefineClass()方法检测java开头的都不允许自定义类加载器加载

如果要加载指定的类还需要重写ClassLoader类的loadClass()方法,根据包名加载,附上loadClass()方法代码:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//非自定义的类还是走双亲委派加载if (!name.startsWith("com.gaorufeng.jvm")){c = this.getParent().loadClass(name);}else{c = findClass(name);}// this is the defining class loader; record the statssun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}if (resolve) {resolveClass(c);}return c;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/3620.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

远程桌面发生身份验证错误,要求的函数不受支持

windows10专业版&#xff1a; 解决方法&#xff1a; 使用微软官方建议修改本地组策略&#xff1a; winr弹出运行窗口&#xff0c;输入gpedit.msc,打开本地组策略 计算机配置>管理模板>系统>凭据分配>加密Oracle修正 选择启用并选择易受攻击。 windows10家庭版&a…

如何调用百度地图API

前言 要调用百度地图API&#xff0c;步骤操作如下 注册并创建一个API密钥。您可以在百度地图API控制台上创建您的密钥。选择要使用的API服务。百度地图API提供了多种服务&#xff0c;包括地图展示、路线规划、地点搜索、实时交通等。您可以在百度地图API控制台上查看所有可用…

【Linux进程】进程状态 {进程状态的介绍,进程状态的转换,Linux中的进程状态,浅度睡眠VS深度睡眠,僵尸进程VS孤儿进程,调度器的作用}

进程状态 一、基本进程状态 1.1 进程状态介绍 创建状态&#xff1a;当一个进程被创建时&#xff0c;它处于创建状态。在这个阶段&#xff0c;操作系统为进程分配必要的资源&#xff08;将代码和数据拷贝到内存&#xff0c;创建PCB结构体等&#xff09;&#xff0c;并为其分配一…

从0到1精通自动化测试,pytest自动化测试框架,配置文件pytest.ini(十三)

一、前言 pytest配置文件可以改变pytest的运行方式&#xff0c;它是一个固定的文件pytest.ini文件&#xff0c;读取配置信息&#xff0c;按指定的方式去运行 二、ini配置文件 pytest里面有些文件是非test文件pytest.ini pytest的主配置文件&#xff0c;可以改变pytest的默认…

Android Binder通信原理(三):service注册

源码基于&#xff1a;Android R 0. 前言 上一文中详细分析了servicemanger 的启动流程&#xff0c;我们知道 servicemanager 作为 binder 机制中的一个特殊service&#xff0c;利用service map管理所有service&#xff0c;也是所有binder 通信的入口。 本文着重分析 service …

智能文档图像处理技术应用与实践

写在前面智能文档处理面临的技术难题智能文档处理的研究领域● 文档图像分析与预处理● 手写板反光擦除● 版面分析与文档还原 写在最后 写在前面 VALSE 2023 无锡视觉与学习青年学者研讨会近期在无锡国际博览中心举办&#xff0c;由江南大学和无锡新吴区联合承办。本次会议旨…

netty学习(1):多个客户端与服务器通信

1. 基于前面一节netty学习&#xff08;1&#xff09;:1个客户端与服务器通信 只需要把服务器的handler改造一下即可&#xff0c;通过ChannelGroup 找到所有的客户端channel&#xff0c;发送消息即可。 package server;import io.netty.channel.*; import io.netty.channel.gr…

陪诊小程序系统|陪诊软件开发|陪诊系统功能和特点

随着医疗服务的逐步改善和完善&#xff0c;越来越多的人群开始走向医院就诊&#xff0c;而其中不少人往往需要有人陪同前往&#xff0c;这就导致了许多矛盾与问题的发生&#xff0c;比如长时间等待、找不到合适的陪诊人员等。因此为人们提供一种方便快捷的陪诊服务成为了一种新…

成本降低60%至70%?中国展现顶级电池技术,锂电就是下一个铅酸

在3月份&#xff0c;宁德时代宣布加速推进钠离子电池产业化&#xff0c;以降低成本并提供差异化产品和技术&#xff0c;帮助客户提升产品竞争力和占据更大市场份额。孚能科技已在上半年开始批量生产钠离子电池&#xff0c;而拓邦股份也在最近的国际电池技术展上发布了自家的钠离…

vue下基于elementui自定义表单-后端数据设计篇

vue下基于elementui自定义表单-后端篇 自定义表单目前数据表单设计是基于数据量不大的信息单据场景&#xff0c;因为不考虑数据量带来的影响。 数据表有: 1.表单模版表&#xff0c;2.表单实例表&#xff0c;3.表单实例项明细表&#xff0c;4表单审批设计绑定表 以FormJson存…

【动态规划】LeetCode 583. 两个字符串的删除操作 Java

583. 两个字符串的删除操作 我的代码&#xff0c;错误代码&#xff0c;只考虑到了字母出现的次数&#xff0c;没有考虑到两个字符串中字母出现的顺序 class Solution {public int minDistance(String word1, String word2) {int[] arr1 new int[26];int[] arr2 new int[26];…

【数据结构】——常见排序算法(演示图+代码+算法分析)

目录 1. 常见排序算法 1.2 稳定性 2. 常见排序算法的实现 2.1 插入排序 2.1.1基本思想 2.1.2代码 2.1.4算法分析 2.2 希尔排序 2.2.1基本思想 2.2.2代码 2.2.3演示图 2.2.4算法分析 2.3 选择排序 2.3.1基本思想 2.3.2代码 2.3.3演示图 2.3.4算法分析 2.4 堆排…