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内,两个相同包和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为是同一个类

  • 相关阅读:
    [译文] 实体与值对象到底是不是一回事?
    实现 WebApi 自托管服务宿主于 WinForms 及其交互
    [译文] C# 8 已成旧闻, 向前, 抵达 C# 9!
    [译文] 为什么你在 C# 里总是应该使用 "var" 关键字
    通过设置iis在局域网中访问网页
    windows 10 安装使用kafka
    ASP.NET Core 2.1 中的 HttpClientFactory (Part 4) 整合Polly实现瞬时故障处理
    ASP.NET Core 2.1 中的 HttpClientFactory (Part 3) 使用Handler实现传出请求中间件
    ASP.NET Core 2.1 中的 HttpClientFactory (Part 2) 定义命名化和类型化的客户端
    Asp.net Core 2.0 OpenId Connect Handler缺失Claims?
  • 原文地址:https://www.cnblogs.com/Mapi/p/14320624.html
Copyright © 2011-2022 走看看