一种简单的Main方法
公共类mm {
publicstaticvoidmain (字符串[ ] 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其他人的
33559 github.com/zscchaofan/open JDK-jdk8u
gdb -q java Mm //gdb设置java命令
set args Mm //设定参数名称的具体含义是百度搜索的
开始//调试
下面设置的几个断点是一个个尝试的
gdb可以直接指定文件和行数断点
详细命令百度就可以了。 我也是百度。 不总结,也不经常使用
调试代码必须参考他人的教程,才能一步一步地进行
使用gdb命令查看当前代码上下附近的代码行,然后尝试与源代码对应
像我这样不懂c语言的人,只能一步一步地看清楚方法名称意图的地方再仔细看
3 breakpoint keepy0x 00007 ffff1e7F4 ainjavamain
at/mnt/d/code/open JDK-jdk8u-master/JDK/src/share/thdwx/Java.c :478
4 breakpoint keepy0x 00007 fffc 97 d a55 in Java _ Java _ lang _ class loader _ findbootstrapclassat/mnt/d/code/open JDK-JAK
9 breakpoint keepy0x 00007 ffff1e9c 72 ingetlauncherhelperclass
at/mnt/d/code/open JDK-jdk8u-master/JDK/src/share/thdwx/Java.c :1250
breakpoint already hit 1 time
14 breakpoint keepy0x 00007 fffc 97 da 94 in Java _ Java _ lang _ class loader _ findbootstrapclassat/mnt/d/code/open JDK-oon
15 breakpoint keepy0x 00007 fffc 97 D3 eainjava _ Java _ lang _ class loader _ define class 1
at/mnt/d/code/open JDK-jdk8u-master/JDK/src/share/native/Java/lang/class loader.c :107
/mnt/d/code/open JDK-jdk8u-master是我存储代码的路径
实际上,在d驱动器代码下,在ubuntu下放入了/mnt
开始调试后,gdb进入此处时会自动停止。 这是第一个地方
/mnt/d/code/open JDK-jdk8u-master/JDK/src/share/thdwx/main.c
(主(int argc,char **argv ) ) ) ) ) ) ) ) ) ) )。
{
水平。
.省略一部分代码反正也不知道
水平。
水平。
returnjli_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_cpw
ildcard, const_javaw, const_ergo_class);}
继续调试之后找到
/mnt/d/code/openjdk-jdk8u-master/jdk/src/share/thdwx/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/thdwx/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对象的过程肯定会先检查这个指令的参数能否在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果不存在,再去实行类加载过程