当前位置: 首页 > 后端技术 > Java

类加载过程——面试必问

时间:2023-04-01 23:55:41 Java

类加载过程loading加载是通过双亲委托机制加载的。主要是出于安全考虑。父加载器不是加载器的加载器,也不是父类加载的加载器。linkinglinkverificationpreparationpreparation静态变量赋默认值,privatestaticinttest=10;在这个阶段,只有test被赋予默认值0,而不是10。resolutionResolution将类、方法、属性等符号引用解析为直接引用。常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用。initalizing初始化privatestaticinttest=10会被赋值到这一步中classtest=10的加载过程publicclassT002_ClassLoaderLevel{publicstaticvoidmain(String[]args){//由顶级类Bootstrap加载System.out.println(String.class.getClassLoader());System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());System.out.println(T002_ClassLoaderLevel.class.getClassLoader());System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());System.out.println(T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());System.out.println(newT006_MSBClassLoader().getParent());System.out.println(ClassLoader.getSystemClassLoader());}}每个类加载后,都会生成一个对应的类对象,我们可以通过类对象获取。输出结果为:nullsun.misc.Launcher$ExtClassLoader@5e2de80csun.misc.Launcher$AppClassLoader@18b4aac2nullnullsun.misc.Launcher$AppClassLoader@18b4aac2sun.misc.Launcher$AppClassLoader@18b4aac2我们先看一下String的ClassLoad,以及String属于核心类库,使用顶级类加载器Bootstrap加载,所以打印为null,sun.net.spi.nameservice.dns.DNSNameService的ClassLoad为ExtClassLoader,然后我们传给System.out。println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());获取DNSNameService加载器的加载器,即ExtClassLoader的加载器也打印为null,也就是说ExtClassLoader的加载器是Bootrap。各种loader的层级关系如下图所示。加载一个类到内存的过程就是根据左边的箭头方向判断是否已经加载。接下来,执行加载操作。如果我们有一个带有自定义加载器的类是第一次加载,流程是:检查CustomClassLoad是否已经加载=否>检查AppClassLoad=否>检查ExtensionClassLoad=否>检查Bootstrap=否>Bootstrap尝试加载,不是核心类库=>ExtensionClassLoadloading=notfound>AppClassLoadloading=notfound>CustomClassLoadloadedsuccessfulloadedorthrowsanexceptionClassNotFoundException,我们通常说的Bootstrap是所有加载器的父类,自定义ClassLoad是底层加载器,是的不是说上图是基于Bootstrap=>Extension=>APP=>CustomClassLoad的继承关系吗?答案是否定的,上面的关系并不是建立在继承关系之上的。它们是继承的,如下图所示。比如CustomClassLoad的父加载器是AppClassLoad,AppClassLoad保存在CustomClassLoad的成员变量中。Q:为什么要使用双亲委派机制?主要是为了安全。如果所有的加载操作都使用一个类加载,我可以自定义一个核心类库比如String来覆盖jdk提供的String。我们现在使用双亲委派机制。每次加载一个类,它都会先检查它是否已经加载。载入后直接返回。同时上层已经加载完毕,下层不需要加载。下面我们用一个小程序来看看不同的加载器加载了什么东西。publicclassT003_ClassLoaderScope{publicstaticvoidmain(String[]args){StringpathBoot=System.getProperty("sun.boot.class.path");System.out.println(pathBoot.replaceAll(";",System.lineSeparator()));System.out.println("--------------------");StringpathExt=System.getProperty("java.ext.dirs");System.out.println(pathExt.replaceAll(";",System.lineSeparator()));System.out.println("--------------------");StringpathApp=System.getProperty("java.class.path");System.out.println(pathApp.replaceAll(";",System.lineSeparator()));}}输出结果为:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/主页/jre/lib/jsse.jar:/库/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/classes---------------------/Users/yanghongxing/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java-------------------/Users/yanghongxing/Library/ApplicationSupport/Code/User/workspaceStorage/7172d33d4189eaa64607305e947a5428/redhat.java/jdt_ws/src_28ac311/binHowtoimplementacustomclassloader首先弄清楚一件事,如果我们想加载一个How写类?我们可以直接调用AppClassLoad的loadClass()方法publicclassTest{publicstaticvoidmain(String[]args)throwsClassNotFoundException{//执行加载操作Classclazz=Test.class.getClassLoader().loadClass("com.yhx.test.test1“);system.out.println(clazz.getName());}}}}}下面下面我们看一下:protectedclass<?>loadsclass(s??tringname,booleanResolve)抛出ClassNotFoundFoundException{name)){//首先,检查类是否已经加载了类<?>c=findloadedclass(name);if(c==null){longt0=system.nanotime();try{if(if(parent!parent!=null){c=parent.loadClass(name,false);}else{c=findbootstrapclassornull(name);}}catch(classNotFoundExceptione){//classNotFoundException如果classnotfoundexceptionthrowthrowtheclassnot/n如果从非nullparentclass中找到//loader}if(c==null){//如果仍然找不到,则调用findClassinorder//找到课程。longt1=System.nanoTime();c=findClass(名字);//这是定义类加载器;记录统计数据sun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if(resolve){resolveClass(c);}返回c;}.....受保护的ClassfindClass(Stringname)throwsClassNotFoundException{thrownewClassNotFoundException(name);}}}}findloadedClass(name)查看查看加载加载,nullnull则则被被,调用父类被没有的父类调用父类的父类父类的父类父类父类父类父类父类父类调用箭头,查看是否被加载,如果全部未加载,开始执行指示图的右半部分,longt1=System.nanoTime();c=findClass(name);调用findClass这个方法,但是这个方法就直接抛出异常(所以我们只需要继承ClassLoader重写findClass方法就可以实现自定义加载器)抛出的异常会被子类捕获,子类会重复执行find操作。publicclassTestextendsClassLoader{publicstaticintseed=0B10110110;@OverrideprotectedClassfindClass(Stringname)throwsClassNotFoundException{ceFilef=newFile("/User"s/yanghongxing,Downname(,'/