JVM基础入门

JVM 基础入门

JVM 基础

聊一聊 Java 从编码到执行到底是一个怎么样的过程?

在这里插入图片描述

假设我们有一个文件 x.Java,你执行 javac,它就会变成 x.class

这个 class 怎么执行的?

当我们调用 Java 命令的时候,class 会被 load 到内存,这块叫【Classloader】,会被 Classloader 装载到内存里。

一般的情况下,我们写自己的类文件的时候也会用到 【Java 的类库】,所以他会把 Java 类库相关的这些个类也要装载到内存里,装载完成之后会调用【字节码解释器】或者是【JIT 即时编译器】来进行解释或编译,编译完之后由【执行引擎】开始执行,这以及下面面对的,那就是操作系统和硬件了。

Java 编译好了之后变成 class, class 会被 load 到内存,与此同时像什么 string, object 这些个 class 也都会被 load 到内存。

Java 是这个解释执行的还是编译执行的?

其实解释和编译是可以混合的,特别常用的一些代码,代码用到的次数特别多,这个时候他会把代码做成一个及时的编译,做成一个本地的编译。

你可以理解为就像 c 语言在 Windows 上执行的时候,把它编译成 exe 一样,那么下次再执行这段代码的时候,就不需要通过解释器来一句一句解释来执行了,执行引擎可以直接交给操作系统去让它调用,这个效率要高很多,不是所有的代码都要都会被 GIT 进行及时编译的,如果是这样的话,那整个 Java 就完全变成了不能跨平台了。

所以有一些特定的,执行起来执行次数好多好多,用的特别多的时候,这个时候会进行一个即时编译器的编译。

什么是 JVM?

所谓的 JVM 虚拟机,其实它本身是一个规范,是虚构出来的一台计算机。拥有自己的操作系统,是一个跨语言平台。

为什么 JVM 虚拟机能够支持多种语言运行在上面呢?

最关键的原因是就是因为 class 这个东西,我们可以说任何的语言,只要你能编译成 class,符合 class 文件的规范,你就可以扔在 Java 虚拟机上去执行。

注意:JVM 只和 class 文件有关,与 java 无关。

JDK 官网:Java SE Specifications (oracle.com)

维基百科:Java虚拟机 - 维基百科,自由的百科全书 (wikipedia.org)

甲骨文中国:Java 软件 | Oracle 中国

常见的 JVM 实现

  1. Hotspot(最常用)

    • oracle 官方,我们做实验用的 JVM,Java 虚拟机
    • java -version 命令可查看使用的是什么 JVM
    • Hotspot 8 之后要收费,但 Open JDK 是开源的版本,免费
  2. Jrockit

    • BEA 曾经号称世界上最快的 JVM
    • 被 Oracle 收购,合并于 hotspot
  3. TaobaoVM(免费)

    • hotspot 深度定制版
  4. LiquidVM

    • 直接针对硬件
  5. azul zing(特别贵)

    • 最新垃圾回收的业界标杆(号称 1ms 以内)
    • www.azul.com
  6. J9-IBM

    • Microsoft VM

JDK JRE JVM 的关系

包含关系。

JVM – 运行 java 字节码的虚拟机

JRE – java 运行环境 == jvm + core(核心类库)

JDK – java 开发工具包 == jre + development kit(开发工具)

Class 文件格式

Class 文件格式(File Format

  • 二进制字节流(由 java 虚拟机解释)
  • 数据类型: u1 u2 u4 u8 和 _info (表类型)
    • info 的来源是 hotspot 源码中的写法
  • 查看 16 进制格式的 ClassFile
    • 软件 - sublime / notepad /
    • IDEA 插件 - BinEd
  • 有很多可以更好观察 ByteCode 的方法
    • 终端命令:javap <class文件路径>-v 参数详细查看
    • JBE 可以直接修改
    • JClassLib - IDEA 插件之一
  • 文件结构
    • General Information(通用信息): 这个部分包含了一些通用的信息,比如文件的魔数(magic number),文件版本号等。
    • Constant Pool(常量池): 常量池是一个重要的数据结构,它存储了一系列常量,包括字面值、符号引用、类和接口的名称等。常量池在字节码文件中起到了存储和管理常量数据的作用。
    • Interfaces(接口): 这部分定义了该类实现的接口。
    • Fields(字段): 这部分描述了类的字段(成员变量),包括字段的名称、类型以及修饰符等信息。
    • Methods(方法): 这部分描述了类的方法,包括方法的名称、参数列表、返回类型、字节码指令等。
    • Attributes(属性): 属性提供了关于类、字段或方法的其他信息,例如源代码行号、注解等。这个部分可以包含多个不同类型的属性,每个属性都有一个名称和相应的数据。

如下图示:

在这里插入图片描述

  • magic:魔数,是一个固定的字节序列,用于标识 Java 字节码文件。它的值为 0xCAFEBABE,用于确定文件是否是有效的 Java 字节码文件。
  • minor version:次版本号,指示 Java 编译器版本的次要更新。用于描述字节码文件与Java虚拟机版本的兼容性。
  • major version:主版本号,指示 Java 编译器版本的主要更新。同样用于描述字节码文件与 Java 虚拟机版本的兼容性。
  • constant_pool_count:常量池计数,表示常量池中常量的数量。十六进制 0010 转化为十进制为 16。常量池真正存储内容的时候存了 16 -1 项。后面的内容都是引用它。
  • #1::常量池项,通常以 # 开头,表示一个常量池中的单个项。
  • access flags:访问标志,描述类或接口的访问级别和特性。
  • this_class:当前类的索引,指示当前类在常量池中的位置。
  • super class:父类的索引,指示父类在常量池中的位置。
  • interface_count:接口数量,表示该类实现的接口数量。
  • interfaces:接口列表,描述该类实现的接口在常量池中的位置。
  • fields_count:字段数量,表示该类中声明的字段数量。
  • methods_count:方法数量,表示该类中声明的方法数量。
  • method_info:方法信息,描述方法的访问标志、方法名、参数列表等。
  • attribute_count:属性数量,表示该类的属性数量。

每个部分描述了 Java 字节码文件的不同方面,从类的声明到方法的定义,以及与常量池等相关的信息。这些信息在 Java 虚拟机中被解析和使用,以正确地加载和执行 Java 类。

Java 的汇编指令有 200 多条。

好文分享:class类文件结构

类加载 - 初始化

加载过程

加载、链接和初始化是 Java 程序运行时的三个主要阶段。

  1. Loading – 加载

  2. Linking – 链接

    • Verification – 验证
    • Preparation – 准备
    • Resolution – 解析
  3. Initializing – 初始化

具体来说:

  1. Loading(加载): 这是类加载过程的第一个阶段。在这个阶段,Java 虚拟机(JVM)会从类的外部源加载类的二进制数据,通常是从磁盘文件中加载,但也可以是网络、内存等。

    加载器将类的二进制数据(class文件)从外部源加载到内存中,并将其放置在运行时数据区的方法区内。

  2. Linking(链接): 这是加载过程的第二个阶段,它将类的二进制数据链接到 JVM 的运行时状态。

    链接过程分为以下三个步骤:

    • Verification(验证):确保类的二进制数据符合 JVM 规范,不违反类加载的安全性要求。这个步骤确保类的结构正确,不会引发运行时错误。
    • Preparation(准备):为类的静态变量分配内存空间,并设置默认的初始值(通常为零值)。这个步骤为类变量分配内存并初始化,但不会为实例变量分配内存。(把 class 文件的静态变量赋默认值,比如 int 类型的默认值为 0)
    • Resolution(解析):将(class文件里面的常量池里面用到的)符号引用转换为直接引用,解析动作可以在运行时再完成,也可以在编译时静态完成。这个步骤处理类、接口、方法和字段的符号引用,将其解析为实际的引用。
  3. Initializing(初始化): 这是链接过程的最后一个阶段,也是类加载的最终阶段。在这个阶段,类的静态初始化器会被执行,初始化静态字段和执行静态块。这个阶段是在类被首次使用时触发的,例如创建类的实例、访问类的静态字段等。(调用类初始化代码 <clinit>,给静态成员变量赋初始值)

总结来说,类加载过程涉及到从外部源加载类的二进制数据,然后将其链接到 JVM 的运行时状态,最终进行初始化。链接阶段包括验证、准备和解析,而初始化阶段则会执行类的静态初始化器。这个过程确保类在 Java 虚拟机中正确加载和使用。

加载之后会发生什么?

任何一个 class 被加载到内存之后,会生成了两块内容。

  • 第一块内容是这个二进制的,这块东西确实被落到内存,放到了内存的一块区域,可以原封不动的扔进去。
  • 第二个,它生成了一个 class 类的对象,通过以后其他的那些我们自己写的对象去访问这个对象,通过这个对象去访问 class 类的文件,所以生成一个 class 的对象,这个 class 对象是指向了这块内容。

在 Java 虚拟机中,类的元数据,包括类的结构信息、字段、方法、父类、接口等,都会被加载到方法区(元空间Metaspace,替代了永久代PermGen)中。Class 对象本身也是类的元数据之一,因此 Class 对象也存放在方法区中。 Class 对象在内存中的位置可以看作是类的描述符,用来操作该类的字节码以及其他相关的元数据。

可以看看下面这两篇文章:

  • 方法区与Metaspace
  • Java8取消permgen,使用mataspace有什么好处,内存结构有什么本质的变化?

类加载器

JVM 它本身有一个类加载器的层次,这个类加载器就是一个普通的 class,JVM 有一个类加载器的层次,分别来加载不同的 class。或者说,JVM 里面所有的 class 都是被类加载器给加载到内存的,那么这个类加载器简单说我们可以把它叫做 ClassLoader。

image

注意:从下往上,是委托给父加载器,不是继承关系,是语法上的一种关系。

在 Java 虚拟机中,类加载器按照层次结构进行组织,分为三个主要层次:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader,也称为系统类加载器)。这些类加载器形成了类加载器的双亲委派模型。

  1. 启动类加载器(Bootstrap ClassLoader):这是 Java 虚拟机的一部分,用于加载 Java 核心库(例如 java.lang 包中的类)。它是所有其他类加载器的父加载器,但它本身不是一个普通的 Java 类,因此在源码中并没有对应的类。它位于虚拟机内部,通常用本地代码实现。它是最顶层的类加载器,负责加载 Java 核心类库。
  2. 扩展类加载器(Extension ClassLoader): 扩展类加载器用于加载 Java 虚拟机的扩展类库,位于 jre/lib/ext 目录中的类。它的父加载器是启动类加载器。
  3. 应用程序类加载器(Application ClassLoader):这个加载器也称为系统类加载器,它负责加载应用程序的类,包括用户自定义的类和第三方库中的类。它的父加载器是扩展类加载器。

启动类加载器、扩展类加载器和应用程序类加载器构成了类加载器的层次结构,通过双亲委派模型来保证类的加载的一致性和安全性。在加载一个类时,首先会尝试由父加载器加载,只有在父加载器无法加载时,子加载器才会尝试加载。这个模型可以防止类的重复加载,同时保证了类的隔离性。

在启动类加载器加载的类中,有一部分是虚拟机内部的类,比如 java.lang.Objectjava.lang.String 等。这些类并不是普通的 Java 类,因此没有对应的源码。而在 Java 虚拟机的源码中,通常会对这些类的加载过程进行描述,但实际上它们是由虚拟机的实现提供的。

最顶层加载器 Bootstrap 会返回一个空值。

public class T004_ParentAndChild {
    public static void main(String[] args) {
        System.out.println(T004_ParentAndChild.class.getClassLoader());
        System.out.println(T004_ParentAndChild.class.getClassLoader().getClass().getClassLoader()); // App
        System.out.println(T004_ParentAndChild.class.getClassLoader().getParent()); // Extension
        System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent()); // Bootstrap,返回 null
        // System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent().getParent()); // 解开注释就会报空指针异常
    }
}

输出结果:

sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@4554617c
null

双亲委派

  1. 父加载器
    • 父加载器不是【类加载器的加载器】!!!也不是【类加载器的父类加载器】
  2. 双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程
  3. 思考:为什么要搞双亲委派?
    • java.lang.String 类由自定义类加载器加载行不行? – 不行,因为不安全

先是自底向上检查是否已经加载,然后再回过头来找 class 并加载,查看是否加载成功,一直到底都没加载成功就会抛出一个 class 找不到异常。

image-20230819014050253

这个缓存是在哪缓存?

可以简单的认为是它自己内部维护的一个容器,一个 list 或者一个数组加载的东西都扔在里面。每个加载器都有自己的缓存。

为什么类加载器要使用双亲委派?(重要)

主要是为了安全。次要原因是资源浪费问题,避免重新加载问题(防止类重复加载)。

类加载器范围

来自 Launcher 源码

  1. 启动类加载器 Bootstrap ClassLoader
    • 加载路径:sun.boot.class.path
    • 范围:它负责加载 Java 核心类库,这些类库包括 Java API 中的基础类,如 java.lang 包下的类等。
    • 作用:它是 Java 类加载器中最顶层的类加载器,负责加载虚拟机自身需要的类,以及在运行期间会被系统使用的类。由于 Bootstrap ClassLoader 是用本地代码来实现的,所以在 Java 中无法直接获取到该类加载器的引用。
  2. 扩展类加载器 ExtensionClassLoader
    • 加载路径:java.ext.dirs
    • 范围:它负责加载 Java 的扩展库,这些库位于 JRE 的 lib/ext 目录或者由系统变量 java.ext.dirs 指定的路径。
    • 作用:它负责加载一些 Java 标准扩展库以及一些自定义的扩展库,这些库通常是一些可选的功能。扩展类加载器是由 sun.misc.Launcher$ExtClassLoader 实现的。
  3. 系统类加载器 AppClassLoader
    • 加载路径:java.class.path
    • 范围:也被称为系统类加载器,它负责加载用户类路径(Classpath)上指定的类。
    • 作用:它负责加载应用程序中的类,包括开发者自己编写的类以及引用的第三方类库。应用类加载器是由 sun.misc.Launcher$AppClassLoader 实现的。

代码查看:

public class T003_ClassLoaderScope {
    public static void main(String[] args) {
        System.out.println("根目录下加载");
        String pathBoot = System.getProperty("sun.boot.class.path");
        System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));

        System.out.println("--------------------");
        System.out.println("ext 下加载");
        String pathExt = System.getProperty("java.ext.dirs");
        System.out.println(pathExt.replaceAll(";", System.lineSeparator()));

        System.out.println("--------------------");
        System.out.println("App 下加载");
        String pathApp = System.getProperty("java.class.path");
        System.out.println(pathApp.replaceAll(";", System.lineSeparator()));
    }
}

输出结果:

根目录下加载
C:\Program Files\Java\jdk1.8.0_321\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_321\jre\classes
--------------------
ext 下加载
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
--------------------
App 下加载
C:\Program Files\Java\jdk1.8.0_321\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\deploy.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\access-bridge-64.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\cldrdata.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\dnsns.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\jaccess.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\jfxrt.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\localedata.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\nashorn.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\sunec.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\sunjce_provider.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\sunmscapi.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\sunpkcs11.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\ext\zipfs.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\javaws.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\jfxswt.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\management-agent.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\plugin.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_321\jre\lib\rt.jar
D:\笔记\学习资料\马士兵\JVM视频源码\out\production\JVM				// 项目路径
D:\software\idea2022\IntelliJ IDEA 2022.1.3\lib\idea_rt.jar

自定义类加载器

当你想加载某个类的时候,可以调用 loadClass 方法

public class T005_LoadClassByHand {
    public static void main(String[] args) throws ClassNotFoundException {
        Class clazz = T005_LoadClassByHand.class.getClassLoader().loadClass("com.mashibing.jvm.c2_classloader.T002_ClassLoaderLevel");
        System.out.println(clazz.getName());

        //利用类加载器加载资源,参考坦克图片的加载
        //T005_LoadClassByHand.class.getClassLoader().getResourceAsStream("");
    }
}
loadClass 源码解析

loadClass 方法是在双亲委派模型下实现的。

findInCache -> parent.loadClass -> findClass()

private final ClassLoader parent; // final 关键字修饰

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查类是否已经加载
            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
                    // 从非空父类装入器
                }
              
							 // 如果父加载器也找不到,则尝试自己加载
                if (c == null) {
                    // 如果仍未找到,则按顺序调用 findClass
                    // to find the class. 要找到这个类
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 这是定义类装入器;记录统计数据
                    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<?> findClass(String name) throws ClassNotFoundException { // findClass被protected修饰,保护起来
        throw new ClassNotFoundException(name);
    }

主要步骤如下:

  1. 首先,方法会尝试从已加载的类中查找目标类是否已经加载,如果已经加载,则直接返回该类。
  2. 如果目标类未加载,它会尝试委派给父类加载器来加载。这是双亲委派模型的核心思想之一。父类加载器会重复这个过程,直到到达启动类加载器(Bootstrap ClassLoader)为止。
  3. 如果父类加载器也无法加载目标类,则 loadClass 方法会调用 findClass 方法,尝试自己加载目标类。这是子类加载器在加载自己类的最后一步
  4. 最后,如果 resolve 参数为 true,则会调用 resolveClass 方法,用于解析加载的类,确保类的完整性和正确性。

什么时候需要自己去加载?

加载进去就生成 class 对象吗?

不是,要经过 初始化 才生成 class 对象。

自定义一个类加载器需要做什么?

只需要做一件事,就是定义自己的 findClass 就可以了。

具体来说:

  • 继承 ClassLoader

  • 重写模板方法 findClass

    • 调用 defineClass(byte[] -> Class clazz)
  • 加密:可自定义类加载器加载自加密的 class

    • 防止反编译
    • 防止篡改

代码如下:

首先继承 ClassLoader 这个类

// 自定义类加载器继承自ClassLoader
public class T006_MSBClassLoader extends ClassLoader {

    // 重写findClass方法,用于加载类字节码
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 根据类名构建文件路径
        File f = new File("c:/test/", name.replace(".", "/").concat(".class"));
        try {
            // 读取字节码文件
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            // 读取文件内容并写入字节数组输出流
            while ((b=fis.read()) !=0) {
                baos.write(b);
            }

            // 将字节数组转换为字节数组
            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close(); // 关闭流

            // 使用defineClass方法定义并返回类
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 若加载失败,则调用父类的findClass方法
        return super.findClass(name); // throws ClassNotFoundException
    }

    // 主函数
    public static void main(String[] args) throws Exception {
        // 创建自定义类加载器实例
        ClassLoader l = new T006_MSBClassLoader();
        
        // 通过自定义类加载器加载类
        Class clazz = l.loadClass("com.mashibing.jvm.Hello");
        Class clazz1 = l.loadClass("com.mashibing.jvm.Hello");

        // 输出两次加载的类是否相同
        System.out.println(clazz == clazz1);

        // 创建加载的类的实例并调用方法
        Hello h = (Hello) clazz.newInstance();
        h.m();

        // 输出类加载器信息
        System.out.println(l.getClass().getClassLoader()); // 输出自定义类加载器的类加载器
        System.out.println(l.getParent()); // 输出自定义类加载器的父类加载器

        System.out.println(getSystemClassLoader()); // 输出系统类加载器
    }
}

public class Hello {
    public void m() {
        System.out.println("Hello JVM!");
    }
}

这段代码实现了一个自定义的类加载器 T006_MSBClassLoader,该类加载器继承自 ClassLoader,重写了 findClass 方法来加载类的字节码。在主函数中,使用这个自定义的类加载器加载类,并输出加载的类是否相同,然后创建该类的实例并调用方法。最后,输出了自定义类加载器的类加载器和父类加载器信息,以及系统类加载器的信息。这种自定义类加载器的方式允许你从非标准的位置加载类文件,并且可以通过不同的类加载器实现类的隔离。


defineClass 方法是 ClassLoader 类中的一个重要方法,用于将字节数组转换为一个 Class 对象。该方法的作用是将一个【字节数组中的类字节码】转换为一个【Java 类的实例】。当一个类加载器调用 defineClass 方法时,它会将字节数组中的类字节码转换为一个 Class 对象,并返回该对象。(不会验证字节码的正确性)

输出结果

true
Hello JVM!
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
加密

大家都知道 java 的代码 class 文件很容易就被反编译了。

但是我要是定义自己的格式,我不想让别人反编译,这时候怎么办?

你可以通过自定义的 class loader 来进行。然后在写逻辑的时候加一个加密操作。

编译器

三大模式:

  • 解释器(bytecode intepreter) – 解释模式
  • JIT(Just In-Time compiler) – 编译模式
  • 混合模式
    • 混合使用解释器 + 热点代码编译(编译成本地代码,就不用在解释器里面解释来执行了,效率提升)
    • 起始阶段采用解释执行
    • 热点代码检测
      • 多次被调用的方法(方法计数器:监测方法执行频率)
      • 多次被调用的循环(循环计数器:检测循环执行频率)
      • 进行编译

为什么不干脆直接编译成本地代码,那执行效率不更高吗?

有两个原因。

  1. 第一,Java 的解释器,现在它的效率也已经非常高了,在一些简单代码的执行上,它并不输于你编译成本地代码。
  2. 第二,如果你一个执行的文件特别多,各种各样的类库的时候,好几十个 class,你上来二话不说先在内存里编译一遍,这个启动过程会长得吓人。所以它现在默认的模式是混合模式。

可以用指明参数的方式指定用什么模式

  1. -Xmixed 默认为混合模式

    开始解释执行,启动速度较快,对热点代码实行检测和编译

  2. -Xint 使用解释模式,启动很快,执行稍慢

  3. -Xcomp 使用纯编译模式,执行很快,启动很慢(要编译的类少的时候,启动也会很快)

懒加载

lazyloading

  • 严格讲应该叫 lazylnitializing
  • JVM 规范并没有规定何时加载
  • 但是严格规定了什么时候必须初始化,五种情况
    1. 使用 new,getstatic,putstatic,invokestatic 指令时,访问 final 变量除外
    2. java.lang.reflect 对类进行反射调用时
    3. 初始化子类的时候,父类必须首先初始化
    4. 虚拟机启动时,被执行的主类必须初始化
    5. 动态语言支持 java.lang.invoke.MethodHandle 解析的结果为 REF_getstatic, REF_putstatic, REF_invokestatic 的方法句柄时,该类必须初始化

拓展知识

双亲委派如何打破?

parent 是如何指定的,打破双亲委派

  1. super(parent) 指定
  2. 双亲委派的打破
    1. 如何打破:重写 loadClass() 方法
    2. 何时打破过?
      1. JDK1.2 之前,自定义 ClassLoader 都必须重写 loadClass()
      2. ThreadContextClassLoader 可以实现基础类调用实现类代码,通过 thread.setContextClassLoader 指定
      3. 热启动,热部署
        • osgi tomcat 都有自己的模块指定 classloader(可以加载同一类库的不同版本)
类加载器涉及到了哪些设计模式?

在 Java 虚拟机 (JVM) 中的类加载器 (ClassLoader) 实现中,通常使用了以下两种设计模式:

  1. 委托模式 (Delegation Pattern):ClassLoader 的实现通常使用了委托模式来处理类加载请求。委托模式指的是当一个对象收到请求时,它将请求委托给其他对象来处理。在类加载器的情况下,当一个类加载器接收到加载类的请求时,它首先会将请求委托给父类加载器进行处理。如果父类加载器无法加载该类,子类加载器才会尝试加载类。这种委托模式的设计方式可以实现类加载器的层次结构,从而实现类加载器的隔离和类加载的委派。
  2. 单一职责模式 (Single Responsibility Pattern):ClassLoader 的主要责任是加载类文件并定义对应的类。ClassLoader 的设计符合单一职责模式,即一个类应该只负责一项职责。ClassLoader 将类加载的职责封装在一个独立的类中,从而使得类加载的逻辑与其他功能解耦,提高了代码的可维护性和可扩展性。

除了委托模式和单一职责模式,ClassLoader 的实现可能还涉及其他设计模式,具体取决于实际的实现细节和需求。例如,一些类加载器的缓存机制可能使用了享元模式 (Flyweight Pattern) 来提高性能和资源利用效率。

在 Java 虚拟机 (JVM) 中的类加载器 (ClassLoader) 实现中,通常使用了以下两种设计模式:

  1. 委托模式 (Delegation Pattern):ClassLoader 的实现通常使用了委托模式来处理类加载请求。委托模式指的是当一个对象收到请求时,它将请求委托给其他对象来处理。在类加载器的情况下,当一个类加载器接收到加载类的请求时,它首先会将请求委托给父类加载器进行处理。如果父类加载器无法加载该类,子类加载器才会尝试加载类。这种委托模式的设计方式可以实现类加载器的层次结构,从而实现类加载器的隔离和类加载的委派。
  2. 单一职责模式 (Single Responsibility Pattern):ClassLoader 的主要责任是加载类文件并定义对应的类。ClassLoader 的设计符合单一职责模式,即一个类应该只负责一项职责。ClassLoader 将类加载的职责封装在一个独立的类中,从而使得类加载的逻辑与其他功能解耦,提高了代码的可维护性和可扩展性。

除了委托模式和单一职责模式,ClassLoader 的实现可能还涉及其他设计模式,具体取决于实际的实现细节和需求。例如,一些类加载器的缓存机制可能使用了享元模式 (Flyweight Pattern) 来提高性能和资源利用效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/253585.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kafka相关知识

一、kafka架构 Kafka基础知识 Kafka是最初由Linkedin公司开发&#xff0c;是一个分布式、分区的、多副本的、多生产者、多订阅者&#xff0c;基于zookeeper协 调的分布式日志系统(也可以当做MQ系统)&#xff0c;常见可以用于webynginx日志、访问日志&#xff0c;消息服务等等&…

pycharm依赖管理(不要用pip freeze)

在使用python虚拟环境时&#xff0c;可以使用requirements.txt来管理当前项目的依赖。 注意&#xff0c;不要用 pip freeze > requirements.txt 这个命令&#xff0c;因为它会引入很多无关的包。 可以使用 pipreqs ./ --encodingutf-8 ./ 表示当前项目的目录&#xff0…

QT for Android安卓编译环境搭建+首次编译3个大坑

1、安装 编译环境能否搭建成功&#xff0c;主要是看各个依赖软件的版本是否匹配。依赖的软件有3个&#xff1a;JDK、安卓SDK、安卓NDK。 我的qt版本是5.14.1&#xff0c;我亲测以下版本可以成功让编译安卓&#xff1a; QT5.14 JDK1.8.0 安卓SDK26.1 安卓NDK20.1 在QT-&g…

为什么在Android中需要Context?

介绍 在Android开发中&#xff0c;Context是一个非常重要的概念&#xff0c;但是很多开发者可能并不清楚它的真正含义以及为什么需要使用它。本文将详细介绍Context的概念&#xff0c;并解释为什么在Android应用中需要使用它。 Context的来源 Context的概念来源于Android框架…

【算法Hot100系列】三数之和

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

第十三章总结

一.泛型 1.定义泛型类 泛型机制语法&#xff1a; 类名<T> 其中&#xff0c;T是泛型的名称&#xff0c;代表某一种类型。 【例13.6】创建带泛型的图书类 代码&#xff1a; 结果&#xff1a; 2.泛型的常规用法 (1)定义泛型类时声明多个变量 class MyClass<T1,T2…

python提取图片型pdf中的文字(提取pdf扫描件文字)

前言 文字型pdf提取&#xff0c;python的库一大堆&#xff0c;但是图片型pdf和pdf扫描件提取&#xff0c;还是有些难度的&#xff0c;我们需要用到OCR&#xff08;光学字符识别&#xff09;功能。 一、准备 1、安装OCR&#xff08;光学字符识别&#xff09;支持库 首先要安…

【PostgreSQL】从零开始:(十三)PostgreSQL-SQL语句操作架构(模式) Schema

Schema概述 PostgreSQL 数据库集群包含一个或多个命名数据库。角色和一些其他对象类型在整个集群中共享。与服务器的客户端连接只能访问单个数据库中的数据&#xff0c;该数据库在连接请求中指定。 用户不一定有权访问集群中的每个数据库。共享角色名称意味着不能在同一集群中…

第1章 做一个多模型思考者

目录 1. 概述2. 大数据时代的模型3. 为什么需要多模型4. 智慧层次结构&#xff08;Wisdom Hierarchy&#xff09;5. 做一个多模型思考者 1. 概述 模型是用数学公式和图表展现的形式化结构 在拥有多个模型的情况下&#xff0c;我们能够避免每个模型本身所固有的局限性 多个模型…

信息收集 - 域名

1、Whois查询: Whois 是一个用来查询域名是否已经被注册以及相关详细信息的数据库(如:域名所有人、域名注册商、域名注册日期和过期日期等)。通过访问 Whois 服务器,你可以查询域名的归属者联系方式和注册时间。 你可以在 域名Whois查询 - 站长之家 上进行在线查询。 2、…

Java架构师系统架构内部维度分析

目录 1 导语2.1 安全性维度概述2.2 流程安全性2.3 架构安全性2.4 安全维度总结3 伸缩性维度概述和场景思路3.1 无状态应用弹性伸缩3.2 阿里云Knative弹性伸缩3.3 有状态应用弹性伸缩3.4 伸缩性维度总结想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导语

(数据结构)单链表的查找和长度计算

代码实现 #include<stdio.h> #include<stdlib.h> typedef struct LNode {int data;struct LNode* next; }LNode,*LinkList; //创建头结点 LNode* InitList(LinkList L) {L (LNode*)malloc(sizeof(LNode));if (L NULL){return NULL;}L->data 0;L->next N…

NBA得分数据可视化

简介 这是上学期的一些课外活动内容&#xff0c;将 NBA 得分数据进行可视化&#xff0c;并进行后续的探索性分析和建模&#xff08;本文未介绍&#xff09;。主要研究动机来源于这篇论文&#xff1a; 该论文使用二元的伽马过程来刻画 NBA 主客场得分数据&#xff0c;并且考虑了…

智能五子棋1

*一、项目需求* 五子棋是一种简单的黑白棋&#xff0c;历史悠久&#xff0c;起源于中国&#xff0c;后传入日本&#xff0c;在日本被称为“连珠”&#xff0c;是一种老少皆宜的益智游戏。 人工智能五子棋系统的目标用户是一切想致力于研究人机对弈算法理论的相关研究者和一切…

【每日一题】【12.17】746.使用最小花费爬楼梯

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 1.题目链接 746. 使用最小花费爬楼梯https://leetcode.cn/problems/min-cost-climbing-stairs/ 2.题目详情 今天的每日一题又…

04_Web框架之Django一

Web框架之Django一 学习目标和内容 1、能够描述Django的作用 2、能够使用Django创建应用 3、能够使用GET和POST请求方式进行传参 4、能够使用Django的函数式方法定义视图 5、能够进行Django的配置文件修改 6、能够基本使用Django的路由定义 一、Django相关介绍 1、什么是Djan…

ASP.NET MVC实战之权限拦截Authorize使用

1&#xff0c;具体的实现方法代码如下 public class CustomAuthorizeAttribute : FilterAttribute, IAuthorizationFilter{/// <summary>/// 如果需要验证权限的时候&#xff0c;就执行进来/// </summary>/// <param name"filterContext"></par…

饥荒Mod 开发(十):制作一把AOE武器

饥荒Mod 开发(九)&#xff1a;物品栏排列 饥荒Mod 开发(十一)&#xff1a;修改物品堆叠 前面的文章介绍了很多基础知识以及如何制作一个物品&#xff0c;这次制作一把武器&#xff0c;装备之后可以用来攻击怪物。 制作武器贴图和动画 1.1 制作贴图。 先准备一张武器的贴图&a…

【23-24 秋学期】NNDL 作业11 LSTM

目录 习题6-4 推导LSTM网络中参数的梯度&#xff0c; 并分析其避免梯度消失的效果 习题6-3P 编程实现下图LSTM运行过程 &#xff08;一&#xff09;numpy实现 &#xff08;二&#xff09;使用nn.LSTMCell实现 &#xff08;三&#xff09; 使用nn.LSTM实现 总结 &#x…

神经网络可以计算任何函数的可视化证明

神经网络可以计算任何函数的可视化证明 对于神经网络&#xff0c;一个显著的事实就是它可以计算任何函数。 如下&#xff1a;不管该函数如何&#xff0c;总有神经网络能够对任何可能的输入x&#xff0c;输出值f&#xff08;x&#xff09; 即使函数有很多输入和输出&#xff0…