zoukankan      html  css  js  c++  java
  • JVM性能专题一:JVM类加载机制

    一、Java类加载运行全过程

      P1:通过Java命令执行代码的大致过程:

        1)window系统下java.exe调用底层的jvm.dll文件创建Java虚拟机(c++实现)

        2)创建一个引导类加载器实例(c++实现)

        3)c++调用Java代码创建JVM启动器,实例sun.misc.Launcher,该类由引导类加载器负责加载,创建其他类加载器

        4)sun.misc.Launcher.getLauncher()获取运行类自己的类加载器ClassLoader,是AppClassLoader的实例

        5)launcher.getClassLoader()调用loadClass加载要运行的类

        6)加载完成的时候JVM会执行该类的main方法入口

        7)程序运行结束,JVM销毁

      P2:其中loadClass的类加载过程有如下几步:

        1)加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class(InstanceMirrorKlass)对象,作为方法区这个类的各种数据的访问入口

        2)验证:校验字节码文件的正确性

        3)准备:给类的静态变量分配内存,并赋予默认值

        4)解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用

        5)初始化:对类的静态变量初始化为指定的值,执行静态代码块

      P3:类被加载到方法区中:

        主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息

        1)类加载器的引用:这个类到类加载器实例的引用

        2)对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的class类型的对象实例放到堆(Heap)中,作为开发人员访问方法区中类定义的入口和切入点

        3)主类在运行过程中如果使用到其他类,会逐步加载这些类。jar包或war包里的类不是一次性全部加载的,是使用到时才会加载

      P4:什么时候会触发类加载:

        1)使用new关键字实例化对象

        2)读取或者设置一个类的静态变量的时候

        3)调用类的静态方法的时候

        4)对类进行反射调用的时候

        5)初始化子类,父类会先被初始化

        6)对类使用动态代理的时候需要被先初始化

    二、JVM核心类加载器

      P1:类加载过程主要通过类加载器来实现的:

        1)引导类加载器:负责加载支撑JVM运行的位于 JRE的lib目录下的核心类库,比如rt.jar、charset.jar等

        2)扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包

        3)应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类

        4)自定义类加载器:负责加载用户自定义路径下的类包

      P2:打印类加载器代码清单:

    1 public class TestClassLoader {
    2 
    3   public static void main(String[] args) {
    4      System.out.println(String.class.getClassLoader());
    5      System.out.println(DESKeyFactory.class.getClassLoader());
    6      System.out.println(TestClassLoader.class.getClassLoader());
    7   }
    8 }

      运行结果:

    1 null
    2 sun.misc.Launcher$ExtClassLoader@4b67cf4d
    3 sun.misc.Launcher$AppClassLoader@18b4aac2

    注意:引导类加载器打印结果是null,该加载器对象不是Java对象是c++对象,不做显示

      P3:类加载器初始化过程:

        1)创建JVM启动器实例sun.misc.Launcher(该类初始化使用了单例模式,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例)

        2)在Launcher构造器方法内部,创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)

        3)JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序

      P4:类加载器初始化过程代码清单:

     1 public Launcher() { //Launcher的构造方法
     2         Launcher.ExtClassLoader var1;
     3         try {
     4             var1 = Launcher.ExtClassLoader.getExtClassLoader(); //构造扩展类加载器,在构造的过程中将其父加载器设置为null
     5         } catch (IOException var10) {
     6             throw new InternalError("Could not create extension class loader", var10);
     7         }
     8 
     9         try { //构造应用类加载器,在构造的过程中将其父类加载器设置为ExtClassLoader
    10             this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
    11         } catch (IOException var9) {
    12             throw new InternalError("Could not create application class loader", var9);
    13         }
    14 
    15         Thread.currentThread().setContextClassLoader(this.loader);
    16         String var2 = System.getProperty("java.security.manager");
    17         if (var2 != null) {
    18             SecurityManager var3 = null;
    19             if (!"".equals(var2) && !"default".equals(var2)) {
    20                 try {
    21                     var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
    22                 } catch (IllegalAccessException var5) {
    23                 } catch (InstantiationException var6) {
    24                 } catch (ClassNotFoundException var7) {
    25                 } catch (ClassCastException var8) {
    26                 }
    27             } else {
    28                 var3 = new SecurityManager();
    29             }
    30 
    31             if (var3 == null) {
    32                 throw new InternalError("Could not create SecurityManager: " + var2);
    33             }
    34 
    35             System.setSecurityManager(var3);
    36         }
    37 
    38     }

    三、类加载双亲委派机制

      P1:双亲委派机制:

        1)这里类加载就是一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,父类加载器一直向上委托,直到委托到引导类加载器

        2)每个加载器会加载自己指定的类路径,如果被加载的目标类在该区域,则自己加载,如果目标类不在该区域,则向下委托

        总结:双亲委派机制简单来说,先让父亲加载,不行再由儿子自己加载

      P2:双亲委派机制代码清单:

     1 protected Class<?> loadClass(String name, boolean resolve)
     2         throws ClassNotFoundException
     3     { //ClassLoader的loadClass方法,里面实现了双亲委派机制
     4         synchronized (getClassLoadingLock(name)) {
     5             // First, check if the class has already been loaded
     6             Class<?> c = findLoadedClass(name); //检查当前类加载器是否已经加载了该类
     7             if (c == null) {
     8                 long t0 = System.nanoTime();
     9                 try {
    10                     if (parent != null) { //如果当前加载器父加载器不为空则委托父加载器加载该类
    11                         c = parent.loadClass(name, false);
    12                     } else { //如果当前加载器为空则委托引导类加载器加载该类
    13                         c = findBootstrapClassOrNull(name);
    14                     }
    15                 } catch (ClassNotFoundException e) {
    16                     // ClassNotFoundException thrown if class not found
    17                     // from the non-null parent class loader
    18                 }
    19 
    20                 if (c == null) {
    21                     // If still not found, then invoke findClass in order
    22                     // to find the class.
    23                     long t1 = System.nanoTime();
    24                     c = findClass(name); //都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
    25 
    26                     // this is the defining class loader; record the stats
    27                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    28                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    29                     sun.misc.PerfCounter.getFindClasses().increment();
    30                 }
    31             }
    32             if (resolve) { //不会执行
    33                 resolveClass(c);
    34             }
    35             return c;
    36         }
    37     }

        1)首先,检查一下指定名称的类是否已经加载过了,如果加载过了,就不需要再加载,直接返回

        2)如果此类没有加载过,那么,再判断一下是否有父类加载器,如果有父类加载器,则由父类加载器加载(即调用parent.loadClass(name,false);)或者是调用bootstrap类加载器来加载

        3)如果父类加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载

      P3:为什么要设计双亲委派机制?

        1)沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便防止核心API类库被随意篡改

        2)避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

      P4:类加载示例:

    1 package java.lang;
    2 
    3 public class String {
    4     public static void main(String[] args) {
    5         System.out.println("********My String Class*********");
    6     }
    7 }

      运行结果:

    错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
       public static void main(String[] args)
    否则 JavaFX 应用程序类必须扩展javafx.application.Application

    四、手写自定义类加载器打破双亲委派机制

      P1:自定义类加载器代码清单:

     1 public class MyClassLoaderTest {
     2 
     3     static class MyClassLoader extends ClassLoader{
     4         private String classPath;
     5 
     6         public MyClassLoader(String classPath){
     7             this.classPath = classPath;
     8         }
     9 
    10         private byte[] loadByte(String name) throws Exception {
    11             name = name.replaceAll("\.", "/");
    12             FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
    13             int len = fis.available();
    14             byte[] data = new byte[len];
    15             fis.read(data);
    16             fis.close();
    17             return data;
    18         }
    19 
    20         protected Class<?> findClass(String name) throws ClassNotFoundException {
    21             try {
    22                 byte[] data = loadByte(name); //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取最终的字节数组
    23                 return defineClass(name,data,0,data.length);
    24             }catch (Exception e){
    25                 e.printStackTrace();
    26                 throw new ClassNotFoundException();
    27             }
    28         }
    29     }
    30 
    31     public static void main(String[] args) throws Exception {
    32         //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
    33         MyClassLoader classLoader = new MyClassLoader("D:/test");
    34         //D盘创建 testcomspring几级目录,将Person类的复制类Person.class丢入该目录
    35         Class<?> clazz = classLoader.loadClass("com.spring.Person");
    36         Object obj = clazz.newInstance();
    37         Method method = clazz.getDeclaredMethod("sout", null);
    38         method.invoke(obj,null);
    39         System.out.println(clazz.getClassLoader().getClass().getName());
    40     }
    41 }

      运行结果:

    ****自己的类加载器加载类调用方法****
    sun.misc.Launcher$AppClassLoader

    如上所示:如果控制台输出的类加载器为AppClassLoader,那么请删掉项目中的src/com/spring/Person.java以及target/classes/com/spring/Person.class文件。
    造成该现象的原因:向上委派了,APPClassLoader可以在自己的类路径下找到该文件

    ****自己的类加载器加载类调用方法****
    com.map.MyClassLoaderTest$MyClassLoader

        1)自定义类加载器只需要继承java.lang.ClassLoader类

        2)该类两个核心方法。一个是loadClass(String,boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要重写findClass方法

       P2:打破双亲委派代码清单:

     1 public class MyClassLoaderTest {
     2 
     3     static class MyClassLoader extends ClassLoader{
     4         private String classPath;
     5 
     6         public MyClassLoader(String classPath){
     7             this.classPath = classPath;
     8         }
     9 
    10         private byte[] loadByte(String name) throws Exception {
    11             name = name.replaceAll("\.", "/");
    12             FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
    13             int len = fis.available();
    14             byte[] data = new byte[len];
    15             fis.read(data);
    16             fis.close();
    17             return data;
    18         }
    19 
    20         protected Class<?> findClass(String name) throws ClassNotFoundException {
    21             try {
    22                 byte[] data = loadByte(name); //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取最终的字节数组
    23                 return defineClass(name,data,0,data.length);
    24             }catch (Exception e){
    25                 e.printStackTrace();
    26                 throw new ClassNotFoundException();
    27             }
    28         }
    29 
    30         /**
    31          * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
    32          * @param name
    33          * @param resolve
    34          * @return
    35          * @throws ClassNotFoundException
    36          */
    37         protected Class<?> loadClass(String name, boolean resolve)
    38                 throws ClassNotFoundException
    39         {
    40             synchronized (getClassLoadingLock(name)) {
    41                 // First, check if the class has already been loaded
    42                 Class<?> c = findLoadedClass(name);
    43                 if (c == null) {
    44                         long t1 = System.nanoTime();
    45                         if (!name.startsWith("com.spring")){ //如果不是com.spring包下的类,向上委派
    46                             c = this.getParent().loadClass(name);
    47                         }else {  //如果是,则自己加载
    48                             c = findClass(name);
    49                         }
    50 
    51                         // this is the defining class loader; record the stats
    52                         sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    53                         sun.misc.PerfCounter.getFindClasses().increment();
    54                 }
    55                 if (resolve) {
    56                     resolveClass(c);
    57                 }
    58                 return c;
    59             }
    60         }
    61     }
    62 
    63     public static void main(String[] args) throws Exception {
    64         //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
    65         MyClassLoader classLoader = new MyClassLoader("D:/test");
    66         //D盘创建 testcomspring几级目录,将Person类的复制类Person.class丢入该目录
    67         Class<?> clazz = classLoader.loadClass("com.spring.Person");
    68         Object obj = clazz.newInstance();
    69         Method method = clazz.getDeclaredMethod("sout", null);
    70         method.invoke(obj,null);
    71         System.out.println(clazz.getClassLoader().getClass().getName());
    72     }
    73 }

       运行结果:

    ****自己的类加载器加载类调用方法****
    com.map.MyClassLoaderTest$MyClassLoader

    如果出现问题,请仔细想想类加载机制以及双亲委派机制,每一个类加载之前都会加载Object类,这个只能引导类加载器去加载

       注意:同一个JVM内,两个相同包和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为是同一个类

  • 相关阅读:
    Android 在一个程序中启动另一个程序
    Android SDK Manager国内无法更新的解决方案
    Android studio 安装中遇到一些问题的解决办法,分享一下
    apache服务器伪静态配置说明
    POJ3253 Fence Repair【贪心】
    洛谷P1090 合并果子【贪心】
    POJ3069 Saruman's Army【贪心】
    洛谷P1012 拼数【字符串+排序】
    POJ3617 Best Cow Line【贪心】
    洛谷P1583 魔法照片【模拟+排序】
  • 原文地址:https://www.cnblogs.com/Mapi/p/14320624.html
Copyright © 2011-2022 走看看