JVM类加载机制详解

JVM类加载运行全过程

运行Math类的main函数,启动程序时,首先需要通过类加载器把类加载到JVM。

package com.cold;public class Math {public int compute() {int a = 1;int b = 2;int c = (a + b) * 10;return c;}public static void main(String[] args) {Math math = new Math();math.compute();}
}

在这里插入图片描述

loadClass的类加载

其中loadClass的类加载过程有如下几步:

加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载

  • 加载: 在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
  • 验证: 校验字节码文件的正确性
  • 准备: 给类的静态变量分配内存,并赋予默认值。
  • 解析: 将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法()替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。
  • 初始化: 对类的静态变量初始化为指定的值,执行静态代码块。

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

类加载器的引用: 这个类到类加载器实例的引用

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

类加载属于懒加载,程序运行时使用到什么类就会加载什么类。

类加载器和双亲委派机制

类加载过程主要是通过类加载器来实现的,Java中有以下几种类加载器:

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

类加载示例

package com.cold;import sun.misc.Launcher;import java.net.URL;public class TestClassLoader {public static void main(String[] args) {System.out.println(String.class.getClassLoader());System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader());System.out.println(TestClassLoader.class.getClassLoader());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("bootstrapLoader加载以下文件:");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"));}}

输出结果

null
sun.misc.Launcher$ExtClassLoader@77459877
sun.misc.Launcher$AppClassLoader@18b4aac2the bootstrapClassLoader : null
the extClassLoader : sun.misc.Launcher$ExtClassLoader@77459877
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2bootstrapLoader加载以下文件:
file:/D:/Program%20Files/Java/jdk-1.8/jre/lib/resources.jar
file:/D:/Program%20Files/Java/jdk-1.8/jre/lib/rt.jar
file:/D:/Program%20Files/Java/jdk-1.8/jre/lib/jsse.jar
file:/D:/Program%20Files/Java/jdk-1.8/jre/lib/jce.jar
file:/D:/Program%20Files/Java/jdk-1.8/jre/lib/charsets.jar
file:/D:/Program%20Files/Java/jdk-1.8/jre/lib/jfr.jar
file:/D:/Program%20Files/Java/jdk-1.8/jre/classesextClassloader加载以下文件:
D:\Program Files\Java\jdk-1.8\jre\lib\ext;C:\Windows\Sun\Java\lib\extappClassLoader加载以下文件:
D:\Program Files\Java\jdk-1.8\jre\lib\charsets.jar;D:\Program Files\Java\jdk-1.8\jre\lib\deploy.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk-1.8\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk-1.8\jre\lib\javaws.jar;D:\Program Files\Java\jdk-1.8\jre\lib\jce.jar;D:\Program Files\Java\jdk-1.8\jre\lib\jfr.jar;D:\Program Files\Java\jdk-1.8\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk-1.8\jre\lib\jsse.jar;D:\Program Files\Java\jdk-1.8\jre\lib\management-agent.jar;D:\Program Files\Java\jdk-1.8\jre\lib\plugin.jar;D:\Program Files\Java\jdk-1.8\jre\lib\resources.jar;D:\Program Files\Java\jdk-1.8\jre\lib\rt.jar;E:\working_folder\code\test-project\target\classes;D:\Program Files\JetBrains\IntelliJ IDEA 2023.1.1\lib\idea_rt.jar

类加载器初始化过程:

参考类运行加载全过程图,可知其中会创建JVM启动器实例sun.misc.Launcher

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

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

在这里插入图片描述

双亲委派机制

这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。

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

在这里插入图片描述

我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

在这里插入图片描述

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

沙箱安全机制: 自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改

避免类的重复加载: 当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

全盘负责委托机制

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

自定义类加载器示例

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

package com.cold;import java.io.FileInputStream;
import java.io.IOException;public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader{private String classPath;public MyClassLoader(String classPath){this.classPath = classPath;}private byte[] loadByte(String name) throws IOException {name = name.replaceAll("\\.", "/");FileInputStream is = new FileInputStream(classPath+"/"+name+".class");int len = is.available();byte[] data = new byte[len];is.read(data);is.close();return data;}@Overrideprotected 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("E:/test");//复制User.class文件到D:/test/com/cold目录下Class<?> clazz = classLoader.loadClass("com.cold.User");// 删除原有的User类输出的结果为com.cold.MyClassLoaderTest$MyClassLoader// 不删除原有的User类输出的结果为sun.misc.Launcher$AppClassLoaderSystem.out.println(clazz.getClassLoader().getClass().getName());}
}

Tomcat打破双亲委派机制

以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?

我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:

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

再看看我们的问题:Tomcat 如果使用默认的双亲委派类加载机制行不行?

答案是不行的。为什么?

第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。

第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。

第三个问题和第一个问题一样。

我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。

Tomcat自定义加载器详解

在这里插入图片描述

tomcat的几个主要类加载器:

  • commonLoader: Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader: Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader: 各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader: 各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本;

从图中的委派关系中可以看出:

CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离。

WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。

而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的热加载功能。

模拟实现Tomcat的webappClassLoader加载自己war包应用内不同版本类实现相互共存与隔离

只需重写Class<?> loadClass(String name, boolean resolve),实现自己的加载逻辑,不委派给双亲加载。

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

package com.cold;import java.io.FileInputStream;
import java.io.IOException;public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader{private String classPath;public MyClassLoader(String classPath){this.classPath = classPath;}private byte[] loadByte(String name) throws IOException {name = name.replaceAll("\\.", "/");FileInputStream is = new FileInputStream(classPath+"/"+name+".class");int len = is.available();byte[] data = new byte[len];is.read(data);is.close();return data;}@Overrideprotected 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();}}@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) {long t0 = System.nanoTime();//非自定义的类还是走双亲委派加载if(!name.startsWith("com.cold.jvm")){c = this.getParent().loadClass(name);}else {c = findClass(name);}// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();// 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;}}}public static void main(String[] args) throws Exception{//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("E:/test");//复制User.class文件到D:/test/com/cold目录下Class<?> clazz = classLoader.loadClass("com.cold.jvm.User");// 输出结果com.cold.MyClassLoaderTest$MyClassLoaderSystem.out.println(clazz.getClassLoader().getClass().getName());}
}

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

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

相关文章

vue2 tinymce富文本插件

一、介绍 TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。同类程序有&#xff1a;UEditor、Kindeditor、Simditor、CKEditor、wangEditor、Suneditor、froala等等。 TinyMCE的优势&#xff1a; 开源可商用&#xff0c;基于LGPL2.1插件丰富&#xff0c;自带插件基…

4核8G服务器价格选择轻量还是CVM合适?

腾讯云服务器4核8G配置优惠价格表&#xff0c;轻量应用服务器和CVM云服务器均有活动&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;轻量应用服务器4核8G12M带宽一年446元、529元15个月&#xff0c;腾讯云百科txybk.com分…

【论文解读】CP-SLAM: Collaborative Neural Point-based SLAM System_神经点云协同SLAM系统(下)

目录 4 CP-SLAM实验 4.1 两个智能体协作&#xff08; Two-agent Collaboration&#xff09; 4.2 单智能体回环&#xff08;Single Agent with Loop&#xff09; 4.3 地图构建&#xff08;Map Reconstruction&#xff09; 4.4 消融实验 姿态图优化&#xff08;Pose Graph …

JUC工具类_CyclicBarrier与CountDownLatch

最近被问到CyclicBarrier和CountDownLatch相关的面试题&#xff0c;CountDownLatch平时工作中经常用到&#xff0c;但是CyclicBarrier没有用过&#xff0c;一时答不上来&#xff0c;因此简单总结记录一下 1.什么是CyclicBarrier&#xff1f; 1.1 概念 CyclicBarrier&#xff…

简单漂亮的首页

效果图 说明 这个首页我也是构思了很久&#xff0c;才想出这个界面&#xff0c;大家喜欢的话&#xff0c;可以拿走去使用 技术的话&#xff0c;采用的就是vue的语法&#xff0c;但是不影响&#xff0c;很多样式我都是直接手敲出来的 代码实现 标语 <!-- 标语 start-->&…

hive sql 取当周周一 str_to_date(DATE_FORMAT(biz_date, ‘%Y%v‘), ‘%Y%v‘)

select str_to_date(DATE_FORMAT(biz_date, %Y%v), %Y%v)方法拆解 select DATE_FORMAT(now(), %Y%v), str_to_date(202346, %Y%v)

【7】Spring Boot 3 集成组件:缓存组件 spring cache + spring data redis

目录 【7】Spring Boot 3 集成组件&#xff1a;缓存组件 spring cache spring data redis什么是缓存抽象声明式注解JSR-107对应SpEL上下文数据 引入依赖cache 支持的缓存类型缓存类型配置NONESIMPLEREDIS自定义配置 CAFFEINE Hazelcast...总结 个人主页: 【⭐️个人主页】 需要…

01Urllib

1.什么是互联网爬虫&#xff1f; 如果我们把互联网比作一张大的蜘蛛网&#xff0c;那一台计算机上的数据便是蜘蛛网上的一个猎物&#xff0c;而爬虫程序就是一只小蜘蛛&#xff0c;沿着蜘蛛网抓取自己想要的数据 解释1&#xff1a;通过一个程序&#xff0c;根据Url(http://www.…

DMA原理和应用

目录 1.什么是DMA 2.DMA的意义 3.DMA搬运的数据和方式 4.DMA 控制器和通道 5.DMA通道的优先级 6.DMA传输方式 7.DMA应用 实验一: 内存到内存搬运 CubeMX配置&#xff1a; ​编辑用到的库函数&#xff1a; 代码实现思路&#xff1a; 实验二: 内存到外设搬运 CubeMX…

一文看分布式锁

为什么会存在分布式锁&#xff1f; 经典场景-扣库存&#xff0c;多人去同时购买一件商品&#xff0c;首先会查询判断是否有剩余&#xff0c;如果有进行购买并扣减库存&#xff0c;没有提示库存不足。假如现在仅存有一件商品&#xff0c;3人同时购买&#xff0c;三个线程同时执…

MySQL内部组件与日志详解

MySQL的内部组件结构 MySQL 可以分为 Server 层和存储引擎层两部分。 Server 层主要包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖 MySQL 的大多数核心服务功能&#xff0c;以及所有的内置函数&#xff08;如日期、时间、数学和加密函数等&#xff09;&am…

Linux网络之传输层协议tcp/udp

文章目录 目录 一、再谈端口号 1.端口号划分 2.知名端口号 3.netstat&#xff0c;pidof 二、UDP协议 1.udp协议格式 2.udp特点 3.基于udp的应用层协议 三、TCP协议 1.tcp报头 确认应答机制&#xff08;ACK) 超时重传机制 连接管理机制&#xff08;三次握手四次挥…

Java JVM虚拟机

加载字节码文件.class 1字节一般为8位 字节码结构: 第一部分 4字节 cafebaby 第二部分 版本号 00 00 00 32, 第三部分 常量数量 count 第四部分常量池 常量类型表示: 继承关系改变 1.1以后 后面是属性方法 等参数 通过javap 反编译class ,javap xx.class javap -c xxx.…

php中RESTful API使用

1、RESTful AP是什么 RESTful API是一种软件架构风格 RESTful API基于HTTP协议&#xff0c;并遵循一系列约定和原则。它的设计理念是将资源&#xff08;Resource&#xff09;作为核心概念&#xff0c;并通过一组统一的接口对资源进行操作。API的资源通常通过URL进行标识&…

​软考-高级-系统架构设计师教程(清华第2版)【第11章 未来信息综合技术(P384~P419)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第11章 未来信息综合技术&#xff08;P384~P419&#xff09;-思维导图】 课本里章节里所有蓝色字体的思维导图

SpringEvent事件通知机制

“Spring Event” 是 Spring 框架通过事件驱动的编程模型来处理应用程序中的事件。开发者可以定义自己的事件&#xff0c;然后在应用程序中触发这些事件。Spring 框架提供了用于发布和监听事件的机制&#xff0c;以实现松散耦合的组件间通信。 有两个核心组件&#xff1a; 事…

PCL_点云分割_基于法线微分分割

一、概述 PCL_点云分割_基于法线微分分割_点云法向量微分-CSDN博客 利用不同的半径&#xff08;大的半径、小半径&#xff09;来计算同一个点的法向量差值P。判断P的范围&#xff0c;从而进行分割。 看图理解&#xff1a; 二、计算流程 1、计算P点小半径的法向量Ns 2、计…

基于乌燕鸥算法优化概率神经网络PNN的分类预测 - 附代码

基于乌燕鸥算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于乌燕鸥算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于乌燕鸥优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

Linux上使用Python源码编译安装Python

安装python apt install python3-dev python3 python3-venv yum install python38-devel源码安装Python 1.下载需要的Python版本 Python源码地址&#xff1a;https://www.python.org/downloads/source/ 2.安装gcc&#xff08;yum install gcc&#xff09; 3.解压&#xff0c…

最新自动定位版本付费进群系统源码

更新内容&#xff1a; 1.在网站首页增加了付款轮播功能。 2.新增了城市定位功能&#xff0c;方便用户查找所在城市的相关信息。 3.对域名库及支付设置进行了更新和优化。 4.增加了一种图模板设置模式&#xff0c;简化了后台模板设置流程。 5.此外还进行了前后台的其他优化…