1.java运行时类是什么时候被加载的
一个类在什么时候开始被加载,《Java虚拟机规范》中并没有进行强制约束,交给了虚拟机自己去自由实现,HotSpot虚拟机是按需加载,在需要用到该类的时候加载这个类。java8官方文档
-XX:+TraceClassLoading:开启类加载跟踪
public class TestClassLoad {
class User {
private String name;
}
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.daxij1.jvm.TestClassLoad$User");
}
}
可以看到,当使用到User类时,将类加载到了虚拟机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxFMGm3y-1670422910039)(/upload/2022/11/jvm-lei-jia-zai-01.pic.jpg)]
2.类的加载过程(生命周期)
类的生命周期包含7个阶段,其中验证、准备、解析三个阶段统称为链接(Linking)
2.1 加载(Loading)
将classpath、jar包、网络、某个磁盘位置下的类的class二进制字节流读进来,在内存中生成一个代表这个类的java.lang.Class对象放入元空间,此阶段我们程序员可以干预,我们可以自定义类加载器来实现类的加载;
2.2 验证(Verification)
验证Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证虚拟机的安全
2.3 准备(Preparation)
- 静态变量
赋默认初始值,int为0,long为0L,boolean为false,引用类型为null; - 常量
直接赋正式值
2.4 解析(Resolution)
把符号引用翻译为直接引用
2.5 初始化(Initialization)
对类的静态变量赋初值,执行静态代码块
2.6 使用(Using)
2.7 卸载(Unloading)
3.继承时父子类初始化顺序是怎样的
父类–初始化静态变量 父类–静态代码块
子类–初始化静态变量 子类–静态代码块
父类–初始化成员变量 父类–初始化块 父类–构造器
子类–初始化成员变量 子类–初始化块 子类–构造器
4.什么是类加载器
将class文件解析成描述其属性和行为的二进制流,并读取到内存中的过程
5.jvm类加载器有哪几种
- BootStrapClassLoader
根类加载器,负责加载JRE的lib目录下的核心类库,以及被-Xbootclasspath参数所指定的路径中存放的类库,由C++实现,是虚拟机的一部分 - ExtClassLoader
扩展类加载器,负责加载被java.ext.dirs系统变量所指定的路径中所有的类库,默认有lib目录下的ext扩展目录中的JAR类包 - AppClassLoader
应用程序加载器,负责加载ClassPath路径下的类包 - 自定义类加载器
负责加载用户自定义路径下的类包
import sun.misc.Launcher;
import java.net.URL;
public class TestClassLoader {
public static void main(String[] args) {
System.out.println("bootstrapLoader加载以下文件:");
for (URL url : Launcher.getBootstrapClassPath().getURLs()) {
System.out.println(url);
}
System.out.println("extClassloader加载以下文件:");
for (String url : System.getProperty("java.ext.dirs").split(";")) {
System.out.println(url);
}
System.out.println("appClassLoader加载以下文件:");
for (String url : System.getProperty("java.class.path").split(";")) {
System.out.println(url);
}
}
}
6.四层类加载器之间是继承关系吗
BootStrapClassLoader、ExtClassLoader、AppClassLoader以及自定义加载器之间并不是继承关系,只是每个类加载器继承自ClassLoader,有成员属性parent,从而确认的父子关系
7.什么是双亲委派机制
类加载器在加载类时,会先委托给父加载器加载,若父加载器在其目录中未能找到类,则自己再去加载,若仍未找到,则抛出ClassNotFoundException
8.jvm为什么要设计双亲委派模型
- 安全,避免核心类库被修改
- 避免类的重复加载,保证类的唯一性
9.如何打破双亲委派机制
双亲委派机制实现的核心在ClassLoader的loadClass()方法,想要打破,自定义类加载器,重写该方法即可
10.自定义类加载器
重写关键方法
- loadClass(String name, boolean resolve)
打破双亲委派机制,需要重写该方法 - findClass(String name)
根据名称将文件加载成字节数组,ClassLoader中没有对其进行实现,当我们想定义一个类加载器,但是不想破坏双亲委派模型的时候,只要重写该方法即可 - defineClass()
jvm提供的本地方法,将class文件字节数组转化成Class对象
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader {
//待加载class的文件目录
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
//根据类的全限定类名将class文件加载成字节数组
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
String filePath = classPath + "/" + name + ".class";
FileInputStream fis = new FileInputStream(filePath);
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
//根据全限定类名生成对应Class对象
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
//重写类加载方法,实现自己的加载逻辑,不委派给父类加载
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
//非自定义的类还是走双亲委派加载
if (!name.startsWith("com.daxij1.jvm")) {
c = this.getParent().loadClass(name);
} else {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader classLoader = new MyClassLoader("/Users/daxiji/Desktop/workspace/idea/java-study/base/src/main/resources");
Class<?> clazz = classLoader.loadClass("com.daxij1.jvm.Test");
Object o = clazz.newInstance();
Method method = clazz.getDeclaredMethod("test");
method.invoke(o,null);
}
}
11.Class.forName和ClassLoader.loadClass区别
两者都可以加载类,区别在于,ClassLoader.loadClass得到的Class是未初始化的,即静态变量未初始化,静态代码块未执行,而Class.forName得到的Class是初始化的。
12.Tomcat的类加载机制是怎样的
tomcat在原来的Java的类加载机制基础上,Tomcat新增了5种类加载器
- Tomcat的基础类加载器
CommonClassLoader、CatalinaClassLoader、SharedClassLoader - web应用的类加载器
WebappClassLoader(每个webapp会产生一个加载器)、JspClassLoader(每个jsp文件会产生一个加载器)
CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现了公有类库的共用
CatalinaClassLoader和SharedClassLoader自己能加载的类与对方相互隔离,WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。
JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃,当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的热加载功能。
13 Tomcat为什么要打破双亲委派模型
Tomcat是web容器,那么一个web容器可能需要部署多个应用程序
1、部署在同一个Tomcat上的两个Web应用所使用的Java类库要相互隔离
2、部署在同一个Tomcat上的两个Web应用所使用的Java类库要互相共享
3、保证Tomcat服务器自身的安全不受部署的Web应用程序影响
4、需要支持JSP页面的热部署和热加载
14 自己实现热部署
思路:
1、实现自己的类加载器
2、从自己的类加载器中加载要热加载的类
3、不断轮询要热加载的类class文件是否有更新,如果有更新,重新加载
自定义类加载器
import java.io.FileInputStream;
public 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;
}
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
实体类,用于被监测热加载
public class User {
private String name = "lisi";
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
热加载类
import java.io.File;
import java.lang.reflect.Method;
public class HotDeployTest {
private static Class userClazz;
private static MyClassLoader myClassLoader;
public static void main(String[] args) throws Exception {
// 1.构造自定义类加载器
String basePath = "/Users/daxiji/Desktop/workspace/idea/java-study/base/src/main/resources";
myClassLoader = new MyClassLoader(basePath);
// 2.开启线程监测文件变化进行类加载(看门狗)
new Thread(() -> {
try {
// 2.1 初始时加载一次类,并记录文件修改时间
String filePath = basePath + "/com/daxij1/hotdeploy/User.class";
File file = new File(filePath);
userClazz = myClassLoader.findClass("com.daxij1.hotdeploy.User");
long tmpModified = file.lastModified();
while (true) {
Thread.sleep(3000);
// 2.2 监测文件最后修改时间,若与上次不同,则进行热部署
file = new File(filePath);
long lastModified = file.lastModified();
if (tmpModified != lastModified) {
System.out.println("监测到DataSource.class文件发生变化,进行热加载...");
// 2.3 一个classLoader不能多次加载同一个类,所以此处必须用新的classLoader
myClassLoader = new MyClassLoader(basePath);
// 只用加载类就行,不需要将dataSourceClazz引用重新赋值
userClazz = myClassLoader.findClass("com.daxij1.hotdeploy.User");
tmpModified = lastModified;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// 3.循环输出user的值,观察热部署是否生效
while (true) {
Thread.sleep(2000);
if (userClazz != null) {
Object dataSourceInstance = userClazz.newInstance();
Method method = userClazz.getDeclaredMethod("toString");
System.out.println(method.invoke(dataSourceInstance));
}
}
}
}
更多章节及Java原创内容,可在微信小程序查看