Main方法的执行过程是怎样的

今天就跟大家聊聊有关Main方法的执行过程是怎样的,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

10年积累的做网站、成都网站设计经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站制作后付款的网站建设流程,更有漳浦免费网站建设让你可以放心的选择与我们合作。

一个简单的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对象的过程肯定会先检查这个指令的参数能否在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果不存在,再去实行类加载过程。

看完上述内容,你们对Main方法的执行过程是怎样的有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注创新互联行业资讯频道,感谢大家的支持。


网站栏目:Main方法的执行过程是怎样的
链接地址:http://azwzsj.com/article/jhdjjo.html