天天看点

JVM类加载详解

类的生命周期

JVM类加载详解

类加载的时机

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,
    • 如果类没有初始化,则需要先触发其初始化;
    • 这4条指令对应的的常见场景分别是:使用new关键字实例化对象、读取或设置一个类的静态字段 (被final修饰、已在编译期把结果放入常量池的静态字段除外) 的时候,以及调用一个类的静态方法的时候。
    • 注:静态内容是跟类关联的而不是类的对象。
  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    • 注:反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
    • 这种动态获取的信息以及动态调用对象的方法的功能称为java 语言的反射机制
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
    • 注:子类执行构造函数前需先执行父类构造函数。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
    • main 方法是程序的执行入口
  • 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化。则需要先触发其初始化。
    • 注:JDK1.7 的一种新增的反射机制,都是对类的一种动态操作。

被动引用不会触发类加载

  • 演示一:通过子类引用父类的静态字段,不会导致子类初始化
JVM类加载详解
  • 演示二:通过数组定义来引用类,不会触发此类的初始化
JVM类加载详解
  • 演示三:常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
JVM类加载详解

类加载过程

  • 加载
    • 通过一个类的全限定名来获取此类的二进制字节流;
      • 注:虚拟机规范并没有明确说明类的二进制字节流从何而来,所以这里可以有非常灵活的实现空间,例如可以用过ZIP 包(如JAR 、EAR 、WAR 格式)读取,从网络中获取,运行时计算生成(如ASM 框架),从数据库中读取等等。
      • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
      • 方法区域Java 堆一样,是各线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据”。而方法区中的数据存储结构格式虚拟机自行定义。
    • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
      • 注:加载阶段完成后,虚拟机在内存中实例化一个java.lang.Class 类的对象(Class是一个实实在在的对象,是记录着类成员、接口等信息的对象)。还有一点是,我们都知道对象肯定是存放在堆中的,但Class 对象比较特殊,对于HotSpot 虚拟机而言,Class 对象是存放在方法区中的。
  • 验证
    • 验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
      • 是否以魔数0xCAFEBABE开头;
      • 主、次版本号是否在当前虚拟机处理范围内;
      • 常量池的常量中是否有不被支持的常量类型(检查常量tag标志);
      • 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
      • CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据
      • 。。。。
    • 元数据验证,保证不存在不符合Java 语言规范的元数据信息
      • 这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类);
      • 这个类的父类是否继承了不允许被继承的类(被final修饰的类);
      • 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法;
      • …..
    • 字节码验证,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件
      • 保证任意时刻操作栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作栈放置了一个int类型的数据,使用时却按long类型来加载如本地变量中;
      • 保证跳转指令不会跳转到方法体以外的字节码指令上;
      • 保证方法体中的类型转换是有效的;
      • ……
    • 符号引用验证,确保在后续的“解析”阶段能正常执行
      • 符号引用中通过字符串描述的全限定名是否能找到对应的类;
      • 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段;
      • …..
  • 准备
    • 准备阶段是正式为类变量分配内存设置类变量初始化值的阶段,这些变量所使用的内存都将在方法区中进行分配。
      • 这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量
      • 这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:public static int value =123那变量value在准备阶段过后的初始化值为0而不是123,
      • 因为这是尚未开始执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后存放在类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。
  • 类或接口的解析

类加载器(双亲委派机制)

JVM类加载详解
  • 启动类加载器(BootstrapClassLoader):这个类加载器负责将\lib目录中的,或被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(如rt.jar)类库加载到虚拟机内存中。
    • 启动类加载器(BootstrapClassLoader):这个类加载器负责将\lib目录中的,或被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(如rt.jar)类库加载到虚拟机内存中。
    • JVM类加载详解
  • 扩展类加载器(ExtensionClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现的,它负责加载\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器,如下示例:
    • 是我的ClassLoaderTest类在classpath下打印的结果:
    • JVM类加载详解
    • 是我的ClassLoaderTest类打包放在/lib/ext目录下的打印结果

    -
    JVM类加载详解

  • 应用程序类加载器(ApplicationClassLoader):从上面的测试可以看到,这个类加载器由sun.misc.Launcher$AppClassLoader实现,由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器。
  • 自定义类加载器(UserClassLoader):所有自定义的类加载器必须继承ClassLoader抽象类(严格说所有类加载器都继承于它,除了Bootstrap ClassLoader,因它是由C/C++实现的)
    • getParent() 返回该类加载器的父类加载器。
    • loadClass(String name)加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
    • findClass(String name)查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
    • findLoadedClass(String name)查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
    • defineClass(String name, byte[] b, int off, int len)把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
    • resolveClass(Class
public class MyClassLoader extends ClassLoader{      
      public MyClassLoader(){}  

      public MyClassLoader(ClassLoader parent){  
         super(parent);  
      }     
      @Override10    
      protected Class<?> findClass(String name) throws ClassNotFoundException{ 
               byte[] bytes = null;        
               /*获取类字节,自定义类我默认在G盘*/        
               FileInputStream fis = null;       
               try {    
                    fis = new FileInputStream("G:\\"+name+".class");           ByteArrayOutputStream baos = new ByteArrayOutputStream();        byte[] buff = new byte[];            
                    int rc = ; 
             while ((rc = fis.read(buff, , )) > ) { 
                 baos.write(buff, , rc); 
                 } 
                bytes = baos.toByteArray();                  

               } catch (Exception e) {             

                  e.printStackTrace();        

               }finally{             
                    if(fis != null){                
                    try {                   
                        fis.close();              

                    } catch (IOException e) {                     
                        e.printStackTrace();               
                    }       
                } 
            }
            /*生产Class对象*/     
            try{
                Class<?> c = this.defineClass(name, bytes, , bytes.length);  
             return c;  
         }catch (Exception e){  
             e.printStackTrace();  
        }  
            return super.findClass(name);
        }   
        public static void main(String[] args) throws Exception{       
            MyClassLoader mlc = new MyClassLoader(MyClassLoader.getSystemClassLoader());        
            Class c = mlc.loadClass("MyClassLoaderTest");         System.out.println(c.newInstance().toString());   
        }  
   }