类加载过程类加载时序一个类型被加载到虚拟机内存中,直到从内存中卸载,它的整个生命周期都会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中验证、准备、解析创建类的实例有7种情况是主动加载连接类,如:newObject();访问类或接口的静态变量,或为静态变量赋值;调用类的静态方法;反射(如Class.forName("com.test.Test");初始化一个类的子类;Java虚拟机启动时标记为启动类的类是包含main方法(JavaTest)的类;JDK1.从7开始提供的动态语言支持,java.lang.invoke.MethodHandle实例REF_getStatic,REF_putStatic,的解析结果,如果REF_invokeStatic句柄对应的类没有初始化,会初始化其他加载条件当Java虚拟machine初始化一个类,它要求所有的父类都被初始化,但是这个规则不适用于接口。先初始化。因此,父接口不会因为它的子接口或类初始化的实现而被初始化,只有在程序第一次使用静态变量时es的特定接口,它会导致接口被初始化。只有当前程序访问的静态变量或Static方法,只有定义在当前类或当前接口中时,才能被认为是接口或类的主动使用。调用ClassLoader类的loadClass方法加载一个类并不是主动使用类,不会导致类初始化。测试示例1:publicclassTest_2extendsTest_2_A{static{System.out.println("子类静态代码块");}{System.out.println("子类代码块");}publicTest_2(){System.out.println("子类constructor");}publicstaticvoidmain(String[]args){newTest_2();}}classTest_2_A{static{System.out.println("父类静态代码块");}{System.out.println("父类代码block");}publicTest_2_A(){System.out.println("父类构造函数");}publicstaticvoidfind(){System.out.println("静态方法");}}//代码块和构造方法执行顺序//1).父类静态代码块//2).子类静态代码块//3).父类代码块//4).父类构造方法//5).子类代码块//6)。子类构造方法测试示例2:publicclassTest_1{publicstaticvoidmain(String[]args){System.out.println(Test_1_B.str);}}classTest_1_A{publicstaticStringstr="Astr";static{System.out.println("AStaticBlock");}}classTest_1_BextendsTest_1_A{static{System.out.println("BStaticBlock");}}//输出结果//AStaticBlock//Astr类加载过程加载到硬盘并读取IO类在使用时会加载字节码文件,比如调用main方法,用new关键字调用对象等,加载阶段会在内存中生成该类的java.lang.Class对象作为这个类的方法区各种数据的访问入口,验证字节码文件的正确性,准备给类的静态变量分配内存,赋默认值,用直接引用解析和替换符号引用。该节点将一些静态方法(符号引用,例如main()方法)替换为指向存储数据的内存的指针或句柄(直接引用)。这就是所谓的静态链接过程(在类加载时完成),而动态链接是在程序运行过程中完成的。用直接引用替换符号引用。初始化将类的静态变量初始化为指定值,并执行静态代码块。类加载器**_Bootstrap类加载器(BootstrapClassLoader)_**负责加载\lib\目录或-Dbootclaspath参数指定的类,如:rt.jar、tool.jar等ExtensionClassLoader负责加载\lib\ext\或-Djava.ext.dirs选项指定目录下的类和jar包。应用类加载器(SystemClassLoader)负责加载CLASSPATH或-Djava.class.path指定目录下的类和jar包。自定义类加载器:负责加载用户自定义包路径下的类包,通过ClassLoader的子类实现Class加载。测试文件:publicclassTestJVMClassLoader{publicstaticvoidmain(String[]args){System.out.println(String.class.getClassLoader());System.out.println(DESKeyFactory.class.getClassLoader());System.out.println(TestJVMClassLoader.class.getClassLoader());System.out.println();ClassLoaderappClassLoader=ClassLoader.getSystemClassLoader();ClassLoaderextClassLoader=appClassLoader.getParent();ClassLoaderbootstrapClassLoader=extClassLoader.getParent();System.out.println("bootstrapClassLoader:"+bootstrapClassLoader);System.out.println("extClassLoader:"+extClassLoader);System.out.println("appClassLoader:"+appClassLoader);System.out.println();System.out.println("bootstrapLoader加载以下文件:");URL[]urls=Launcher.getBootstrapClassPath().getURLs();for(URLurl:urls){System.out.println(url);}System.out.println();System.out.println("extClassLoader加载以下文件:");System.out.println(System.getProperty("java.ext.dirs"));System.out.println();System.out.println("appClassLoaderloadsthefollowingfiles:");System.out.println(System.getProperty("java.class.path"));}}双亲委托机制什么是双亲委托机制?当一个类加载器收到类加载请求时,它不会先尝试自己加载类,而是将请求委托给父类加载器来完成。每一级的类加载器都是如此,所以所有的请求最终都应该发送到顶层的启动类加载器,只有当父加载器报告无法完成加载请求(即需要的类不是在搜索范围内找到),子加载器将尝试自己完成加载类加载和双亲委派模型如下图所示。我们看一下ClassLoader类的loadClass方法//loadClassprotectedClass>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){//首先检查当前类是否加载Class>c=findLoadedClass(name);if(c==null){longt0=System.nanoTime();try{if(parent!=null){//如果父类加载器不为空,先尝试父类加载加载c=parent.loadClass(name,false);}else{//Bootstrap类加载器尝试加载c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){//如果classnotfound抛出ClassNotFoundException//fromthenon-nullparentclassloader}if(c==null){//如果仍然没有找到,则调用findClassinorder//tofindtheclass.longt1=System.nanoTime();//尝试加载c=findClass(name);//thisisthedefiningclassloader;recordthestatssun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if(resolve){resolveClass(c);}returnc;}}//类加载器的包含关系publicabstractclassClassLoader{privatestaticnativevoidregisterNatives();static{registerNatives();}//当前ClassLoader与parentClassLoader的包含关系privatefinalClassLoaderparent;}总结:不是树结构(只是逻辑树结构),而是包含/包关系加载顺序,应用类加载器,扩展加载器,系统加载器。如果有一个类加载器能够成功加载Test类,那么这个类加载器就称为定义类加载器,所有可能返回Class对象引用的类加载器(包括定义类加载器)都称为初始类加载器。设计父母委托机制的目的是什么?保证Java核心库的类型安全:所有java应用至少会引用java.在机器上,如果加载过程是由Java应用程序自身的类加载器完成的,那么JVM中可能存在多个版本的java.lang.Object类,而这些类仍然是不兼容的。相互不可见(起作用的是命名空间)借助双亲委派机制,Java核心库中的类加载工作统一由启动类加载器完成。这确保了所有Java应用程序都使用相同版本的Java核心类库,并且它们彼此兼容。可以保证Java核心库提供的类不会被自定义类替代。不同的类加载器可以为同一类(二进制名称)的类创建额外的命名空间。Java虚拟机中可以共存同名的类,只需要不同的类加载器加载即可。不同类加载器的类是不兼容的,相当于在JAVA虚拟机内部创建了一个Java虚拟机。另一个孤立的Java类空间,这种技术在许多框架中都有使用。自定义类加载器自定义类加载器加载类,下面是一个简单的Demoimportjava.io.ByteArrayOutputStream;importjava.io.File;importjava.io.FileInputStream;importjava.io.InputStream;publicclassClassLoaderTestextendsClassLoader{privatestaticStringrxRootPath;static{rxRootPath="/temp/class/";}@OverridepublicClassfindClass(Stringname){byte[]b=loadClassData(name);returndefineClass(name,b,0,b.length);}/***读取.class文件为word段数组**@paramname全路径类名*@return*/privatebyte[]loadClassData(Stringname){try{StringfilePath=fullClassName2FilePath(name);InputStreamis=newFileInputStream(newFile(filePath));ByteArrayOutputStreambos=newByteArrayOutputStream();byte[]buf=newbyte[2048];intr;while((r=is.read(buf))!=-1){bos.write(buf,0,r);}returnbos.toByteArray();}catch(Throwablee){e.printStackTrace();}returnnull;}/***完全限定名称转换为文件路径**@paramname*@return*/privateStringfullClassName2FilePath(Stringname){returnrxRootPath+name.replace(".","//")+".class";}publicstaticvoidmain(String[]args)throwsClassNotFoundException{ClassLoaderTestclassLoader=newClassLoaderTest();StringclassName="com.test.TestAA";Classclazz=classLoader.loadClass(className);System.out.println(clazz.getClassLoader());//输出结果//cn.xxx.xxx.loader.ClassLoaderTest@3764951d}}Tomcat类加载器Tomcat中的类加载器模型Tomcat类加载器描述了tomcat的几种主要类加载器:commonLoader:Tomcat最基本的类加载器,加载路径中的类可以通过Tomcat容器本身和各个WebAppcatalinaLoader:Tomcat容器私有类加载器加载路径中的类对Webapp是不可见的;sharaLoader:各个Webapp共享的类加载器,加载路径中的类对所有webapp可见,但对于TomcatContainer是不可见的。webappLoader:每个webapp的私有类加载,加载路径中的类只对当前webapp可见,比如加载war包中的相关类,每个war包应用都有自己的webappClassLoader对象,对应不同的命名空间,实现相互隔离,比如可以在war包中引入不同的spring版本,实现多个spring版本应用的同时运行。总结:从图中的委托关系可以看出,CommonClassLoader可以加载的类可以被CatalinaClassLoader和SharedClassLoader使用,从而实现公共类库的共享,而CatalinaClassLoader和SharedClassLoader可以自己加载的类相互隔离Webappclassloader可以使用SharedLoader加载的类,但是每个Webappclassloader实例是相互隔离的,JasperLoader的加载范围只有这个JSP文件编译出来的。它出现的目的是被丢弃:当Web容器检测到JSP文件被修改时,会替换当前的Jasperloader实例,通过新建Jsp类加载器实现JSP文件的热加载功能.Tomcat的类加载机制是否违反了java推荐的双亲委派模型?答案是:违反tomcat的不是这样实现的。tomcat为了实现隔离,不遵守这个约定。每个webapp加载器加载自己目录下的class文件,不会传递给父类加载器,打破双亲委派机制参考《深入理解 Java 虚拟机》第三版周志明ApacheTomcat文档
