类加载机制 1. 类的加载过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7 个阶段。其中准备、验证、解析 3 个部分统称为连接(Linking)。如图所示:
1 2 3 4 5 6 7 graph LR 加载-->验证 验证-->准备 准备-->解析 解析-->初始化 初始化-->使用 使用-->卸载 
加载、验证、准备、初始化和卸载这 5 个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 语言的运行时绑定(也称为动态绑定或晚期绑定)。以下陈述的内容都已 HotSpot 为基准。
1.1 加载 虚拟机在加载阶段需要完成三件事:
通过一个类的全限定名来获取定义此类的二进制字节流,如 Class 文件,网络,动态生成,数据库等 
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口 
 
1.2 验证 验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全,验证阶段大致会完成 4 个阶段的检验动作:
文件格式验证:验证字节流是否符合 Class 文件格式的规范;例如:是否以魔术 0xCAFEBABE 开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。 
元数据验证:对字节码描述的信息进行语义分析(注意:对比 javac 编译阶段的语义分析),以保证其描述的信息符合 Java 语言规范的要求;例如:这个类是否有父类,除了 java.lang.Object 之外。 
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。 
符号引用验证:确保解析动作能正确执行。 
 
1.3 准备 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被 static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值.
如下定义:public static int value=123; 那变量 value 在准备阶段过后的初始值为 0 而不是 123.因为这时候尚未开始执行任何 java 方法,而把 value 赋值为 123 的 putstatic 指令是程序被编译后,存放于类构造器()方法之中,所以把 value 赋值为 123 的动作将在初始化阶段才会执行。
如下定义:public static final int value=123; 即当类字段的字段属性是 ConstantValue 时,会在准备阶段初始化为指定的值,所以标注为 final 之后,value 的值在准备阶段初始化为 123 而非 0.
1.4 解析 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析针对如下 7 类符号引用进行:
类或接口
 
1.5 初始化 类初始化阶段是类加载过程的最后一步,才真正开始执行类中定义的 Java 程序代码(或者说是字节码)。前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。
在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器()方法的过程。我们放到后面再讲()方法是怎么生成的,在这里,我们先看一下()方法执行过程中可能会影响程序运行行为的一些特点和细节,这部分相对更贴近于普通的程序开发人员[7]:()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。()方法与类的构造函数(或者说实例构造器()方法)不同,它不需要显式地调用父类构造器,虚拟机会保证在子类的()方法执行之前,父类的()方法已经执行完毕。因此在虚拟机中第一个被执行的()方法的类肯定是 java.lang.Object。()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作,如下代码执行字段 B 的值将会是 2 而不是 1。()方法执行顺序:           
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package  sf.jvm.load; class  Parent  {     public  static  int  A  =  1 ;     static  {         A = 2 ;     }      public  int  getA () {          return  A;      } } class  Sub  extends  Parent  {     public  static  int  B  =  A;      public  int  getB () {          return  B;      }      public  static  void  main (String[] args)  {          new  Parent ();         System.out.println(Sub.B);         System.out.println(new  Sub ().getB());     } } 
·()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成()方法。()方法。但接口与类不同的是,执行接口的()方法不需要先执行父接口的()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的()方法。()方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有耗时很长的操作,那就可能造成多个进程阻塞,在实际应用中这种阻塞往往是很隐蔽的。          
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package  sf.jvm.load;class  DeadLoopClass  {    static  {                  if  (true ) {             System.out.println(Thread.currentThread() + "initDeadLoopClass" );             while  (true ) {             }         }     }     public  static  void  main (String[] args)  {         Runnable  script  =  new  Runnable () {             public  void  run ()  {                 System.out.println(Thread.currentThread() + "start" );                 DeadLoopClass  dlc  =  new  DeadLoopClass ();                 System.out.println(Thread.currentThread() + "runover" );             }         };         Thread  thread1  =  new  Thread (script);         Thread  thread2  =  new  Thread (script);         thread1.start();         thread2.start();     } } 
运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 Thread[main,5 ,main]initDeadLoopClass 通过分析:一条线程正在死循环以模拟长时间操作,另外一条线程在阻塞等待. 线程堆栈如下: 2017 -07 -29  20 :05 :00 Full thread dump Java HotSpot (TM)  64 -Bit Server VM  (25.91 -b14 mixed mode) : "Monitor Ctrl-Break"  #10  daemon prio=5  os_prio=0  tid=0x0000000018554800  nid=0x4920  runnable [0x00000000190de000 ]   java.lang.Thread.State: RUNNABLE         at java.net.DualStackPlainSocketImpl.accept0(Native Method)         at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131 )         at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409 )         at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199 )         - locked <0x00000000d79d67c0 > (a java.net.SocksSocketImpl)         at java.net.ServerSocket.implAccept(ServerSocket.java:545 )         at java.net.ServerSocket.accept(ServerSocket.java:513 )         at com.intellij.rt.execution.application.AppMain$1. run(AppMain.java:79 )         at java.lang.Thread.run(Thread.java:745 ) "Finalizer"  #3  daemon prio=8  os_prio=1  tid=0x00000000027d8800  nid=0x2d14  in Object.wait() [0x000000001837e000 ]   java.lang.Thread.State: WAITING (on object monitor)         at java.lang.Object.wait(Native Method)         - waiting on <0x00000000d7808ee0 > (a java.lang.ref.ReferenceQueue$Lock)         at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143 )         - locked <0x00000000d7808ee0 > (a java.lang.ref.ReferenceQueue$Lock)         at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164 )         at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209 ) "Reference Handler"  #2  daemon prio=10  os_prio=2  tid=0x00000000027d3000  nid=0x4914  in Object.wait() [0x000000001827f000 ]   java.lang.Thread.State: WAITING (on object monitor)         at java.lang.Object.wait(Native Method)         - waiting on <0x00000000d7806b50 > (a java.lang.ref.Reference$Lock)         at java.lang.Object.wait(Object.java:502 )         at java.lang.ref.Reference.tryHandlePending(Reference.java:191 )         - locked <0x00000000d7806b50 > (a java.lang.ref.Reference$Lock)         at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153 ) "main"  #1  prio=5  os_prio=0  tid=0x000000000220e000  nid=0x450c  runnable [0x00000000026de000 ]   java.lang.Thread.State: RUNNABLE         at sf.jvm.load.DeadLoopClass.<clinit>(DeadLoopClass.java:8 )         at java.lang.Class.forName0(Native Method)         at java.lang.Class.forName(Class.java:264 )         at com.intellij.rt.execution.application.AppMain.main(AppMain.java:123 ) "VM Thread"  os_prio=2  tid=0x0000000016ff7000  nid=0x6d4  runnable"GC task thread#0 (ParallelGC)"  os_prio=0  tid=0x00000000026f7800  nid=0x4890  runnable"GC task thread#1 (ParallelGC)"  os_prio=0  tid=0x00000000026f9000  nid=0x4514  runnable"VM Periodic Task Thread"  os_prio=2  tid=0x00000000184e1800  nid=0x4934  waiting on conditionJNI global references: 15  
2 类加载器 2.1  类加载器概述 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。
2.2 类加载器的结构 1 2 3 4 5 graph BT 启动类加载器-->扩展类加载器 扩展类加载器-->应用类加载器 应用类加载器-->自定义加载器1 应用类加载器-->自定义加载器2 
Java 虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),这个类加载器使用 C++语言实现[2],是虚拟机自身的一部分;另外一种就是所有其他的类加载器,这些类加载器都由 Java 语言实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。从 Java 开发人员的角度来看,类加载器就还可以划分得更细致一些,绝大部分 Java 程序都会使用到以下三种系统提供的类加载器::
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
破坏双亲委派模型
(1)将以 java.*开头的类,委派给父类加载器加载。
 
虽然使用了“被破坏”这个词来形容上述不符合双亲委派模型原则的行为,但这里“被破坏”并不带有贬义的感情色彩。只要有足够意义和理由,突破已有的原则就可算作一种创新。正如 OSGi 中的类加载器并不符合传统的双亲委派的类加载器,并且业界对其为了实现热部署而带来的额外的高复杂度还存在不少争议,但在 Java 程序员中基本有一个共识:OSGi 中对类加载器的使用是很值得学习的,弄懂了 OSGi 的实现,自然就明白了类加载器的精粹。
2.3 自定义类加载器实例: 2.3.1 文件加载: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package  sf.jvm.load.classloader;import  java.io.ByteArrayOutputStream;import  java.io.File;import  java.io.FileInputStream;import  java.io.IOException;import  java.io.InputStream;import  java.lang.reflect.Method;public  class  FileSystemClassLoader  extends  ClassLoader  {  private  String rootDir;   public  FileSystemClassLoader (String rootDir)  {       this .rootDir = rootDir;   }   protected  Class<?> findClass(String name) throws  ClassNotFoundException {       byte [] classData = getClassData(name);       if  (classData == null ) {           throw  new  ClassNotFoundException ();       } else  {           return  defineClass(name, classData, 0 , classData.length);       }   }   private  byte [] getClassData(String className) {       String  path  =  classNameToPath(className);       try  {           InputStream  ins  =  new  FileInputStream (path);           ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();           int  bufferSize  =  4096 ;           byte [] buffer = new  byte [bufferSize];           int  bytesNumRead  =  0 ;           while  ((bytesNumRead = ins.read(buffer)) != -1 ) {               baos.write(buffer, 0 , bytesNumRead);           }           return  baos.toByteArray();       } catch  (IOException e) {           e.printStackTrace();       }       return  null ;   }   private  String classNameToPath (String className)  {       return  rootDir + File.separatorChar + className.replace('.' , File.separatorChar) + ".class" ;   }   public  static  void  main (String[] args)  {       String  classDataRootPath  =  "D:\\Code\\Jcode\\notes\\java-jlp\\java-jvm\\target\\classes" ;       FileSystemClassLoader  fileSystemClassLoader1  =  new  FileSystemClassLoader (classDataRootPath);       FileSystemClassLoader  fileSystemClassLoader2  =  new  FileSystemClassLoader (classDataRootPath);       String  className  =  "sf.jvm.load.simple.Sample" ;       try  {           Class<?> class1 = fileSystemClassLoader1.loadClass(className);           Object  obj1  =  class1.newInstance();           Class<?> class2 = fileSystemClassLoader1.loadClass(className);           Object  obj2  =  class2.newInstance();           Method  setSampleMethod  =  class1.getMethod("setSample" , Object.class);           setSampleMethod.invoke(obj1, obj2);           Method  setSampleMethod2  =  class1.getMethod("compare" , Object.class);           setSampleMethod2.invoke(obj1, obj2);       } catch  (Exception e) {           e.printStackTrace();       }   } } 
2.3.2 网络加载: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package  sf.jvm.load.classloader;import  sf.jvm.load.api.ICalculator;import  java.io.ByteArrayOutputStream;import  java.io.InputStream;import  java.net.URL;public  class  NetworkClassLoader  extends  ClassLoader  {  private  String rootUrl;   public  NetworkClassLoader (String rootUrl)  {       this .rootUrl = rootUrl;   }   protected  Class<?> findClass(String name) throws  ClassNotFoundException {       byte [] classData = getClassData(name);       if  (classData == null ) {           throw  new  ClassNotFoundException ();       } else  {           return  defineClass(name, classData, 0 , classData.length);       }   }   private  byte [] getClassData(String className) {       String  path  =  classNameToPath(className);       try  {           URL  url  =  new  URL (path);           InputStream  ins  =  url.openStream();           ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ();           int  bufferSize  =  4096 ;           byte [] buffer = new  byte [bufferSize];           int  bytesNumRead  =  0 ;           while  ((bytesNumRead = ins.read(buffer)) != -1 ) {               baos.write(buffer, 0 , bytesNumRead);           }           return  baos.toByteArray();       } catch  (Exception e) {           e.printStackTrace();       }       return  null ;   }   private  String classNameToPath (String className)  {       return  rootUrl + "/"  + className.replace('.' , '/' ) + ".class" ;   }   public  static  void  main (String[] args)  {       String  url  =  "http://localhost:8080/ClassloaderTest/classes" ;       NetworkClassLoader  ncl  =  new  NetworkClassLoader (url);       String  basicClassName  =  "sf.jvm.load.simple.CalculatorBasic" ;       String  advancedClassName  =  "sf.jvm.load.simple.CalculatorAdvanced" ;       try  {           Class<?> clazz = ncl.loadClass(basicClassName);           ICalculator  calculator  =  (ICalculator) clazz.newInstance();           System.out.println(calculator.getVersion());           clazz = ncl.loadClass(advancedClassName);           calculator = (ICalculator) clazz.newInstance();           System.out.println(calculator.getVersion());       } catch  (Exception e) {           e.printStackTrace();       }   } }