JVM基础入门

news2024/11/27 6:20:32

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) 来提高性能和资源利用效率。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习 | 线性算法 —— 大禹治水

Machine-Learning: 《机器学习必修课&#xff1a;经典算法与Python实战》配套代码 - Gitee.com 如果说KNN算法体现了人们对空间距离的理解&#xff0c; 那么线性算法则体现了人们对事物趋势上的认识。 注意图中横纵坐标的不同。 线性回归、多项式回归多用于预测&#xff0c;逻辑…

分布式定时任务系列7:XXL-job源码分之任务触发

传送门 分布式定时任务系列1&#xff1a;XXL-job安装 分布式定时任务系列2&#xff1a;XXL-job使用 分布式定时任务系列3&#xff1a;任务执行引擎设计 分布式定时任务系列4&#xff1a;任务执行引擎设计续 分布式定时任务系列5&#xff1a;XXL-job中blockingQueue的应用 …

spring-kakfa依赖管理之org/springframework/kafka/listener/CommonErrorHandler错误

问题&#xff1a; 整个项目使用spring-boot2.6.8版本&#xff0c;使用gradle构建&#xff0c;在common模块指定了implementation org.springframework.kafka:spring-kafka:2.6.8’这个工程也都能运行&#xff08;这正常发送kafka消息和接收消息&#xff09;&#xff0c;但是执行…

重新认识Word——尾注

重新认识Word——尾注 参考文献格式文献自动生成器插入尾注将数字带上方括号将参考文献中的标号改为非上标 多处引用一篇文献多篇文献被一处引用插入尾注有横线怎么删除&#xff1f;删除尾注 前面我们学习了如何给图片&#xff0c;公式自动添加编号&#xff0c;今天我们来看看毕…

[Verilog] Verilog 数据类型

主页&#xff1a; 元存储博客 文章目录 前言1. bit 类型2. reg 类型3 wire类型4 integer类型5 real类型6 parameter类型7 enum类型8 array 类型9 向量类型10 time 类型11 string 类型 前言 在 Verilog 中&#xff0c;有几种不同的数据类型可以用于声明和操作变量。 在 Verilo…

docker文档转译1

写在最前面 本文主要是转译docker官方文档。主题是Docker overview&#xff0c;这里是链接 Docker概述 Docker是一个用于开发、发布和运行应用程序的开放平台。Docker使你能够将应用程序与基础设施分离&#xff0c;从而可以快速交付软件。你可以使用相同的方法像管理应用程序…

设计模式 五种不同的单例模式 懒汉式 饿汉式 枚举单例 容器化单例(Spring单例源码分析) 线程单例

单例模式 第一种 饿汉式 优点&#xff1a;执行效率高&#xff0c;性能高&#xff0c;没有任何的锁 缺点&#xff1a;某些情况下&#xff0c;可能会造成内存浪费 /*** author LionLi*/ public class HungrySingleton {private static final HungrySingleton hungrySingleton n…

【Hadoop面试】HDFS读写流程

HDFS&#xff08;Hadoop Distributed File System&#xff09;是GFS的开源实现。 HDFS架构 HDFS是一个典型的主/备&#xff08;Master/Slave&#xff09;架构的分布式系统&#xff0c;由一个名字节点Namenode(Master) 多个数据节点Datanode(Slave)组成。其中Namenode提供元数…

插入排序----希尔排序

希尔排序 希尔排序法又称缩小增量法。希尔排序法的基本思想是&#xff1a;先选定一个整数&#xff0c;把待排序文件中所有记录分成个gap组&#xff0c;所有距离为的记录分在同一组内&#xff0c;并对每一组内的记录进行排序。然后&#xff0c;取&#xff0c;重复上述分组和排序…

虚拟化之安全虚拟化

虚拟化首次引入是在Armv7-A架构中。那时&#xff0c;Hyp模式&#xff08;在AArch32中相当于EL2&#xff09;仅在非安全状态下可用。当Armv8.4-A引入时&#xff0c;添加了对安全状态下EL2的支持作为一个可选特性。 当处理器支持安全EL2时&#xff0c;需要使用SCR_EL3.EEL2位从E…

DFT音频还原及降噪实战

傅里叶变换与信息隐写术(二) 声音数据 ​ 声音可以用连续的波形来表示 ​ 声音在计算机中的存储是离散的 ​ 计算机中存储的是声音的几个采样点的数据&#xff0c;1 秒钟采样 5 个点就表示采样频率是 5 Hz&#xff08;每隔 0.25 秒取一个点&#xff0c;注意第 0 秒也取&#…

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

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

实现el-table操作列点击弹出echarts

代码&#xff1a; <el-table-column :width"90"><template #default"scope"><el-popover placement"left-end" width"550" trigger"click"><div><div style"font-size: 18px; margin-left…

Postman介绍和快速使用

Postman 是什么&#xff1f; Postman 是一个流行的API&#xff08;Application Programming Interface&#xff09;开发工具&#xff0c;它使得开发者可以很容易地创建、测试、共享和文档化API。Postman 提供了一个友好的用户界面&#xff0c;来发送HTTP请求&#xff0c;接收响…

How to helm install prometheus 【 helm 安装 prometheus 】

文章目录 1. 简介2. 简单部署3. 数据持久化部署3.1 设置必要的环境变量3.2 运行安装脚本3.3 查看 1. 简介 kube-prometheus-stack是一个基于Prometheus和Grafana的开源软件套件&#xff0c;用于在Kubernetes集群中进行监控和可视化。它提供了一套完整的工具和组件&#xff0c;…

Python Django 连接 PostgreSQL 操作实例

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python Django 连接 PostgreSQL 操作实例&#xff0c;全文3500字&#xff0c;阅读大约10分钟 在Web开发中&#xff0c;使用Django连接到PostgreSQL数据库是一种常见的选择。…

如何从 iPhone 上恢复已删除的照片教程分享

您是否错误地删除了 iPhone 上的错误照片&#xff1f;或者您可能已将手机恢复出厂设置&#xff0c;但现在所有照片都消失了&#xff1f;如果您现在遇到这样的情况&#xff0c;我们可以为您提供解决方案。 在本文中&#xff0c;我们将向您展示七种数据恢复方法&#xff0c;可以…

饥荒Mod 开发(十四):制作屏幕弹窗

饥荒Mod 开发(十三)&#xff1a;木牌传送 在上一个文章里面制作了一个传送选择页面&#xff0c;是一个全屏的窗口&#xff0c;那饥荒中如何制作一个全屏的窗口&#xff0c;下面介绍一下如何从零开始制作一个全屏窗口 制作屏幕窗口 饥荒中的全屏窗口都有一个基类 “Screen”,我…

使用Nginx实现负载均衡的实践指南

目录 前言1 负载均衡简介2 需要实现的效果3 准备2个tomcat服务器4 配置Nginx实现负载均衡5 Nginx的服务器策略5.1 轮询&#xff08;默认&#xff09;5.2 权重&#xff08;weight&#xff09;5.3 IP哈希&#xff08;ip_hash&#xff09;5.4 响应时间公平分配&#xff08;fair&am…

论文阅读:Learning sRGB-to-Raw-RGB De-rendering with Content-Aware Metadata

论文阅读&#xff1a;Learning sRGB-to-Raw-RGB De-rendering with Content-Aware Metadata Abstract 大多数的 Camera ISP 会将 RAW 图经过一系列的处理&#xff0c;变成 sRGB 图像&#xff0c;ISP 的处理中很多模块是非线性的操作&#xff0c;这些操作会破坏环境光照的线性…