JVM核心——JVM运行和类加载全过程

时间:2022-04-27 15:30:33

1.类加载全过程

(1)类加载机制

JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的Java类型的过程。

JVM核心——JVM运行和类加载全过程

  • 加载

  将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。 这个过程需要类加载器参与。

  •  链接 将Java类的二进制代码合并到JVM的运行状态之中的过程

  验证:
      确保加载的类信息符合JVM规范,没有安全方面的问题。
  准备:
    正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
  解析
    虚拟机常量池内的符号引用替换为直接引用的过程

  • 初始化

    初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
     当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化
    虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

(2)类的主动引用(一定会发生类的初始化)

  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类

(3) 类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化

    通过子类引用父类的静态变量,不会导致子类初始化

  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

JVM核心——JVM运行和类加载全过程

 2.类缓存

  标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class对象。

3.java.class.ClassLoader类

作用:
  java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java 类,即 java.lang.Class类的一个实例。
  除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
相关方法:
  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<?> c) 链接指定的 Java 类。
对于以上给出的方法,表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如com.example.Sample$1和com.example.Sample$Inner等表示方式。

4.类加载器的层次结构(树状结构)

(1)引导类加载器(bootstrap class loader)
  它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的内容),是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
  加载扩展类和应用程序类加载器。并指定他们的父类加载器。
(2) 扩展类加载器(extensions class loader)
  用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java类。
  由sun.misc.Launcher$ExtClassLoader实现
(3)应用程序类加载器(application class loader)
  它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。
  由sun.misc.Launcher$AppClassLoader实现
(4)自定义类加载器
  开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

JVM核心——JVM运行和类加载全过程

 

5.类加载器的代理模式

 (1)代理模式
交给其他加载器来加载指定的类
(2)双亲委托机制

  • 就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
  • 双亲委托机制是为了保证 Java 核心库的类型安全。

    这种机制就保证不会出现用户自己能定义java.lang.Object类的情况。

  • 类加载器除了用于加载类,也是安全的最基本的屏障。
  • 双亲委托机制是代理模式的一种

    并不是所有的类加载器都采用双亲委托机制。
    tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的

 6.自定义类加载器

  自定义类加载器的流程:

  (1)首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤2

  (2)委派类加载请求给父类加载器(更准确的说应该是双亲类加载器,真个虚拟机中各种类加载器最终会呈现树状结构),如果父类加载器能够完成,则返回父类加载器加载的Class实例否则转入步骤3

  (3)调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(…)导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(…), loadClass(…)转抛异常,终止加载过程(注意:这里的异常种类不止一种)。

   注意:被两个类加载器加载的同一个类, JVM不认为是相同的类。

  文件类加载器:

/**
 * 自定义文件系统类加载器
 * @author MA
 *
 */
public class FileSystemClassLoader extends ClassLoader{
    private String rootDir;
    
    public FileSystemClassLoader(String rootDir){
        this.rootDir = rootDir;
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        
        Class<?> c = findLoadedClass(name);
        
        //先查询是否加载过这个类
        if(c!=null){
            return c;
        }
        else{
            ClassLoader parent = this.getParent();
            try{
                c = parent.loadClass(name);//委派给父类加载
            }catch(Exception e){
                //e.printStackTrace();
            }
            
            if(c!=null){
                return c;
            }
            else{
                byte[] classDate = getClassDate(name);
                if(classDate==null){
                    throw new ClassNotFoundException();
                }
                else{
                    //把字节码转化为Class
                    c = defineClass(name, classDate, 0, classDate.length);
                }
            }
        }
        return c;
    }
    
    private byte[] getClassDate(String classname){
        String path = rootDir + "/" + classname.replace('.', '/') + ".class";
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try{
            is = new FileInputStream(path);
            
            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp=is.read(buffer))!=-1){
                baos.write(buffer,0,temp);
            }
            
            return baos.toByteArray();
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is!=null){
                    is.close();
                }
                if(baos!=null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }              
     }    
}

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException {
        FileSystemClassLoader loader = new FileSystemClassLoader("D:/develop/javatest1/TestClass");
        
        Class<?> c = loader.findClass("HelloWorld");
        
        System.out.println(c);
    }
}

  网络类加载器:

/**
 * 网络类加载器
 * @author MA
 *
 */
public class NetClassLoader extends ClassLoader{
    private String rootUrl;
    
    public NetClassLoader(String rootUrl){
        this.rootUrl = rootUrl;
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        
        Class<?> c = findLoadedClass(name);
        
        //先查询是否加载过这个类
        if(c!=null){
            return c;
        }
        else{
            ClassLoader parent = this.getParent();
            try{
                c = parent.loadClass(name);//委派给父类加载
            }catch(Exception e){
                //e.printStackTrace();
            }
            
            if(c!=null){
                return c;
            }
            else{
                byte[] classDate = getClassDate(name);
                if(classDate==null){
                    throw new ClassNotFoundException();
                }
                else{
                    //把字节码转化为Class
                    c = defineClass(name, classDate, 0, classDate.length);
                }
            }
        }
        return c;
    }
    
    private byte[] getClassDate(String classname){
        String path = rootUrl + "/" + classname.replace('.', '/') + ".class";
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try{
            URL url = new URL(path);
            is = url.openStream();
            
            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp=is.read(buffer))!=-1){
                baos.write(buffer,0,temp);
            }
            
            return baos.toByteArray();
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is!=null){
                    is.close();
                }
                if(baos!=null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }    
     }
}

  加密解密类加载器:

/**
 * 加密工具类
 * @author MA
 *
 */
public class EncrptUtil {
    
    public static void main(String[] args) {
        encrpt("D:/develop/javatest1/TestClass/HelloWorld.class", "D:/develop/javatest1/TestClass/temp/HelloWorld.class");
    }
    
    public static void encrpt(String src, String dest){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try{
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);
                        
            int temp = -1;
            while((temp=fis.read())!=-1){
                fos.write(temp^0xff); //取反操作
            }
            
            
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            try {
                if(fis!=null){
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            try {
                if(fos!=null){
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
    }
}
/**
 * 测试简单加密解密操作
 * @author MA
 *
 */
public class Demo02 {
    public static void main(String[] args) throws Exception {
        //加密后的class文件,正常的类加载器无法加载
        //FileSystemClassLoader loader = new FileSystemClassLoader("D:/develop/javatest1/TestClass");
        //Class<?> c = loader.findClass("HelloWorld");
    
        DecrptClassLoader loader = new DecrptClassLoader("D:/develop/javatest1/TestClass");
        Class<?> c = loader.loadClass("HelloWorld");
        System.out.println(c);
    }
}

/**
 * 加载文件系统中加密后的class字节码的类加载器
 * @author MA
 *
 */
public class DecrptClassLoader extends ClassLoader{
    private String rootDir;
    
    public DecrptClassLoader(String rootDir){
        this.rootDir = rootDir;
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        
        Class<?> c = findLoadedClass(name);
        
        //先查询是否加载过这个类
        if(c!=null){
            return c;
        }
        else{
            ClassLoader parent = this.getParent();
            try{
                c = parent.loadClass(name);//委派给父类加载
            }catch(Exception e){
                //e.printStackTrace();
            }
            
            if(c!=null){
                return c;
            }
            else{
                byte[] classDate = getClassDate(name);
                if(classDate==null){
                    throw new ClassNotFoundException();
                }
                else{
                    //把字节码转化为Class
                    c = defineClass(name, classDate, 0, classDate.length);
                }
            }
        }
        return c;
    }
    
    private byte[] getClassDate(String classname){
        String path = rootDir + "/" + classname.replace('.', '/') + ".class";
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try{
            is = new FileInputStream(path);
            
            int temp = -1;
            while((temp=is.read())!=-1){
                baos.write(temp^0xff); //取反操作
            }
            
            return baos.toByteArray();
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is!=null){
                    is.close();
                }
                if(baos!=null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
                
     }
}