美文网首页
带你手撸万元java进阶课程:jvm基础知识、字节码、类加载器

带你手撸万元java进阶课程:jvm基础知识、字节码、类加载器

作者: 你可以叫我老白 | 来源:发表于2022-09-09 16:55 被阅读0次

    编程语言

    演化:

    机器语言->编程语言->高级语言(java,c++,Go,Rust等)

    面向过程--面向对象-面向函数

    java是一种面向对象、静态类型、编译执行,有VM(虚拟机)/GC和运行时、跨平台的高级语言。重点:VM(虚拟机)/GC(Garbage Collector)和运行时、跨平台。

    跨平台步骤:字节码文件被虚拟机加载(类加载器)加载到内存中,转换成具体的对象

    字节码

    结构:

    Java byteCode由单字节(byte)指令构成,理论上最多支持256个操作码(opcode)。实际上java只使用了200左右的操作码,其他留给了调试操作。

    根据指令的性质大概分为四大类:

    1.栈操作指令,包括与局部变量交互的指令,

    2.程序流程指令,

    3.对象操作指令,比如方法调用的指令,

    4.算数运算以及类型转换的指令,

    运行步骤:

    JVM是一个基于栈的计算机,每个线程都有独属于自己的线程栈(JVM Stack),用语存储栈帧。每次调用方法就会自动创建一个线程栈。栈帧是由操作数栈、局部变量表以及一个class引用组成,class引用中又包含着我们使用的常量池

    操作demo:https://juejin.cn/post/7141206840456511496/

    类加载器

    类生命周期的七个步骤:

    1.加载:找到class文件;

    2.验证:验证字节码文件格式是否正确、依赖是否完备;

    3.准备:静态字段、方法表;

    4.解析:符合解析为引用;

    5.初始化:构造器,静态变量赋值,静态代码块;

    6.使用

    7.卸载

    前五步是我们通常所说的类加载过程,其中2、3、4可以合在一起称为-链接:

    1 找到class文件,读出来

    2 验证格式,解析字段方法,所有符号转化为实际引用

    3 类相关初始化

    类的加载时机:

    虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):

    1.3.1 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。

    1.3.2 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。

    1.3.3当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

    1.3.4 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;

    1.3.5 当使用 JDK.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;

    以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:

    通过子类引用父类的静态字段,不会导致子类初始化。

    通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。

    常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

    总结:显式,隐式

    隐式,子类父类,实现类和接口,反射,动态调用

    显式,main方法,new,静态字段和方法

    三类加载器和特点:

    1.启动类加载器(BootstrapClass Loader)

    这个类加载使用C/C++语言实现,嵌套在JVM内部

    它用来加载JAVA的核心库(JAVA_HOME/jre/lib/rt.jar,resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类

    并不继承自Java.lang.ClassLoader,没有父加载器

    加载扩展类和应用程序类加载器,并指定为它们的父类加载器

    出于安全考虑,Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类

    启动类加载器不像其他类加载器有实体,它是没有实体的,JVM将C++处理类加载的一套逻辑定义为启动类加载器。因此,启动类加载器是无法被Java程序调用的。

    2.扩展类加载器(Extension Class Loader)

    java语言编写,由sun.misc.Launcher$ExtClassLoader实现

    派生于ClassLoader类

    父类加载器为启动类加载器

    从Java.ext.dirs系统属性所指的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

    public static void main(String[] args) {

        ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();

        URLClassLoader urlClassLoader = (URLClassLoader) classLoader;

        URL[] urls = urlClassLoader.getURLs();

        for (URL url : urls) {

            System.out.println(url);

        }

    }

    3.应用程序加载器(系统类加载器,System Class Loader/App Class Loader)

    java语言编写,由sun.misc.Launcher&AppClassLoader实现

    派生于ClassLoader类

    父类加载器为扩展类加载器

    它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库

    该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载

    通过ClassLoader#getSystemClassLoader()方法可以获得到该类加载器

    public static void main(String[] args) {

        String[] urls = System.getProperty("java.class.path").split(":");

        for (String url : urls) {

            System.out.println(url);

        }

        System.out.println("================================");

        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

        URL[] urls1 = classLoader.getURLs();

        for (URL url : urls1) {

            System.out.println(url);

        }

    }

    4.用户自定义类加载器

    在Java的日常应用程序开发中,类加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。

    1、开发人员可以通过继承抽象类java.lang.classLoader类的方式,实现自己的类加载器,以满足一些特殊的需求

    2、在JDK2.0之前,在自定义类加载器时,总会去继承classLoader类并重写loadclass ()方法,从而实现自定义的类加载类,但是在JDK2.0之后已不再建议用户去覆盖loadclass ()方法,而是建议把自定义的类加载逻辑写在findclass ()方法中

    3、在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findclass ()方法及其获取字节码流的方式,这样会让自定义类加载器编写更为简单一些。

    双亲委派

    双亲委派机制的原理:

    如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。

    如果父类的加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终达到顶层的启动类加载器。

    如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制。

    优点:

    避免类的重复加载,确保一个类的全局唯一性

    保护程序安全,防止核心API被随意篡改

    缺点:

    无法做到不委派,无法做到向下委派

    在某些场景下双亲委派制过于局限,所以有时候必须打破双亲委派机制来达到目的。例如:SPI机制,这个SPI机制涉及到打破双亲委派机制,工作中没有涉及到就不细说了,感兴趣的同学可以自己研究下。

    双亲委派在JVM中的实现代码:

    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) {

                        // this 是AppClassLoader, this.parent是ExtClassLoader

                        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;

        }

    }

    这一期的课程大概就讲了这么多吧,说实话看完还是好多记不住和不理解,也是反复记忆并且查了好多资料才知道,所以不理解很正常,没有接触过就能一遍看懂的一般都是高级及以上了,慢慢看就可以了。看一点就是进步。

    下期这周末写,大概是内存模型和JMM的相关知识,小伙伴可以先复习下,然后查漏补缺。

    创作不易,如果这篇文章对你有用,请点个赞谢谢♪(・ω・)ノ!

    相关文章

      网友评论

          本文标题:带你手撸万元java进阶课程:jvm基础知识、字节码、类加载器

          本文链接:https://www.haomeiwen.com/subject/camtyrtx.html