目录
前言
做一个有趣的程序员。哈哈哈哈
本次铁村的小蓝猫主要给大家详细分享JVM其中重要的2块。 类装载器 +运行时数据区。
分享前,我们先了解下本次分享内容的框架。
一、JVM定义
1.1JVM是什么
jvm是java的核心和基础,在java编译器和os平台之间的虚拟处理器,可在上面执行字节码程序。
1.2JVM由什么组成
JVM 包含二个子系统,和二个组件分别为
1.类加载器
2.执行引擎
3.运行时内存区域
4.本地接口 (基本上用不到)
一个字节码文件,通过类装载子系统,丢到运行时数据区,最后通过字节码执行引擎来执行内存区中的代码。
二、类加载器
类加载器脑图
2.1 Java命令执行代码流程
1.java指令调用jvm.dll文件,创建java虚拟机
2.启动java虚拟机过程中,创建引导类加载器
3.通过c++调用java虚拟机的真正的启动程序sum.misc.Lancher。这个类调用launcer.getClassLoader()可以创建java的其他类加载器。
- 通过jvm的类加载器,调用loadClass方法加载磁盘里的.class字节码文件
5.加载完成字节码文件后,调用到c++的Main方法,执行代码
6.JVM摧毁
2.2 类加载过程
类加载的过程可分:
加载->验证->准备->解析->初始化->使用->卸载
1.加载:把class字节码文件从各个来源通过类加载器加载到内存中(遵循双亲委派机制)
2.验证:检查字节流文件是否符合JVM规范
3.准备:为类中定义的变量分配内存和赋值初始化–比如int=0;boolean=false,引用=null,由虚拟机规定,和最终值无关
4.解析:将java虚拟机常量池的符号引用替换为直接引用
5.初始化:对static修饰的静态变量或者代码赋值–比如int从0变为1
6.使用
7.卸载
2.3 类加载器种类
1.引导类加载器(bootstrapLoader)
负责加载支撑JVM运行JRE的lib目录核心库
2.扩展类加载器(extClassLoader)
负责加载支撑JVM运行JRE的lib目录下ext扩展目录中的JAR包
3.应用程序加载器(appClassLoader)
负责ClassPath,主要负责自己写的那些类
4.自定义加载器
继承ClassLoader,核心方法,负责加载用户自定义路径下的类包
2.4 双亲委派机制
2.4.1双亲委派机制概括
加载某个类时会先委托父加载器寻找目标类。
找不到再委托上层父加载器加载。
如果所有父加载器都在自己的加载类路径下找不到目标。
则在自己的类加载路径中查找并载入目标类。
2.4.2双亲委派流程图解
2.4.3双亲委派机制的好处
1.沙箱安全机制-防止核心API被随意篡改
2.避免类的重复加载-保证加载类的唯一性
2.4.4怎么打破双亲委派机制
自定义类加载器,重写类加载方法,
实现自己的加载逻辑,不委派给父加载器加载。
注意,因为java核心类还得是靠双亲委派机制来加载,
所以得加上相关的判断逻辑使核心类继续使用双亲委派机制加载.
以下自定义加载器是继承ClassLoader,重写的2个方法的代码
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = new byte[0];
try {
data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name)
if (c == null) {
long t1 = System.nanoTime();
if(!name.startsWith("com.attcatstudy.jvm")){
c = this.getParent().loadClass(name);//非这个包其他都还是向上委派
}else {
c = findClass(name);
}
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
贴心小例子:
任务目标:一个main方法 ,实现运行2个不同路径,相同包名,加相同类名的类
准备2 个user,分别丢在main 方法根目录+自定义目录
根目录user:
存放
电脑路径
package com.attcatstudy.jvm;
/**
* @author attcat
* ---做一个有趣的程序员
* @date 2022-10-01 18:41
* @description
*/
public class User {
public String a;
static {System.out.println("*************load Use里面************"); }
public void sout(){
System.out.println("*************load sout Use里面************");
}
}
自定义user:
存放
package com.attcatstudy.jvm;
/**
* @author attcat
* ---做一个有趣的程序员
* @date 2022-10-01 18:41
* @description
*/
public class User {
public String a;
static {System.out.println("*************load Use外面************"); }
public void sout(){
System.out.println("*************load sout Use外面************");
}
}
运行main方法()
public static void main(String[]args)throws Exception{
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加器设置为应用程序类加载器AppClassLoader
User user=new User();
user.sout();
//选择自己文件的根目录地址
MyClassLoader classLoader=new MyClassLoader("/Users/attcat/studyCode/");
//创建 /Users/attcat/studyCode/ 几级目录,将User类的复制类User1.class丢入该目录
Class clazz=classLoader.loadClass("com.attcatstudy.jvm.User");
Object obj=clazz.newInstance();
//调用sout方法
Method method=clazz.getDeclaredMethod("sout",null);
method.invoke(obj,null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
运行结果达到目的。2个user同时被加载 并且执行
2.4.5tomcat为什么要打破双亲委派机制
1.一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,
不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离
2.部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机
3.web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
4.web容器要支持jsp的修改,jsp 文件最终也是要编译成class文件才能在虚拟机中运行, web容器需要支持 jsp 修改后不用重启。
2.4.6tomcat怎么实现热加载机制
每个jsp文件都有独立的类加载器
有线程去检测文件是否改动
如果改动了,就将这个类加载器置空,然后创建新的加载器来重新加载这个文件
三、运行时数据区
JVM运行时数据区主要分为:堆、栈、方法区(元空间)、本地方法栈、程序计数器。
其中,堆、方法区是所有线程共享。
程序计数器,本地方法栈、虚拟机栈、是每个线程独有的。
3.1 内容脑图+区域图
3.2 程序计数器
每一个线程独有,每个线程都有的一块内存空间,用于记录即将执行的代码的内存地址。
每一个方法执行完一行代码,字节码执行引擎会马上动态地修改程序计数器的值。根据这个值判断下一次执行那一个方法。
3.3 本地方法栈
每一个线程都有自己的本地方法栈,存放 用native修饰的本地方法
3.4 虚拟机栈
虚拟机栈:存放栈帧,先进后出。每次都从栈顶取,符合java代码的执行流程,后调用的方法先释放掉(出栈)
3.5 堆
所有线程共享的内存区域,唯一目的就是存放对象实例。
3.6 方法区
各个线程共享的内存区域,存储已被虚拟机加载的 类型信息、常量、静态变量、类信息等。