乐趣区

关于spring:一个Main方法的执行过程

一个简略的 Main 办法

public class Mm {public static void main(String[] args){Mm mm = new Mm();
            System.out.println(mm.getClass().getClassLoader());
        }
}

javac Mm.java  
java Mm
这么的话 就进行了一次编译并执行

然而如上执行的话咱们是没方法调试的,
因而 java Mm 命令不要间接执行,用 gdb 模式执行
所以咱们要先编译一版 openJDK,具体编译 OpenJdk 代码过程自行百度,举荐用 Windows 商店的 ubuntu 零碎编译

以下是 OpenJdk 源码,fork 他人的

https://github.com/zscchaofan/openjdk-jdk8u
gdb -q java Mm  //gdb 设置 java 命令
set args Mm  // 设置参数名 具体含意不懂百度搜的
start // 启动调试

下边是设置的一些断点 都是一个一个试出来的 
gdb 能够间接指定文件和行数打断点
具体命令能够百度 我也是百度的就不总结了 也不罕用
调试代码如果不参考他人的教程 那就得一步步的走 走几步
就用 gdb 命令查看一下以后代码高低左近的几行代码 再对应到源码下来看看
像我这不懂 c ++ 语言的  只能一步步走 看到办法名用意很显著得中央再认真看
3       breakpoint     keep y   0x00007fffff1e7f4a in JavaMain
                                                   at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c:478
4       breakpoint     keep y   0x00007ffffc97da55 in Java_java_lang_ClassLoader_findBootstrapClass at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:265
9       breakpoint     keep y   0x00007fffff1e9c72 in GetLauncherHelperClass
                                                   at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c:1250
        breakpoint already hit 1 time
14      breakpoint     keep y   0x00007ffffc97da94 in Java_java_lang_ClassLoader_findBootstrapClass at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:272
15      breakpoint     keep y   0x00007ffffc97d3ea in Java_java_lang_ClassLoader_defineClass1
                                                   at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:107

/mnt/d/code/openjdk-jdk8u-master 是我寄存代码的门路
其实是 d 盘 code 下,在 ubuntu 下加了 /mnt                                                     

启动调试后 gdb 进入这里会主动停下,这就是最开始的中央
/mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/main.c

main(int argc, char **argv)
{
    .
    . 省略一部分代码 反正也看不懂
    .
    .
    return JLI_Launch(margc, margv,
                   sizeof(const_jargs) / sizeof(char *), const_jargs,
                   sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
                   FULL_VERSION,
                   DOT_VERSION,
                   (const_progname != NULL) ? const_progname : *margv,
                   (const_launcher != NULL) ? const_launcher : *margv,
                   (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
                   const_cpwildcard, const_javaw, const_ergo_class);
}

持续调试之后找到
/mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c 办法, 如下
FindBootStrapClass这个办法里查找了 jdk 里的这个类 sun.launcher.LauncherHelper,这个类是 c ++ 和 java 代码沟通的桥梁了,LauncherHelper 实例化时会实例化一个零碎类加载器AppClassLoader

if (helperClass == NULL) {
        NULL_CHECK0(helperClass = FindBootStrapClass(env,
                "sun/launcher/LauncherHelper"));
}

之后再去寻找执行类的 Main 办法并执行,就是 c ++ 调用 java 办法,sun.launcher.LauncherHelper#checkAndLoadMain

NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                "checkAndLoadMain",
                "(ZILjava/lang/String;)Ljava/lang/Class;"));

因为咱们是执行 java Mm 命令,所以很显著是从 Mm 类中找到 main 办法。其余的比方 java -jar 命令还有别的解析办法寻找 Main 办法

LauncherHelper.checkAndLoadMain 这个办法中会通过 Class.forName()查找 Mm 这个类,依据双亲委派机制必定会调用虚拟机的类加载器

    at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:265
    cls = JVM_FindClassFromBootLoader(env, clname);
    
查看参数 (gdb) p clname
$53 = 0x7fffff7bf3c0 "Mm"

虚拟机返回空

at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:272
    if (clname != buf) {free(clname);
         }

         return cls;
    }
查看参数 (gdb) p cls
$54 = (jclass) 0x0

所以还是回到了 java 代码中的 AppClassLoader 类加载器中父类 URLClassLoader 的 defineClass 办法中去搜寻 Mm.class,找到之后再去调用虚拟机办法存储以后的类

 private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);
看到这里才算明确 
为啥自定义的类加载器加载过指定类之后,new 关键字实例化对象时还是会用零碎类加载器加载,new 关键字必定是虚拟机执行的 如果本人实现类加载器 加载的类不汇报给虚拟机
那必定虚拟机是不认可的

在之后虚构机会真正调用 Mm 的 Main 办法

  /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c
  
  (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

尽管 Main 办法中有调用
Mm mm = newMm(); 办法,然而再也没有走到类加载器,因为之前曾经加载过了

总结
  • 1. 首先 main 办法执行须要一个操作来启动,像 java Mm 这种命令
  • 2. 这种命令首先是操作系统解析找到 java 命令属于 jdk 的货色,并调用 jdk 的的启动函数,就像 windows 的双击操作一样,双击必定是操作系统搞了什么小动作关上了软件
  • 3. 当操作系统调用了虚拟机的命令后,虚构机会拿到命令的参数比方 Mm,而后去找编译后的文件
  • 4. 虚拟机找到文件后会调用 jdk 中的 java 代码,找到这个类sun.launcher.LauncherHelper,这个类作为一个工具类,作为桥梁链接了 c ++ 和 java 代码
  • 5. 调用 sun.launcher.LauncherHelper 类的 checkAndLoadMain 办法,通过这个办法找执行类 Mm 的 Main 办法
  • 6. 加载好之后执行 Main
无关类加载器一个问题
之前想过一个问题就是如何让 new 关键字实例化的时候用自定义类加载器?当初感觉如同无奈实现,除非替换 jdk 的类加载器!
//Main
public class CustomerMain {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {CustomerClassLoader customerClassLoader = new CustomerClassLoader();
        CustomerMain customerMain = (CustomerMain)(customerClassLoader.findClass("CustomerMain").newInstance());
    }
}
// 自定义类加载器
class CustomerClassLoader extends ClassLoader{
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        try {FileInputStream fileInputStream = new FileInputStream("D:\\code\\zerolearnspring\\target\\classes\\cn\\doourbest\\learn\\spring\\zerolearnspring\\controller\\" + name +".class");
            byte[] bb = new byte[fileInputStream.available()];
            int read = fileInputStream.read(bb);
            return defineClass("cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain",bb,0,read);
        } catch (FileNotFoundException e) {e.printStackTrace();
        } catch (IOException e) {e.printStackTrace();
        }
        throw new ClassNotFoundException("!!");
    }
}

-----console  错误信息
Exception in thread "main" java.lang.ClassCastException: cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain cannot be cast to cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain
    at cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain.main(CustomerMain.java:18)

java 虚拟机书中解释了 new 对象的过程必定会先查看这个指令的参数是否在常量池中定位到这个类的符号援用,并且查看这个符号援用代表的类是否已被加载、解析和初始化过,如果不存在,再去履行类加载过程

退出移动版