从jdk源码解析jvm类加载机制

  1. 首先看一段源码
public class Math {

    private  static  final  int  initData = 666;
    private  static UserService user = new UserService();

    public int compute(){   //一个方法对应一块栈针内存区域

        int a =1;
        int b = 2;
        int c = a+ b;
        return  c;
    }

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

我们在程序中运行上面的一段代码,运行main方法,具体做了什么?

java命令执行代码的大体流程

Math.class --------->windows环境下,java.exe调用底层的jvm.dll文件创建java虚拟机(c++实现)------->创建一个启动类加载器实例(c++现)----------> c++调用java代码创建jvm启动器 实例sun.misc.Launer,该类由启动类加载器负责加载创建其他类加载器 -------.> sun.misc.Launer.getLauncher() ---------- 获取运行类自己的加载器ClassLoader,是AppClassLoader

的实例-------------》launcher.getClassLoader()---------调用loadClass加载要运行的类Math-----------------------------------------

– >ClassLoader.loadClass(“Math”)----------加载完成时Jvm会执行Math类的main方法--------》Math.main()-------程序运行结束-

-----------》jvm销毁

loadClass的类加载过程有下面几步

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

可以通过java -p -v Math.class 命令看到汇编语言编写的字节码 执行顺序 (对应 .clsss文件)

验证 检验字节码文件的正确性

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

解析 将符号引用转为直接引用(内存地址)

初始化 对类的静态变量初始化指定的值,并执行静态代码块

类加载到方法区中后主要包含 运行时常量池, 类型信息, 字段信息, 方法信息,类加载器的引用,对应Class实例的引用

使用

卸载

类的执行顺序:

父类静态代码块 —》子类的静态代码块----》父类非静态代码快 -------》父类 构造方法 -------》子类的非静态代码块-------》子类的构造方法

类加载器以及双亲委派模型

启动类加载器 负责加载jJVM运行的位于JRE目录的核心类库,比如rt.jar,charset.jar等

扩展程序类加载器 负责加载JVM位于JRE目录下的ext扩展目录的jar包

应用程序类加载器 负责加载ClassPath路径下的类包,主要加载自己写的哪些类。

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

双亲委派原理参考java面试高级篇所写

双亲委派模型优点:

沙箱保护机制以及避免类的重复加载

可以通过代码来打破双亲委派模型,只需要自己写的类 extends ClassLoader , 重写loadClass 方法

package com.tuling.jvm;

import lombok.SneakyThrows;

import java.io.FileInputStream;
import java.lang.reflect.Method;


public class MyClassLoaderTest {


    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;

        }

        @SneakyThrows
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] data = loadByte(name);
            return  defineClass(name,data,0,data.length);

        }


        /**
         * 重写父加载器,实现自己的加载逻辑,不委派双亲委派
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */
        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();
                    //如果类名不是以com.test.Math 开头,调用AppconfigClassLoader进行加载
                    if(! name.startWith("com.test.Math")){
                      c = this.getParent().loadClass(name);
                  }else{
                      //调用自己的加载器去加载
                      c = findClass(name);
                    }
          
                    // this is the defining class loader; record the stats
                    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 myClassLoader = new MyClassLoader("C:\\test");
            //依次在  C盘 test目录下分层建立目录com/test/Math.class
            Class clazz = myClassLoader.loadClass("com.test.Math");
            Object obj = clazz.newInstance();
            //调用类中方法compute
            Method method = clazz.getDeclaredMethod("compute", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass());
        }
    }
}

当没有loadClass 方法时,自己项目路径包com.test有个Math.java类,可以看出类加载器为Application ClassLoader, 当删除掉之后,加载我们自己的类加载器。可以加入loadClass 方法,来打破双亲委派模型。这种测试很好的验证双亲委派模型的执行流程。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值