JVM笔记【一】java和Tomcat类加载机制

JVM笔记一java和Tomcat类加载机制

java和Tomcat类加载机制
  • Java类加载

  • * loadClass加载步骤
    
    • 类加载机制
    • 类加载器初始化过程
    • 双亲委派机制
    • 全盘负责委托机制
    • 类关系图
    • 自定义类加载器
    • 打破双亲委派机制
  • Tomcat类加载器

  • * 为了解决以上问题,tomcat是如何实现类加载机制的?
    

Java类加载

当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到jvm中。类似流程图如下。
加载过程图

loadClass加载步骤

类全生命周期:加载 >>验证>>准备>>解析>>初始化>>使用>>卸载。
加载周期

加载:在硬盘上查找并通过IO兑入字节码文件,使用到类时才会加载,例如调用类的main方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象(推荐),放在堆中,作为访问这个类在方法区中类元数据的各个数据的接口。
验证:校验字节码文件的正确性。如:文件格式的验证,元数据的验证,字节码的验证,符号引用的验证。
准备:给类的静态变量分配内存,并赋予默认值,此处给默认值,不一定是我们程序中赋予的值。如果被final修饰,在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值,即没有赋初值这一步

解析:将符号引用 替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或者句柄等(直接引用),这是所谓的静态链接 过程(类加载期间完成),动态链接 是在程序运行期间完成的,将符号引用替换为直接引用(// TODO 待补充),解析后的信息存储在ConstantPoolCache类实例中
初始化:对类的静态变量初始化为指定的值,执行静态代码块,比如:initData 的666值是此时才赋予的。构造放在在静态代码块之后。

public static final int initData=666;

v2

类被加载到方法区中后,主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应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 test************");B b = null; //B不会加载,除非这里执行 new 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 test************

类加载机制

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

  • 引导类加载器(bootstrapLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如:rt.jar、charsets.jar等
  • 扩展类加载器(ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
  • 应用程序类加载器(AppClassLoader):负责加载ClassPath路径下的类包,主要就是加载自己写的那些类。自定义加载器:负责加载用户自定义路径下的类包。

类加载器初始化过程

由上面的加载过程图可知,JVM启动,实例sun.misc.Launcher类,而sun.misc.Launcher构造方法内部,创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。

JVM会默认调用Launcher中的getClassLoader()方法,返回的加载器会是AppClassLoader来加载我们的应用程序。代码截取

//Launcher的构造方法
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");。。。 。。。 //省略一些不需关注代码}public ExtClassLoader(File[] var1) throws IOException {// Launcher.ExtClassLoader.getExtClassLoader(); 实例化会走到地方,他父加载器传的是nullsuper(getExtURLs(var1), (ClassLoader)null, Launcher.factory);SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}

双亲委派机制

JVM类加载器是有亲子层级结构的,如下图:
双亲委派图

这里类加载其实就是一个双亲委派机制,加载某个类时,launcher的getClassLoader会给出appClassLoader这个加载器,调用其最上层抽象ClassLoader的loadClass方法,其流程是会先找自己有没**有加载过(并不是加载)**这个类,如果有直接返回,如果没有,则委托父加载器寻找目标类,而此时父加载器(扩展类加载器)同样是实现ClassLoader的类,同样走loadClass方法,逻辑也是找自身是否加载过此类,如果没有,则继续委托其父加载器;如果依然找不到目标类,则在自己的类加载路径中查找并载入目标类。

比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载 器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。

//ClassLoader的loadClass方法,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {//  // 检查当前类加载器是否已经加载了该类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;}

双亲委派说简单的,就是先找父亲加载,不行再有儿子自己加载,此处注意:父类加载器和父类不是一个概念。

全盘负责委托机制

“全盘负责”是指当一个ClassLoader装载一个类时,除非显示的使用另一个ClassLoader,该类所有依赖及引用的类也有这个ClassLoader一次载入完毕。(因为正常情况下,依赖和引用类是只有在使用的时候才加载的。)

类关系图

classLoader类继承图

自定义类加载器

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

package com.tuling.jvm;import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;/*** @Description: 自定义类加载器* @ClassName: MyClassLoaderTest* @Author: * @Date: 2021/8/11 0:42**/
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 fis = new FileInputStream(classPath + "/" + name + ".class");int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();return data;}protected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}}public static void main(String args[]) throws Exception {//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 器设置为应用程序类加载器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("D:"+ File.separator+"program_test");//D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录Class clazz = classLoader.loadClass("com.tuling.jvm.User1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());}
}

打破双亲委派机制

package com.tuling.jvm;import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;/*** @Description: 双亲委派和反* @ClassName: MyClassLoaderTest* @Author: * @Date: 2021/8/11 0:42**/
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 fis = new FileInputStream(classPath + "/" + name + ".class");int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();return data;}protected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}/*** 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载** @param name* @param resolve* @return* @throws ClassNotFoundException*/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 (name.startsWith("com.tuling.jvm")) {c = findClass(name);} else {c = super.loadClass(name, false);}}if (resolve) {resolveClass(c);}return c;}}}public static void main(String args[]) throws Exception {//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 器设置为应用程序类加载器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("D:" + File.separator + "program_test");//D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录Class clazz = classLoader.loadClass("com.tuling.jvm.User1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());}
}

Tomcat类加载器

思考:tomcat是web容器,他需要解决什么问题?

  1. 一个web容器可能需要部署两个及以上的应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本 ,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
  2. 部署在同一个web容器中的**相同的类库相同的版本可以共享。**否则,如果服务器有10个应用程序,那么要有10分相同的类库加载进入虚拟机
  3. **web容器也有自己依赖的类库,不能与应用程序的类库混淆,**基于安全考虑,应该让容器的类库和程序的类库隔离开来。
  4. web容器要支持JSP的修改,jsp文件最终也要编译成class文件才能在虚拟机中运行,但是jsp修改频繁,需要热加载。

为了解决以上问题,tomcat是如何实现类加载机制的?

tomcat双亲委派类型图

  • commonClassLoader:tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个WebApp访问;
  • catalinaClassLoader:tomcat容器私有的类加载器,加载路径中的class对于WebApp不可见。
  • sharedClassLoader:各个WebApp共享的类加载器,加载路径中的class对于所有WebApp可见,但是对于Tomcat容器不可见
  • WebAppClassLoader:各个WebApp私有的类加载器,加载路径中的class只对于当前WebApp可见,比如加载war包里的相关类,每个war包应用都有自己的WebAppClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载给咱的spring版本。webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制
    委派关系:
  1. CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。
  2. WebAppClassLoader可以使用SharedClassLoader加载到的类,但WebAppClassLoader实例之间相互隔离。
  3. JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。

加载类关系

tomcatclassloader类继承关系

**注意:**同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。

课后小问题

1.为什么先执行静态代码块,后执行构造方法?

答:因为在初始化的时候,会执行静态代码块的内容,而构造方法是在new对象的时候调用的。这也正证实加载并不会new一个完整对象出来。而仅仅只会有一个class对象到堆中。

2.为什么要设计双亲委派机制?

答:(1)沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改。
(2)避免类的重复加载:当父亲已经加载了该类时,就没有必要子Classloader在加载一次,保证被加载的类的唯一性。

3.Tomcat如果使用默认的双亲委派类加载机制行不行? 为什么?

答:不行。这就涉及到Tomcat要解决的问题。

  • 如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器是不管类的版本,只根据全名查找,且只有一份。
  • 如何实现jsp的热加载问题,因为jsp会编译成calss文件,如果修改了jsp,但是类名并没有变,类加载器会直接取方法区中的已存在的信息,修改的并不会被加载到。所以,Tomcat的处理办法是,当你修改jsp,他会卸载掉这个jsp的类加载器,重新创建新的类加载器,加载jsp文件。

4.Tomcat打破了类加载机制,是否可以恶意定义HashMap?是否安全

答:不可以,因为上层类加载器依然没有变,根据列关系图,都会走到secureClassLoader,做安全校验。

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

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

相关文章

IP编址(来自YESLAB新网工的笔记)

上层协议类型 概念&#xff1a;通常指的是位于网络层&#xff08;如 IP 层&#xff09;以上的协议类型&#xff0c;这些协议在数据传输时需要由网络层&#xff08;或更低层&#xff09;协议承载。以 IP 协议为例&#xff0c;IP 报文头部中的 协议字段&#xff08;Protocol Fie…

SpringBoot学习(过滤器Filter。拦截器Interceptor。全局异常捕获处理器GlobalExceptionHandler)(详细使用教程)

目录 一、过滤器Filter。 1.1定义与规范。 1.2工作原理与范围。 1.3使用场景。 1.4 SpringBoot实现过滤器。&#xff08;Filter配置2种方式&#xff09; <1>注解配置(WebFilter、Order、ServletComponentScan)。 创建过滤器类。 启用 Servlet 组件扫描。 <2>配置类…

c++题目_P1443 马的遍历

P1443 马的遍历 # P1443 马的遍历 ## 题目描述 有一个 $n \times m$ 的棋盘&#xff0c;在某个点 $(x, y)$ 上有一个马&#xff0c;要求你计算出马到达棋盘上任意一个点最少要走几步。 ## 输入格式 输入只有一行四个整数&#xff0c;分别为 $n, m, x, y$。 ## 输出格式 …

清华《数据挖掘算法与应用》K-means聚类算法

使用k均值聚类算法对表4.1中的数据进行聚类。代码参考P281。 创建一个名为 testSet.txt 的文本文件&#xff0c;将以下内容复制粘贴进去保存即可&#xff1a; 0 0 1 2 3 1 8 8 9 10 10 7 表4.1 # -*- coding: utf-8 -*- """ Created on Thu Apr 17 16:59:58 …

HarmonyOS-ArkUI V2工具类:AppStorageV2:应用全局UI状态存储

AppStorageV2是一个能够跨界面存储数据,管理数据的类。开发者可以使用AppStorageV2来存储全局UI状态变量数据。它提供的是应用级的全局共享能力,开发者可以通过connect绑定同一个key,进行跨ability数据共享。 概述 AppStorageV2是一个单例,创建时间是应用UI启动时。其目的…

打靶日记 zico2: 1

一、探测靶机IP&#xff08;进行信息收集&#xff09; 主机发现 arp-scan -lnmap -sS -sV -T5 -p- 192.168.10.20 -A二、进行目录枚举 发现dbadmin目录下有个test_db.php 进入后发现是一个登录界面&#xff0c;尝试弱口令&#xff0c;结果是admin&#xff0c;一试就出 得到加…

使用Java基于Geotools的SLD文件编程式创建与磁盘生成实战

前言 在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;地图的可视化呈现至关重要&#xff0c;而样式定义语言&#xff08;SLD&#xff09;文件为地图元素的样式配置提供了强大的支持。SLD 能够精确地定义地图图层中各类要素&#xff08;如点、线、面、文本等&#x…

kubernetes》》k8s》》Service

Kubernetes 中的 Service 是用于暴露应用服务的核心抽象&#xff0c;为 Pod 提供稳定的访问入口、负载均衡和服务发现机制。Service在Kubernetes中代表了一组Pod的逻辑集合&#xff0c;通过创建一个Service&#xff0c;可以为一组具有相同功能的容器应用提供一个统一的入口地址…

【HDFS】EC重构过程中的校验功能:DecodingValidator

一、动机 DecodingValidator是在HDFS-15759中引入的一个用于校验EC数据重构正确性的组件。 先说下引入DecodingValidator的动机,据很多已知的ISSUE(如HDFS-14768, HDFS-15186, HDFS-15240,这些目前都已经fix了)反馈, EC在重构的时候可能会有各种各样的问题,导致数据错误…

现代c++获取linux系统架构

现代c获取linux系统架构 前言一、使用命令获取系统架构二、使用c代码获取系统架构三、验证四、总结 前言 本文介绍一种使用c获取linux系统架构的方法。 一、使用命令获取系统架构 linux系统中可以使用arch或者uname -m命令来获取当前系统架构&#xff0c;如下图所示 archuna…

didFinishLaunching 与「主线程首次 idle」, 哪个是更优的启动结束时间点 ?

结论先行 在这两个候选时间点里—— application:didFinishLaunchingWithOptions: 执行结束主线程第一次进入 idle&#xff08;RunLoop kCFRunLoopBeforeWaiting&#xff09; 若你只能二选一&#xff0c;以「主线程首次 idle」作为 启动结束 更合理。它比 didFinishLaunchin…

Vue3 + TypeScript中defineEmits 类型定义解析

TypeScript 中 Vue 3 的 defineEmits 函数的类型定义&#xff0c;用于声明组件可以触发的事件。以下是分步解释&#xff1a; 1. 泛型定义 ts <"closeDialog" | "getApplySampleAndItemX"> 作用&#xff1a;定义允许的事件名称集合&#xff0c;即组…

树莓派超全系列教程文档--(34)树莓派配置GPIO

配置GPIO GPIO控制gpio 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 GPIO控制 gpio 通过 gpio 指令&#xff0c;可以在启动时将 GPIO 引脚设置为特定模式和值&#xff0c;而以前需要自定义 dt-blob.bin 文件。每一行都对一组引脚应用相同的设…

AladdinEdu(H卡GPU算力平台)使用教程: 1)注册与开通流程 2)插件使用流程

一、注册与开通流程 首先进入AladdinEdu官网&#xff1a;AladdinEdu-同学们用得起的H卡算力平台-高效做AI就上Aladdin 完成注册&#xff0c;并进行学生认证&#xff1a;学生认证账户&#xff0c;认证期间享受教育优惠价。 登录官网进入控制台 二、插件使用流程 VScode中…

精益数据分析(6/126):深入理解精益分析的核心要点

精益数据分析&#xff08;6/126&#xff09;&#xff1a;深入理解精益分析的核心要点 在创业和数据驱动的时代浪潮中&#xff0c;我们都在不断探索如何更好地利用数据推动业务发展。我希望通过和大家分享对《精益数据分析》的学习心得&#xff0c;一起在这个充满挑战和机遇的领…

2.深入剖析 Rust+Axum 类型安全路由系统

摘要 详细解读 RustAxum 路由系统的关键设计原理&#xff0c;涵盖基于 Rust 类型系统的路由匹配机制、动态路径参数与正则表达式验证以及嵌套路由与模块化组织等多种特性。 一、引言 在现代 Web 开发中&#xff0c;路由系统是构建 Web 应用的核心组件之一&#xff0c;它负责…

运筹学之模拟退火

目录 一、历史二、精髓思想三、案例与代码实现 一、历史 问&#xff1a;谁在什么时候提出模拟退火&#xff1f;答&#xff1a;模拟退火算法&#xff08;Simulated Annealing&#xff0c;SA&#xff09;是由斯图尔特柯尔斯基&#xff08;Scott Kirkpatrick&#xff09; 等人在 …

android测试依赖

Android 项目中常用的测试相关库 1. androidx.arch.core:core-testing:2.2.0 作用&#xff1a; 提供与 Android Architecture Components&#xff08;如 LiveData、ViewModel&#xff09;相关的测试工具。主要用于测试基于 LiveData 的异步操作。 常见功能&#xff1a; 即时…

stack,queue和priority_queue

1. stack 1.1 stack 的介绍 栈是一种容器适配器&#xff0c;专门设计用于LIFO环境&#xff08;后进先出&#xff09;&#xff0c;其中元素仅从容器的一端插入和提取。 容器适配器&#xff0c;也就是使用特定容器类的封装对象作为其底层容器&#xff0c;提供一组特定的成员函…

MinnowBoard MAX单板UEFI BIOS代码编译教程

此教程用于UEFI EDK2代码的研究&#xff0c;虽然EDK2框架代码开源&#xff0c;但是都是在模拟器上跑仿真&#xff0c;差点意思&#xff0c;搞过嵌入式的应该有一个共识&#xff0c;是骡子是马&#xff0c;你得把板子点亮啊。MinnowBoard MAX单板是intel10多年前发布的软硬件全部…