main方法的执行流程
image-20230224180340262
1:先把文件编译成class文件
2:window下,java.exe调用jvm.dll创建虚拟机,dll文件相当于jar包。
3:c++创建引导类加载器。
4:实例化Launcher,由引导类加载器加载。
5:加载JvmTest.class文件
6:执行main方法
loadClass加载流程,类是如何加载到java虚拟机的?
static int a = 10;
int b = 5;
1:加载:将字节码文件丢到内存
2:验证:需要验证字节码格式是否正确。Cafe baba标准的字节码文件开头。
3:准备:对静态变量进行赋值,赋上默认值,将a赋为0。
4:解析:将符号引用转变成直接引用,只是将静态的能够确定被谁调用的才会被转变,这个叫做静态解析,类加载时候完成。
动态解析,在编译的时候没有办法确定被谁调用,只能在运行的时候才能判断出来,比如多态。类运行时候完成。
符号引用包括三种:类、接口的符号引用。字段的符号引用。方法的符号引用。
直接引用:符号的内存地址。
5:初始话:将静态遍变量真正赋值,将a赋值为10,静态代码块也会在这里执行 。
6:将class文件加载到JVM虚拟机中。
类的加载机制一般是懒加载,在只有用到的时候才会真正的加载。
public class JvmTest {
static int b = 10;
static {
System.out.println("jvmTest静态方法");
System.out.println("b的值 = " + b);
}
public static void main(String[] args) {
System.out.println("执行main方法");
new A();
}
}
class A {
static {
System.out.println("A的静态方法");
}
A() {
System.out.println("A的构造方法");
}
}
class B {
static {
System.out.println("B的静态方法");
}
B(){
System.out.println("B的构造方法");
}
}
执行结果:
jvmTest静态方法
b的值 = 10
执行main方法
A的静态方法
A的构造方法
A的静态代码块输出是在main方法之后,也就是在真正使用的时候才会被加载。然后B没有被使用,所以不会被加载。
类加载器和双亲委派机制
类加载器分为:
1:引导类加载器:是由c++创建,在jvm创建之后加载,它负责加载JRE下lib目录的jar。
2:扩展类加载器:负责加载JER下lib的ext,extClassLoader
3:应用程序加载器:负责加载应用程序,appClassLoader
4:自定义加载器:自己定义的
System.out.println("String加载类:" + String.class.getClassLoader());
System.out.println("DESKeyFactory加载类:" + com.sun.crypto.provider.DESCipher.class.getClassLoader());
System.out.println("JvmTest加载类:" + JvmTest.class.getClassLoader());
输出:
String加载类:null
DESKeyFactory加载类:sun.misc.Launcher$ExtClassLoader@372f7a8d
JvmTest加载类:sun.misc.Launcher$AppClassLoader@18b4aac2
String的加载类是引导类加载器加载的,引导类是由c++生成的,所以看不到,是个null。
extClassLoader是扩展类加载器。
AppClassLoader引用程序加载器。
Launcher
C++引导类加载完之后会实例化Launcher
class Launcher{
private ClassLoader loader;
private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
return launcher;
}
... ...
}
launcher是个静态变量,是个单例,在加载的时候就初始话好了。
初始话的时候实例化ext和app ClassLoader
public Launcher() {
//实例化扩展类加载器,它的父类没有设置值
Launcher.ExtClassLoader var1 =
Launcher.ExtClassLoader.getExtClassLoader();
//初始化APPclassLoader,并且将ext传入设置为父类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
}
loader赋值为appClassLoader,默认先用appClassLoader加载
双亲委派机制
image-20230224132150805
C++在加载的时候调用
Launcher.getLauncher().getClassLoader().loadClass("JvmTest.class")
Launcher的classLoader是在实例化的时候赋值了AppClassLoader。
1:AppClassLoader去自己已经加载的类里边找,如果没有向上委托
2:ExtClassLoader去自己已经加载的类里边找,如果没有向上委托。
3:引导类去自己加载的类里边找,如果没有,
4:引导类尝试加载,判断这个类是不是应该由自己加载,判断是不是JRE包路径下的,如果是加载并且返回,不是向下传播
5:ExtClassLoader判断这个类是不是ext包路径下的,如果是自己加载并返回,不是向下传播
6:AppClassLoader判断这个类是不是classPath路径下的,如果是的化加载。不是的话,ClassNotFound
为什么先由AppClassLoader,而不是引导类开始?
因为大部分类都是我们自己写的,百分之95都是存放到AppClassLoader,只有第一次加载的时候会多走一步,之后大部分都直接从AppClassLoader里边获取。
loadClass("JvmTest.class")源码
Class<?> loadClass(String name, boolean resolve){
//从自己加载的类里边找
Class<?> c = findLoadedClass(name);
if (c == null) {
if (parent != null) {
//父加载器加载
c = parent.loadClass(name, false);
} else {
//ext的父是null,这是最后一层,引导类类加载器
c = findBootstrapClassOrNull(name);
}
if (c == null) {
//由URLClassLoader实现
c = findClass(name);
}
}
return c;
}
URLClassLoader.findClass源码
Class<?> findClass(final String name){
String path = name.replace('.', '/').concat(".class");
//判断是不是自己要加载的。jrt/ ext/
Resource res = ucp.getResource(path, false);
if (res != null) {
//真正加载class的方法
return defineClass(name, res);
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
新增一个String类
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println(111);
}
}
执行报错信息:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
我们新增的 java.lang.String,是能够在引导类加载器的JRE包下边找到的,并且返回String类,这个返回的是jdk自带的类,并不是我们自己写的类,所以会报找不到main方法。
jdk为什么要用双亲委派机制?
jdk不让修改自己内部的类,沙箱安全机制,防止核心API被篡改。
避免类的重复加载,父加载器已经加载完了,自己就不用再加载了。
全盘负责:
public class JvmTest {
static User user;
}
当加载JvmTest类的时候,也会加载User类,这两个类都会由同一个类加载器加载,不会由乱七八糟的加载器加载。
自定义加载器
我们要自定义加载器,只需要实现ClassLoader,重写findClass就可以。
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;
}
@Override
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();
}
}
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader =
new MyClassLoader("C:/myClassLoader");
Class<?> clazz = myClassLoader.loadClass("com.bbk.code.User");
Object o = clazz.newInstance();
Method method = clazz.getDeclaredMethod("eat");
method.invoke(o);
System.out.println("当前MyClassLoader的类加载器:"+MyClassLoader.class.getClassLoader());
}
}
自定义classLoade继承ClassLoader初始话会先初始化父类,在这时候会给自定义classLoader赋值parent为AppClassLoader。具体代码体现在:
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
... ...
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
... ...
return scl;
}
private static synchronized void initSystemClassLoader() {
... ...
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
scl = l.getClassLoader(); //返回this.loader,引导类在加载Launcher的时候会赋值为AppClassLoader
... ...
}
执行main方法,需要把User的class从target中删除,并且加入到我们自定义的文件夹中。执行输出
我是User类eat方法,我的加载器是com.bbk.code.MyClassLoader@5a07e868
如何打破双亲委派机制
意思就是不在委托父加载,直接自己加载类。双亲委派的逻辑代码实现在loadClass方法中。需要继承ClassLoader重写loadClass方法即可:
image-20230224173458980
把红框的代码删除掉。
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
重新执行main方法,报错:
java.io.FileNotFoundException: C:\myClassLoader\java\lang\Object.class (系统找不到指定的路径。)
在加载User的时候,会现在父类Object,我们打破双亲委派之后,自定义的加载器不存在Object所以会报错。
我们把Object.class放入到我们的本地文件夹中。重新执行报错:
//禁止加载java.lang 包
java.lang.SecurityException: Prohibited package name: java.lang
java.lang必须由我们引导类加载器加载,沙箱安全。我们只能让我们的引导类去加载java.lang。修改代码
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
if(!name.startsWith("com.bbk")){
c = this.getParent().loadClass(name);
}else{
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
只有当name是由com.bbk开头的由我们加载器加载。其他的都由父加载器加载。不会报错。