JVM知识总结
一、类的加载机制
*1、运行main函数
——》java.exe调用jvm.dll创建java虚拟机
——》创建一个引导类加载器
——》创建JVM启动器,实例sun.misc.Launcher,引导类加载器,并加载、创建其他类加载器sun.misc.Launcher.getLauncher()
——》获得运行类自己的加载器classLauncher,appClassLauncher
——》调用classLauncher加载需要运行的类...加载完成之后JVM会执行当前类的main方法
*2、loadClass类加载的流程:
加载、验证、准备、解析、初始化、使用、卸装
*加载:在硬盘上查找,通过IO读入字节码文件,使用时才会被加载这些字节码文件,例如调用类的main方法,new等,
加载阶段只会生成代表这个类的java.lang.Class对象,作为方法区该类的访问入口。
*验证:校验字节码文件的正确性,一些规则是否符合。
*准备:给静态变量分配内存,并赋予初始值,如:final static int MAX_I = 127;此时int初始值应该为0;
*解析:将符号引用代替为直接引用
(如:List<String> list = new ArrayList<String>();中,list为符号引用,在堆内会为new ArrayList<String>()
分配一块空间,这块内存空间的地址即为直接引用)
该阶段会把一些静态方法(符号引用,比如main方法)替换为指向数据所在内存的指针或句柄等(直接引用);即为静态链接。
动态链接是在程序运行期间完成讲符号的引用替换为直接引用。
*初始化:对类的静态变量初始化为指定的值,并执行静态代码块,如:准备中的MAX置为指定的127。
如一个controller类——》编译打包——》加载(验证、准备、解析、初始化)——》JVM
类加载到方法区后主要包含:运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对class实例的引用等信息。
类加载器的引用:这个类到类加载器的引用。
对应class实例的引用:类加载器在加载类型信息放到方法区之后,会创建一个对应class类型的对象放到堆(heap)中,作为开发人员访问这个类的入口
注意:主类运行过程中使用到其它类,会逐步加载这些类。使用时才加载。
*3、类的双亲委派机制
java中的几种类加载器:
1)、引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如:rt.jar、charsets.jar等
2)、扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。
3)、应用程序类加载器:负责加载ClassPath路径下的类包,主要是自己写的类。
4)、自定义类加载器:负责加载用户自定义路径下的类包。
类加载器初始化过程:
JVM启动器实例sun.misc.Launcher,该实例为单例的设计模式,保证一个JVM虚拟机只有一个。
Launcher构造方法内部,有ExtClassLoader和AppClassLoader
JVM默认使用Launcher的getClassLauncher()方法返回类加载器AppClassLauncher的实例加载我们的应用程序。
//Launcher的构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {//构造扩展类加载器,在构造的过程中将其父加载器设置为null
var1 = 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");
//省略一些不需关注代码
}
**双亲委派机制:父类加载失败,由子加载器自己加载
引导类加载器
^^
扩展类加载器
^^
应用程序类加载器
^^
自定义类加载器
基本原理:
首先会判断一下加载器是否已经加载过了,若有,直接返回,没有找父类加载。
找父类加载,父类在自己的加载路径中加载不到,再由子加载器加载。
**设计双亲委派机制的原因:
1)、沙箱安全机制:防止核心API被修改,如自己写java.lang.String.class
2)、避免重复加载:父类已经加载,没有必要子类再加载一遍。
**全盘负责机制:
当一个classLoader装载一个类时,除非显示使用的另外一个ClassLoader,该类的依赖及医用也由这个ClassLoader载入。
*Tomcat打破双亲委派机制
1、Tomcat是一个web容器,一个web容器可能要部署两个应用程序,当两个应用程序依赖不同的第三方类库,要保证每个类库都是独立的。
2、同一个web容器中相同的类库应该可以共享,否则多个应用程序加载多个类库,造成资源浪费。
3、web容器也有自己依赖的类库,应该与应用程序的类库隔离开
4、web容器要支持jsp的修改,jsp也是要编译成class文件才能在虚拟机中运行,web容器需要支持jsp修改后不用重启。
每个jsp文件对应唯一的类加载器,当一个jsp文件修改了,直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
tomcat几个重要的类加载器
commonLoader:最基础的类加载器,加载路径中的class可以被tomcat容器本身及各个WebAPP访问
catalinaLoader:私有类加载器,加载路径对于webAPP不可见。
sharedLoader:各个webAPP共享的类加载器,对于Tomcat不可见
webAppClassLoader:各个webApp私有的类加载器,只对当前webAPP可见,比如加载war包里相关的类。每个war包都有自己的webAppClassLoader,实现相互隔离。
比如:war包应用引入不同的spring版本,这样就实现加载各自的spring
两个对象是否是同一个,除了看包名类名是否相同之外,还需要他们的类加载器也是同一个。
二、JVM内存模型
*1、JVM内存结构:
堆:放置实例对象,字符串常量池,基本数据类型常量池等
栈(线程):程序计数器、
栈帧(局部变量表、操作数栈、动态链接、方法出口)一个方法加载一个栈帧
本地方法栈
方法区(1.8称元空间):运行时常量池,静态变量,类信息
*2、JVM内存参数设置:
堆:-Xms -Xmx
新生代(年轻代):-Xmm
方法区(元空间)
-XX:MaxMataspaceSize: 元空间最大值,默认-1,不受限制,只受限于本地内存大小。
-XX:MetaspaceSize:指定触发FullGC的初始阈值,以字节为单位,默认21M,达到阈值触发FullGC,进行类型卸装
同时收集器会动态调整元空间大小,如释放大量空间,就会降低元空间大小,否则,提高该值。
-XX:PermSize永久代初始容量
PS:元空间大小的调整会触发FullGC,非常消耗,若程序在启动时发生大量FullGC,通常是永久代或元空间大小发生调整,
基于这种情况一般在JVM参数中,MetaspaceSize和MaxMataspaceSize设置成一样,并且设置的比初始值大,对于8G物理内存,一般建议256M
-Xss栈大小
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具