【JVM】类加载机制

背景

和朋友交流总是觉得自己基础薄弱,以及去年出去面试被问到偏底层点的问题就很无奈。一直想沉下心来学习一下Java底层以及面试八股文,从今天开始,希望自己能够长期坚持下去,坚持卷。本文建议有一定基础的人看。码农都是很直接的,直接上干货

类加载过程

示例类:

package com.ruoyuan.test;

public class Math {

    public int calc(){
        int data1 = 1;
        int data2 = 2;
        return data1*data2;
    }

    public static void main(String[] args) {
        Math math = new Math();
        math.calc();
    }
}

Java底层执行类过程

【JVM】类加载机制_第1张图片

从广义的角度上还可以这样说:
【JVM】类加载机制_第2张图片

类加载流程

加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

加载

在磁盘上找到并通过IO读字节码文件,使用到类时才会加载。加载阶段在内存中生成一个代表该类的java.lang.Class的对象。用于方法区这个类各种数据的访问入口

验证

根据Java虚拟机规范,来校验加载进来的“.class”文件中的内容,是否符合指定的规范

准备

给类的静态变量分配内存,并且赋予初始值

解析

虚拟机将常量池内的符号引用替换为直接引用的过程

初始化

赋值(对类的静态变量初始化为指定的值,执行静态代码块)类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的 引用、对应class实例的引用等信息

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

类加载器

引导类加载器

负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等

扩展类加载器

加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包

扩展程序类加载器

负责加载ClassPath路径下的类包,主要就是加载自己写的那 些类

自定义类加载器

负责加载用户自定义路径下的类包

双亲委派机制

【JVM】类加载机制_第3张图片

双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载。如果有感兴趣的可以去研究一下:AppClassLoader 的loadClass方法最终会调用其父类ClassLoader的loadClass方法

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

思考

为什么设计双亲委派机制

  • 沙箱隔离安全(防止核心API被随意更改)
  • 避免类重复加载(唯一性)

如何自定义类加载器

自定义类加载器只需要继承java.lang.ClassLoader,该类的核心方法有一个loadClass(String, boolean)实现双亲委派机制,还有一个是findClass()默认实现是:空方法-如下:

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

所以自定义类加载器主要是手动实现:findClass方法

public class MyClassLoaderTest {
    public static class MyClassLoader extends ClassLoader {

        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节 数组。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }


    }


    public static void main(String args[]) throws Exception {
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载 器设置为应用程序类加载器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/practice_work");
        //D盘创建 practice_work/com/ruoyuan/demo/main 几级目录,将MainTest1类的复制类MainTest1.class丢入该目录
        Class clazz = classLoader.loadClass("com.ruoyuan.demo.main.MainTest1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("testFun", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());

    }
}

如何打破双亲委派机制

上文有说到loadClass(String, boolean)实现双亲委派机制,那么要打破该机制就得修改loadClass方法。
附上该方法的jdk源码:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

观察代码发现,其实要打破双亲委派机制,其实就是去掉加载父类代码。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
    }

	public static void main(String args[]) throws Exception {
		MyClassLoader classLoader = new MyClassLoader("D:/practice_work");
	    Class clazz = classLoader.loadClass("lang.String");
	    Object obj = clazz.newInstance();
	    Method method = clazz.getDeclaredMethod("testFun", null);
	    method.invoke(obj, null);
	    System.out.println(clazz.getClassLoader().getClass().getName());
	}

以上就是关于JVM类加载的部分知识。JVM包含知识点很多,请关注后续更新
如有问题,希望大家评论区留言多多指教!如觉得写得还可以,就给一个赞吧!

你可能感兴趣的:(【Java】-,JVM,JVM,类加载机制)