应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

一文带你深扒ClassLoader内核,揭开它的神秘面纱

  本文转载自我没有三颗心脏作者:我没有三颗心脏id:wmyskxz
  MoreThanJava宣扬的是学习,不止CODE。
  如果觉得不错的朋友,欢迎关注留言分享,文末有完整的获取链接,您的支持是我前进的最大的动力!前言
  ClassLoader可以说是Java最为神秘的功能之一了,好像大家都知道怎么回事儿,又都说不清楚具体是怎么一回事。
  今天,我们就来深度扒一扒,揭开它神秘的面纱!
  Part1。类加载是做什么的?
  首先,我们知道,Java为了实现一次编译,到处运行的目标,采用了一种特别的方案:先编译为与任何具体及其环境及操作系统环境无关的中间代码(也就是。class字节码文件),然后交由各个平台特定的Java解释器(也就是JVM)来负责解释运行。
  ClassLoader就是那个把字节码交给JVM的搬运工。它负责将字节码形式的Class转换成JVM中内存形式的Class对象。
  字节码可以是来自于磁盘上的。class文件,也可以是jar包里的。class,甚至是来自远程服务器提供的字节流。字节码的本质其实就是一个有特定复杂格式的字节数组byte〔〕。
  另外,类加载器不光可以把Class加载到JVM之中并解析成JVM统一要求的对象格式,还有一个重要的作用就是审查每个类应该由谁加载。
  而且,这些Java类不会一次全部加载到内存,而是在应用程序需要时加载,这也是需要类加载器的地方。Part2。ClassLoader类结构分析
  以下就是ClassLoader的主要方法了:
  defineClass()用于将byte字节流解析成JVM能够识别的Class对象。有了这个方法意味着我们不仅可以通过。class文件实例化对象,还可以通过其他方式实例化对象,例如通过网络接收到一个类的字节码。
  findClass()通常和defineClass()一起使用,我们需要直接覆盖ClassLoader父类的findClass()方法来实现类的加载规则,从而取得要加载类的字节码。protectedClasslt;?findClass(Stringname)throwsClassNotFoundException{thrownewClassNotFoundException(name);}
  如果你不想重新定义加载类的规则,也没有复杂的处理逻辑,只想在运行时能够加载自己制定的一个类,那么你可以用this。getClass()。getClassLoader()。loadClass(class)调用ClassLoader的loadClass()方法来获取这个类的Class对象,这个loadClass()还有重载方法,你同样可以决定再什么时候解析这个类。
  loadClass()用于接受一个全类名,然后返回一个Class类型的对象。
  resolveClass()用于对Class进行链接,也就是把单一的Class加入到有继承关系的类树中。如果你想在类被加载到JVM中时就被链接(Link),那么可以在调用defineClass()之后紧接着调用一个resolveClass()方法,当然你也可以选择让JVM来解决什么时候才链接这个类(通常是真正被实实例化的时候)。
  ClassLoader是个抽象类,它还有很多子类,如果我们要实现自己的ClassLoader,一般都会继承URLClassLoader这个子类,因为这个类已经帮我们实现了大部分工作。
  例如,我们来看一下java。net。URLClassLoader。findClass()方法的实现:入参为Class的binaryname,如java。lang。StringprotectedClasslt;?findClass(finalStringname)throwsClassNotFoundException{以上代码省略通过binaryname生成包路径,如java。lang。StringjavalangString。classStringpathname。replace(。,)。concat(。class);根据包路径,找到该Class的文件资源Resourceresucp。getResource(path,false);if(res!null){try{调用defineClass生成java。lang。Class对象returndefineClass(name,res);}catch(IOExceptione){thrownewClassNotFoundException(name,e);}}else{returnnull;}以下代码省略}Part3。Java类加载流程详解
  以下就是ClassLoader加载一个class文件到JVM时需要经过的步骤。
  事实上,我们每一次在IDEA中点击运行时,IDE都会默认替我们执行以下的命令:
  javacXxxx。java找到源文件中的publicclass,再找publicclass引用的其他类,Java编译器会根据每一个类生成一个字节码文件;
  javaXxxx找到文件中的唯一主类publicclass,并根据publicstatic关键字找到跟主类关联可执行的main方法,开始执行。
  在真正的运行main方法之前,JVM需要加载、链接以及初始化上述的Xxxx类。
  这一步是读取到类文件产生的二进制流(findClass()),并转换为特定的数据结构(defineClass()),初步校验cafebabe魔法数、常量池、文件长度、是否有父类等,然后在Java堆中创建对应类的java。lang。Class实例,类中存储的各部分信息也需要对应放入运行时数据区中(例如静态变量、类信息等放入方法区)。
  以下是一个Class文件具有的基本结构的简单图示:
  如果对Class文件更多细节感兴趣的可以进一步阅读:https:juejin。impost6844904199617003528
  这里我们可能会有一个疑问,为什么JVM允许还没有进行验证、准备和解析的类信息放入方法区呢?
  答案是加载阶段和链接阶段的部分动作(比如一部分字节码文件格式验证动作)是交叉进行的,也就是说加载阶段还没完成,链接阶段可能已经开始了。但这些夹杂在加载阶段的动作(验证文件格式等)仍然属于链接操作。
  Link阶段包括验证、准备、解析三个步骤。下面我们来详细说说。
  验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
  文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
  元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java。lang。Object之外。
  字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  符号引用验证:确保解析动作能正确执行。
  验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
  准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
  1这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
  2这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
  3如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。
  例如,假设这里有一个类变量publicstaticintvalue666;,在准备阶段时初始值是0而不是666,在初始化阶段才会被真正赋值为666。
  假设是一个静态类变量publicstaticfinalintvalue666;,则再准备阶段JVM就已经赋值为666了。
  解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
  符号引用的作用是在编译的过程中,JVM并不知道引用的具体地址,所以用符号引用进行代替,而在解析阶段将会将这个符号引用转换为真正的内存地址。
  直接引用可以理解为指向类、变量、方法的指针,指向实例的指针和一个间接定位到对象的对象句柄。
  为了理解上面两种概念的区别,来看一个实际的例子吧:publicclassTester{publicstaticvoidmain(String〔〕args){Stringstr关注【我没有三颗心脏】,关注更多精彩;System。out。println(str);}}
  我们先在该类同级目录下运行javacTester编译成。class文件然后再利用javapverboseTester查看类的详细信息:上面是类的详细信息省略。。。{。。。。。publicstaticvoidmain(java。lang。String〔〕);descriptor:(〔LjavalangString;)Vflags:(0x0009)ACCPUBLIC,ACCSTATICCode:stack2,locals2,argssize10:ldc7String关注【我没有三颗心脏】,关注更多精彩2:astore13:getstatic9FieldjavalangSystem。out:LjavaioPrintStream;6:aload17:invokevirtual15MethodjavaioPrintStream。println:(LjavalangString;)V10:returnLineNumberTable:line4:0line5:3line6:10}SourceFile:Tester。java
  可以看到,上面定义的str变量在编译阶段会被解析称为符号引用,符号引用的标志是astore,这里就是astore1。
  store1的含义是将操作数栈顶的关注【我没有三颗心脏】,关注更多精彩保存回索引为1的局部变量表中,此时访问变量str就会读取局部变量表索引值为1中的数据。所以局部变量str就是一个符号引用。
  再来看另外一个例子:publicclassTester{publicstaticvoidmain(String〔〕args){System。out。println(关注【我没有三颗心脏】,关注更多精彩);}}
  这一段代码反编译之后得到如下的代码:上面是类的详细信息省略。。。{。。。。。。publicstaticvoidmain(java。lang。String〔〕);descriptor:(〔LjavalangString;)Vflags:(0x0009)ACCPUBLIC,ACCSTATICCode:stack2,locals1,argssize10:getstatic7FieldjavalangSystem。out:LjavaioPrintStream;3:ldc13String关注【我没有三颗心脏】,关注更多精彩5:invokevirtual15MethodjavaioPrintStream。println:(LjavalangString;)V8:returnLineNumberTable:line4:0line5:8}SourceFile:Tester。java
  我们可以看到这里直接使用了ldc指令将关注【我没有三颗心脏】,关注更多精彩推送到了栈,紧接着就是调用指令invokevirtual,并没有将字符串存入局部变量表中,这里的字符串就是一个直接引用。
  初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
  1声明类变量是指定初始值;
  2使用静态代码块为类变量指定初始值;
  JVM初始化步骤:
  1假如这个类还没有被加载和连接,则程序先加载并连接该类
  2假如该类的直接父类还没有被初始化,则先初始化其直接父类
  3假如类中有初始化语句,则系统依次执行这些初始化语句
  类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下几种:
  创建类的实例,也就是new的方式
  访问某个类或接口的静态变量,或者对该静态变量赋值
  调用类的静态方法
  反射(如Class。forName(com。wmyskxz。Tester))
  初始化某个类的子类,则其父类也会被初始化
  Java虚拟机启动时被标明为启动类的类,直接使用java。exe命令来运行某个主类
  使用JDK7新加入的动态语言支持时,如果一个java。lang。invoke。MethodHanlde实例最后的解析结果为REFgetstatic、REFputstatic、REFinvokeStatic、REFnewInvokeSpecial四种类型的方法句柄时,都需要先初始化该句柄对应的类
  接口中定义了JDK8新加入的默认方法(default修饰符),实现类在初始化之前需要先初始化其接口Part4。深入理解双亲委派模型
  我们在上面已经了解了一个类是如何被加载进JVM的依靠类加载器在Java语言中自带有三个类加载器:
  BootstrapClassLoader最顶层的加载类,主要加载核心类库,JREHOMElib下的rt。jar、resources。jar、charsets。jar和class等。
  ExtentionClassLoader扩展的类加载器,加载目录JREHOMElibext目录下的jar包和class文件。
  AppclassLoader也称为SystemAppClass加载当前应用的classpath的所有类。
  我们可以通过一个简单的例子来简单了解Java中这些自带的类加载器:publicclassPrintClassLoader{publicstaticvoidmain(String〔〕args){printClassLoaders();}publicstaticvoidprintClassLoaders(){System。out。println(Classloaderofthisclass:PrintClassLoader。class。getClassLoader());System。out。println(ClassloaderofLogging:com。sun。javafx。util。Logging。class。getClassLoader());System。out。println(ClassloaderofArrayList:java。util。ArrayList。class。getClassLoader());}}
  上方程序打印输出如下:Classloaderofthisclass:sun。misc。LauncherAppClassLoader18b4aac2ClassloaderofLogging:sun。misc。LauncherExtClassLoader60e53b93ClassloaderofArrayList:null
  如我们所见,这里分别对应三种不同类型的类加载器:AppClassLoader、ExtClassLoader和BootstrapClassLoader(显示为null)。
  一个很好的问题是:Java类是由java。lang。ClassLoader实例加载的,但类加载器本身也是类,那么谁来加载类加载器呢?
  我们假装不知道,先来跟着源码一步一步来看。
  在JDK源码sun。misc。Launcher中,蕴含了Java虚拟机的入口方法:publicclassLauncher{privatestaticLauncherlaunchernewLauncher();privatestaticStringbootClassPathSystem。getProperty(sun。boot。class。path);publicstaticLaunchergetLauncher(){returnlauncher;}privateClassLoaderloader;publicLauncher(){CreatetheextensionclassloaderClassLoaderextcl;try{extclExtClassLoader。getExtClassLoader();}catch(IOExceptione){thrownewInternalError(Couldnotcreateextensionclassloader,e);}Nowcreatetheclassloadertousetolaunchtheapplicationtry{loaderAppClassLoader。getAppClassLoader(extcl);}catch(IOExceptione){thrownewInternalError(Couldnotcreateapplicationclassloader,e);}设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解Thread。currentThread()。setContextClassLoader(loader);}Returnstheclassloaderusedtolaunchthemainapplication。publicClassLoadergetClassLoader(){returnloader;}Theclassloaderusedforloadinginstalledextensions。staticclassExtClassLoaderextendsURLClassLoader{}Theclassloaderusedforloadingfromjava。class。path。runsinarestrictedsecuritycontext。staticclassAppClassLoaderextendsURLClassLoader{}}
  源码有精简,但是我们可以得到以下信息:
  1Launcher初始化了ExtClassLoader和AppClassLoader。
  2Launcher没有看到BootstrapClassLoader的影子,但是有一个叫做bootClassPath的变量,大胆一猜就是BootstrapClassLoader加载的jar包的路径。
  3ExtClassLoader和AppClassLoader都继承自URLClassLoader,进一步查看ClassLoader的继承树,传说中的双亲委派模型也并没有出现。
  4注意以下代码:ClassLoaderextcl;extclExtClassLoader。getExtClassLoader();loaderAppClassLoader。getAppClassLoader(extcl);
  分别跟踪查看到这两个ClassLoader初始化时的代码:一直追踪到最顶层的ClassLoader定义,构造器的第二个参数标识了类加载器的父类privateClassLoader(Voidunused,ClassLoaderparent){this。parentparent;代码省略。。。。。}Ext设置自己的父类为nullpublicExtClassLoader(File〔〕var1)throwsIOException{super(getExtURLs(var1),(ClassLoader)null,Launcher。factory);SharedSecrets。getJavaNetAccess()。getURLClassPath(this)。initLookupCache(this);}手动把Ext设置为App的parent(这里的var2是传进来的extc1)AppClassLoader(URL〔〕var1,ClassLoadervar2){super(var1,var2,Launcher。factory);this。ucp。initLookupCache(this);}
  由此,我们得到了这样一个类加载器的关系图:
  奇怪,为什么ExtClassLoader的parent明明是null,我们却一般地认为BootstrapClassLoader才是ExtClassLoader的父加载器呢?
  答案的一部分就藏在java。lang。ClassLoader。loadClass()方法里面:(这也就是著名的双亲委派模型现场了)protectedClasslt;?loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){首先检查是否已经加载过了Classlt;?cfindLoadedClass(name);if(cnull){longt0System。nanoTime();try{if(parent!null){父加载器不为空则调用父加载器的loadClass方法cparent。loadClass(name,false);}else{父加载器为空则调用BootstrapClassLoadercfindBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){ClassNotFoundExceptionthrownifclassnotfoundfromthenonnullparentclassloader}if(cnull){Ifstillnotfound,theninvokefindClassinordertofindtheclass。longt1System。nanoTime();父加载器没有找到,则调用findclasscfindClass(name);thisisthedefiningclassloader;recordthestatssun。misc。PerfCounter。getParentDelegationTime()。addTime(t1t0);sun。misc。PerfCounter。getFindClassTime()。addElapsedTimeFrom(t1);sun。misc。PerfCounter。getFindClasses()。increment();}}if(resolve){调用resolveClass()resolveClass(c);}returnc;}}
  代码逻辑很好地解释了双亲委派的原理。
  1当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。(每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。)
  2当前ClassLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到BootstrapClassLoader。(当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。)
  所以,答案的另一部分是因为最高一层的类加载器Bootstrap是通过CC实现的,并不存在于JVM体系内,所以输出为null。
  OK,我们理解了为什么ExtClassLoader的父加载器为什么是表示为null的Bootstrap加载器,那我们自己实现的ClassLoader父加载器应该是谁呢?
  观察一下ClassLoader的源码就知道了:protectedClassLoader(ClassLoaderparent){this(checkCreateClassLoader(),parent);}protectedClassLoader(){this(checkCreateClassLoader(),getSystemClassLoader());}
  类加载器的parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:
  1由外部类创建ClassLoader时直接指定一个ClassLoader为parent;
  2由getSystemClassLoader()方法生成,也就是在sun。misc。Laucher通过getClassLoader()获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。(建议去看一下源码)
  简单来说,主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String,同时也避免了重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类,如果相互转型的话会抛java。lang。ClassCaseException。
  如果我们要实现自己的类加载器,不管你是直接实现抽象类ClassLoader,还是继承URLClassLoader类,或者其他子类,它的父加载器都是AppClassLoader。
  因为不管调用哪个父类构造器,创建的对象都必须最终调用getSystemClassLoader()作为父加载器。而该方法最终获取到的正是AppClassLoader。
  这也就是我们熟知的最终的双亲委派模型了。
  Part5。实现自己的类加载器
  在学习了类加载器的实现机制之后,我们知道了双亲委派模型并非强制模型,用户可以自定义类加载器,在什么情况下需要自定义类加载器呢?
  1隔离加载类。在某些框架内进行中间件与应用的模块隔离,把类加载器到不同的环境。比如,阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包。
  2修改类加载方式。类的加载模型并非强制,除了Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需的动态加载。
  3扩展加载源。比如从数据库、网络,甚至是电视机顶盒进行加载。(下面我们会编写一个从网络加载类的例子)
  4防止源码泄露。Java代码容易被编译和篡改,可以进行编译加密。那么类加载器也需要自定义,还原加密的字节码。
  实现一个自定义的类加载器比较简单:继承ClassLoader,重写findClass()方法,调用defineClass()方法,就差不多行了。
  我们先来编写一个测试用的类文件:publicclassTester{publicvoidsay(){System。out。println(关注【我没有三颗心脏】,解锁更多精彩!);}}
  在同级目录下执行javacTester。java命令,并把编译后的Tester。class放到指定的目录下(我这边为了方便就放在桌面上啦UserswmyskxzDesktop)
  我们编写自定义ClassLoader代码:importjava。io。ByteArrayOutputStream;importjava。io。File;importjava。io。FileInputStream;importjava。io。IOException;publicclassMyClassLoaderextendsClassLoader{privatefinalStringmLibPath;publicMyClassLoader(Stringpath){TODOAutogeneratedconstructorstubmLibPathpath;}OverrideprotectedClasslt;?findClass(Stringname)throwsClassNotFoundException{TODOAutogeneratedmethodstubStringfileNamegetFileName(name);FilefilenewFile(mLibPath,fileName);try{FileInputStreamisnewFileInputStream(file);ByteArrayOutputStreambosnewByteArrayOutputStream();intlen0;try{while((lenis。read())!1){bos。write(len);}}catch(IOExceptione){e。printStackTrace();}byte〔〕databos。toByteArray();is。close();bos。close();returndefineClass(name,data,0,data。length);}catch(IOExceptione){TODOAutogeneratedcatchblocke。printStackTrace();}returnsuper。findClass(name);}获取要加载的class文件名privateStringgetFileName(Stringname){TODOAutogeneratedmethodstubintindexname。lastIndexOf(。);if(index1){returnname。class;}else{returnname。substring(index1)。class;}}}
  我们在findClass()方法中定义了查找class的方法,然后数据通过defineClass()生成了Class对象。
  我们需要删除刚才在项目目录创建的Tester。java和编译后的Tester。class文件来观察效果:importjava。lang。reflect。InvocationTargetException;importjava。lang。reflect。Method;publicclassClassLoaderTester{publicstaticvoidmain(String〔〕args){创建自定义的ClassLoader对象MyClassLoadermyClassLoadernewMyClassLoader(UserswmyskxzDesktop);try{加载class文件Classlt;?cmyClassLoader。loadClass(Tester);if(c!null){try{Objectobjc。newInstance();Methodmethodc。getDeclaredMethod(say,null);通过反射调用Test类的say方法method。invoke(obj,null);}catch(InstantiationExceptionIllegalAccessExceptionNoSuchMethodExceptionSecurityExceptionIllegalArgumentExceptionInvocationTargetExceptione){TODOAutogeneratedcatchblocke。printStackTrace();}}}catch(ClassNotFoundExceptione){TODOAutogeneratedcatchblocke。printStackTrace();}}}
  运行测试,正常输出:关注【我没有三颗心脏】,解锁更多精彩!
  突破了JDK系统内置加载路径的限制之后,我们就可以编写自定义的ClassLoader。你完全可以按照自己的意愿进行业务的定制,将ClassLoader玩出花样来。
  例如,一个加密解密的类加载器。(不涉及完整代码,我们可以来说一下思路和关键代码)
  首先,在编译之后的字节码文件中动一动手脚,例如,给文件每一个byte异或一个数字2:(这就算是模拟加密过程)FilefilenewFile(path);try{FileInputStreamfisnewFileInputStream(file);FileOutputStreamfosnewFileOutputStream(pathen);intb0;intb10;try{while((bfis。read())!1){每一个byte异或一个数字2fos。write(b2);}fos。close();fis。close();}catch(IOExceptione){TODOAutogeneratedcatchblocke。printStackTrace();}}catch(FileNotFoundExceptione){TODOAutogeneratedcatchblocke。printStackTrace();}
  然后我们再在findClass()中自己解密:FilefilenewFile(mLibPath,fileName);try{FileInputStreamisnewFileInputStream(file);ByteArrayOutputStreambosnewByteArrayOutputStream();intlen0;byteb0;try{while((lenis。read())!1){将数据异或一个数字2进行解密b(byte)(len2);bos。write(b);}}catch(IOExceptione){e。printStackTrace();}byte〔〕databos。toByteArray();is。close();bos。close();returndefineClass(name,data,0,data。length);}catch(IOExceptione){TODOAutogeneratedcatchblocke。printStackTrace();}
  其实非常类似,也不做过多讲解,直接上代码:importjava。io。ByteArrayOutputStream;importjava。io。InputStream;importjava。net。URL;publicclassNetworkClassLoaderextendsClassLoader{privateStringrootUrl;publicNetworkClassLoader(StringrootUrl){指定URLthis。rootUrlrootUrl;}获取类的字节码OverrideprotectedClasslt;?findClass(Stringname)throwsClassNotFoundException{byte〔〕classDatagetClassData(name);if(classDatanull){thrownewClassNotFoundException();}else{returndefineClass(name,classData,0,classData。length);}}privatebyte〔〕getClassData(StringclassName){从网络上读取的类的字节StringpathclassNameToPath(className);try{URLurlnewURL(path);InputStreaminsurl。openStream();ByteArrayOutputStreambaosnewByteArrayOutputStream();intbufferSize4096;byte〔〕buffernewbyte〔bufferSize〕;intbytesNumRead0;读取类文件的字节while((bytesNumReadins。read(buffer))!1){baos。write(buffer,0,bytesNumRead);}returnbaos。toByteArray();}catch(Exceptione){e。printStackTrace();}returnnull;}privateStringclassNameToPath(StringclassName){得到类文件的URLreturnrootUrlclassName。replace(。,)。class;}}
  Part6。必要的扩展阅读
  学习到这里,我们对ClassLoader已经不再陌生了,但是仍然有一些必要的知识点需要去掌握,希望您能认真阅读以下的材料:
  1能不能自己写一个类叫java。lang。System或者java。lang。String?https:blog。csdn。nettang9140articledetails42738433
  2深入理解Java之JVM启动流程https:cloud。tencent。comdeveloperarticle1038435
  3真正理解线程上下文类加载器(多案例分析)https:blog。csdn。netyangcheng33articledetails52631940
  4曹工杂谈:Java类加载器还会死锁?这是什么情况?https:www。cnblogs。comgreywolfp11378747。htmllabel2
  5谨防JDK8重复类定义造成的内存泄漏https:segmentfault。coma1190000022837543
  7Tomcat类加载器的实现https:juejin。impost6844903945496690695
  8Spring中的类加载机制https:www。shuzhiduo。comAgVdnwgAlzW参考资料
  《深入分析JavaWeb技术内幕》许令波著
  Java类加载机制分析https:www。jianshu。comp3615403c7c84
  Class文件解析实战https:juejin。impost6844904199617003528
  图文兼备看懂类加载机制的各个阶段,就差你了!https:juejin。impost6844904119258316814
  Java面试知识点解析(三)JVM篇https:www。wmyskxz。com20180516javamianshizhishidianjiexisanjvmpian
  一看你就懂,超详细Java中的ClassLoader详解https:blog。csdn。netbribluearticledetails54973413

娃要上小学了,妈妈继续家里蹲还是找工作?看这些条件允许吗在孩子小的时候,因为没有人帮忙照顾孩子,很多宝妈不得不选择从职场退出回归家庭。于是宝妈们想着只要等到孩子上小学了,她们就又可以回到工作岗位上了。不过问题可能没有像宝妈们想象的那……就问你爱不爱吧教学目的1。学习颜色搭配技巧;2。了解不同主体物的绘画技巧;3。体会综合材料的画面表现效果。教学工具白卡、马克笔、勾线笔、宝石装饰教学引导……夏日水蜜桃教学目的1。水蜜桃百科知识2。学习渐变色晕染及线条装饰的叠加绘画3。感受美丽的色彩搭配4。学习平面绘画与立体装饰相结合的画面表现效果教学工具……波士顿艺术家最新作品落地Seaport,青铜和玻璃碰撞出水滴在各大城市的街头,适当添加一些合适的雕塑,不仅可以为这座城市增添些许艺术的气息,还能给市民以及游客带来好心情。这不,波士顿当地艺术家ChrisWilliams最新的艺术雕……中国银行原油宝事件警示守住父母的钱袋子,小心那些老来横祸1hr中国银行原油宝太狠了。放高利贷、P2P、炒股,最多也就是上家跑路,平仓,你血本无归,哪里比得上原油宝。夜之间,不仅本金全部亏光,还倒欠银行一大笔钱了。我……临终的人,一般身上会出现5种表现,亲友不妨好好陪陪吧人到一定年纪,总要接受周围亲朋好友的离去。只不过,有时候我们未必会注意到他们临终时的表现,错过和他们最后的告别,然后留下无法化解的遗憾。承担着身边人离开的苦楚和无奈。因此……这3种肉食是长高能手,妈妈经常弄给孩子吃,让娃轻松长高个著名的医学杂志《柳叶刀》曾发表过一篇文章,我国19岁男性身高平均值是173。5厘米,而女性身高的平均值则是163。5厘米。然而,据调查数据显示,很多人其实并没有达到身高的平均值……有多少家庭知道孕前体检的重要性和必要性?一、为什么要孕前体检虽然现在没有婚检的硬性规定,但为了每个家庭能够生个健康和出色的宝宝,孕前体检是非常必要且非常重要的哦。孕前体检能够调整夫妻在最佳状态下怀孕,可以……是什么让家长对教育产生无力感?每个父母都希望孩子能够变得更好,但是随着他们年龄的增加,家长变得越来越不自信,孩子不听话,叛逆,犯错这些似乎都在预示着家长教育的失败,从而产生一种无力感。面对这种情况,很……秋季腹泻又来了!如何避免中招?虽然许多地方还很热,但立秋过后,就是秋天来啦。讲起秋天的儿童多发病,许多妈妈就会立马惊醒:它来了,它来了,秋季腹泻这个坏蛋它又来了!是的,那些处心积虑攻击宝宝的细菌……真假宫缩傻傻分不清?这三种表现赶紧去医院都说宫缩阵痛就是即将发动的信号,因此越是临近预产期,许多准妈妈越忧心忡忡,不知道宝宝什么时候出生。肚子里稍微有些动静,就以为是分娩前的征兆,家人也越严阵以待,一有什么动静……ampampquot等放学了妈妈第一个来接你ampampqu转眼又到了开学季,如果家里有准备上幼儿园的孩子,那么父母和孩子都会或多或少有些焦虑。孩子会在人生中第一次体验到分离,会哭、会闹、会不想去幼儿园。而家长也特别心疼孩子,会担心孩子……
孩子不听话,像我这样做,简单又有效在春天想念天堂的TA教育听我说成年人,几乎都面对过亲人的逝去。爷爷奶奶或是外公外婆。尤其是失去父母的成年人,每当生活遇到困境时,就特别容易想起失去的父母。想起他们的不容易……又一起悲剧4岁幼童闷死在校车内!自救方式家长一定要教会孩子孩子是一个家庭的希望,是一个家庭的未来。没有哪一对父母不是把孩子顶在头上,捧在手心,唯恐他们有一点闪失。而一旦孩子出现意外,一家人捧在手上的希望就没有了。那种痛彻心扉的失……用过才敢说好用!盘点孕期自用的5款洗发水,个个都是独家珍藏相见恨晚!怀孕期发掘的宝藏洗发水,最简单粗暴的还是这个!油头克星、蓬松神器,动动手指就能拥有郭碧婷发质的柔顺!怀孕再也不用羡慕少女了,感觉自己归来仍是少女我拥……疱疹性咽峡炎的治疗疱疹性咽峡炎有传染性,跟手足口病是堂兄弟,幼儿园的宝宝更容易交叉感染,它的临床表现可以有不发热,咽喉部长疱疹,上鄂处出血点。大多表现为高热,39度甚至40度的高温,上颚或者咽峡……陈粟搭档之脱颖,讲述差点胎死腹中的苏中七战七捷内幕(八)粟裕忧心邵伯战场,邵伯关系全局十纵能守么?李默庵确为用兵高手,他看到了华中野战军的最大的问题,兵力不足,诺大的苏中解放区,既要保持主力机动作战,又要防御多路国军进攻,鱼和……13个月孕妇禁止吃的蔬菜有哪些?13个月孕妇禁止吃的蔬菜有哪些目前没有明确的证据支持,13个月的孕妇禁止吃哪种蔬菜,理论上没有什么食物在孕期是绝对禁止吃的。孕妇和非孕妇都应禁止吃有毒的蔬菜。此外,禁止食……现在孩子都怎么了?(三)前面两篇讲的都是男生,这件事讲的是女生。事情是这样的:周五的时候体育活动课,我家姑娘和同学们玩,包括那个女生,大家在玩沙子,我家姑娘不小心把沙子弄到了那个女生的身上,那姑娘直接……除夕年夜饭,分享6道下酒硬菜,都是家常做法,学会待客倍有面子一岁之末为腊,意为新旧交替,辞旧迎新之时,年味儿越来越浓。春节也愈发愈近了,也抵挡不住一颗回家的心,纵然相隔千万里,依然影响不了一趟归家的行程和期盼已久的那顿团圆饭!除夕年夜饭……边洗拖边除菌一机搞定?这应该是养娃家庭的更佳选择大家好,我是梦想是个猪,今天为大家带来的是一款洗地机的使用评测。前言假设要在手持吸尘器、扫地机器人和拖地机中要让我做一个选择的话,我想我应该是会毫不犹豫的选择拖地机。……千年的铁树开了花千年的铁树开了花说千年的铁树开了花,是原先的一句俗话。这句俗话前面还有一句黄鳅生鳞马长角,后面才是千年的铁树开了花。这句连贯起来的俗话,说的是这样的事情很难出……孩子总不爱跟人打招呼,怎么办?经常有父母抱怨:孩子见人不说话,甚至躲到父母身后,见人总不爱打招呼,埋怨孩子胆小、不懂礼貌,真的是这样吗?背后到底是什么原因,怎么解决呢?果妈四问帮你找到答案!果妈……减肥有秘诀,谨记这几项,甩肉很容易生活中,无数小事关系着我们的减肥大业,很多人减肥失败的原因,都隐藏在这些小事中!想要减肥效果好,也是有秘诀的,比如,下面的6件小事,千万不能再做了,改善了这些小事,你离成……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网