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 实现
-
Hotspot(最常用)
- oracle 官方,我们做实验用的 JVM,Java 虚拟机
java -version
命令可查看使用的是什么 JVM- Hotspot 8 之后要收费,但 Open JDK 是开源的版本,免费
-
Jrockit
- BEA 曾经号称世界上最快的 JVM
- 被 Oracle 收购,合并于 hotspot
-
TaobaoVM(免费)
- hotspot 深度定制版
-
LiquidVM
- 直接针对硬件
-
azul zing(特别贵)
- 最新垃圾回收的业界标杆(号称 1ms 以内)
- www.azul.com
-
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 程序运行时的三个主要阶段。
-
Loading – 加载
-
Linking – 链接
- Verification – 验证
- Preparation – 准备
- Resolution – 解析
-
Initializing – 初始化
具体来说:
-
Loading(加载): 这是类加载过程的第一个阶段。在这个阶段,Java 虚拟机(JVM)会从类的外部源加载类的二进制数据,通常是从磁盘文件中加载,但也可以是网络、内存等。
加载器将类的二进制数据(class文件)从外部源加载到内存中,并将其放置在运行时数据区的方法区内。
-
Linking(链接): 这是加载过程的第二个阶段,它将类的二进制数据链接到 JVM 的运行时状态。
链接过程分为以下三个步骤:
- Verification(验证):确保类的二进制数据符合 JVM 规范,不违反类加载的安全性要求。这个步骤确保类的结构正确,不会引发运行时错误。
- Preparation(准备):为类的静态变量分配内存空间,并设置默认的初始值(通常为零值)。这个步骤为类变量分配内存并初始化,但不会为实例变量分配内存。(把 class 文件的静态变量赋默认值,比如 int 类型的默认值为 0)
- Resolution(解析):将(class文件里面的常量池里面用到的)符号引用转换为直接引用,解析动作可以在运行时再完成,也可以在编译时静态完成。这个步骤处理类、接口、方法和字段的符号引用,将其解析为实际的引用。
-
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。
注意:从下往上,是委托给父加载器,不是继承关系,是语法上的一种关系。
在 Java 虚拟机中,类加载器按照层次结构进行组织,分为三个主要层次:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader,也称为系统类加载器)。这些类加载器形成了类加载器的双亲委派模型。
- 启动类加载器(Bootstrap ClassLoader):这是 Java 虚拟机的一部分,用于加载 Java 核心库(例如
java.lang
包中的类)。它是所有其他类加载器的父加载器,但它本身不是一个普通的 Java 类,因此在源码中并没有对应的类。它位于虚拟机内部,通常用本地代码实现。它是最顶层的类加载器,负责加载 Java 核心类库。 - 扩展类加载器(Extension ClassLoader): 扩展类加载器用于加载 Java 虚拟机的扩展类库,位于
jre/lib/ext
目录中的类。它的父加载器是启动类加载器。 - 应用程序类加载器(Application ClassLoader):这个加载器也称为系统类加载器,它负责加载应用程序的类,包括用户自定义的类和第三方库中的类。它的父加载器是扩展类加载器。
启动类加载器、扩展类加载器和应用程序类加载器构成了类加载器的层次结构,通过双亲委派模型来保证类的加载的一致性和安全性。在加载一个类时,首先会尝试由父加载器加载,只有在父加载器无法加载时,子加载器才会尝试加载。这个模型可以防止类的重复加载,同时保证了类的隔离性。
在启动类加载器加载的类中,有一部分是虚拟机内部的类,比如 java.lang.Object
、java.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()); // AppSystem.out.println(T004_ParentAndChild.class.getClassLoader().getParent()); // ExtensionSystem.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
双亲委派
- 父加载器
- 父加载器不是【类加载器的加载器】!!!也不是【类加载器的父类加载器】
- 双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程
- 思考:为什么要搞双亲委派?
- java.lang.String 类由自定义类加载器加载行不行? – 不行,因为不安全
先是自底向上检查是否已经加载,然后再回过头来找 class 并加载,查看是否加载成功,一直到底都没加载成功就会抛出一个 class 找不到异常。
这个缓存是在哪缓存?
可以简单的认为是它自己内部维护的一个容器,一个 list 或者一个数组加载的东西都扔在里面。每个加载器都有自己的缓存。
为什么类加载器要使用双亲委派?(重要)
主要是为了安全。次要原因是资源浪费问题,避免重新加载问题(防止类重复加载)。
类加载器范围
来自 Launcher 源码
- 启动类加载器 Bootstrap ClassLoader:
- 加载路径:
sun.boot.class.path
- 范围:它负责加载 Java 核心类库,这些类库包括 Java API 中的基础类,如
java.lang
包下的类等。 - 作用:它是 Java 类加载器中最顶层的类加载器,负责加载虚拟机自身需要的类,以及在运行期间会被系统使用的类。由于 Bootstrap ClassLoader 是用本地代码来实现的,所以在 Java 中无法直接获取到该类加载器的引用。
- 加载路径:
- 扩展类加载器 ExtensionClassLoader:
- 加载路径:
java.ext.dirs
- 范围:它负责加载 Java 的扩展库,这些库位于 JRE 的
lib/ext
目录或者由系统变量java.ext.dirs
指定的路径。 - 作用:它负责加载一些 Java 标准扩展库以及一些自定义的扩展库,这些库通常是一些可选的功能。扩展类加载器是由
sun.misc.Launcher$ExtClassLoader
实现的。
- 加载路径:
- 系统类加载器 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);}
主要步骤如下:
- 首先,方法会尝试从已加载的类中查找目标类是否已经加载,如果已经加载,则直接返回该类。
- 如果目标类未加载,它会尝试委派给父类加载器来加载。这是双亲委派模型的核心思想之一。父类加载器会重复这个过程,直到到达启动类加载器(Bootstrap ClassLoader)为止。
- 如果父类加载器也无法加载目标类,则
loadClass
方法会调用findClass
方法,尝试自己加载目标类。这是子类加载器在加载自己类的最后一步。 - 最后,如果
resolve
参数为true
,则会调用resolveClass
方法,用于解析加载的类,确保类的完整性和正确性。
什么时候需要自己去加载?
加载进去就生成 class 对象吗?
不是,要经过 初始化 才生成 class 对象。
自定义一个类加载器需要做什么?
只需要做一件事,就是定义自己的 findClass 就可以了。
具体来说:
-
继承 ClassLoader
-
重写模板方法 findClass
- 调用 defineClass(byte[] -> Class clazz)
-
加密:可自定义类加载器加载自加密的 class
- 防止反编译
- 防止篡改
代码如下:
首先继承 ClassLoader 这个类
// 自定义类加载器继承自ClassLoader
public class T006_MSBClassLoader extends ClassLoader {// 重写findClass方法,用于加载类字节码@Overrideprotected 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) – 编译模式
- 混合模式
- 混合使用解释器 + 热点代码编译(编译成本地代码,就不用在解释器里面解释来执行了,效率提升)
- 起始阶段采用解释执行
- 热点代码检测
- 多次被调用的方法(方法计数器:监测方法执行频率)
- 多次被调用的循环(循环计数器:检测循环执行频率)
- 进行编译
为什么不干脆直接编译成本地代码,那执行效率不更高吗?
有两个原因。
- 第一,Java 的解释器,现在它的效率也已经非常高了,在一些简单代码的执行上,它并不输于你编译成本地代码。
- 第二,如果你一个执行的文件特别多,各种各样的类库的时候,好几十个 class,你上来二话不说先在内存里编译一遍,这个启动过程会长得吓人。所以它现在默认的模式是混合模式。
可以用指明参数的方式指定用什么模式
-
-Xmixed 默认为混合模式
开始解释执行,启动速度较快,对热点代码实行检测和编译
-
-Xint 使用解释模式,启动很快,执行稍慢
-
-Xcomp 使用纯编译模式,执行很快,启动很慢(要编译的类少的时候,启动也会很快)
懒加载
lazyloading
- 严格讲应该叫 lazylnitializing
- JVM 规范并没有规定何时加载
- 但是严格规定了什么时候必须初始化,五种情况
- 使用 new,getstatic,putstatic,invokestatic 指令时,访问 final 变量除外
- java.lang.reflect 对类进行反射调用时
- 初始化子类的时候,父类必须首先初始化
- 虚拟机启动时,被执行的主类必须初始化
- 动态语言支持 java.lang.invoke.MethodHandle 解析的结果为 REF_getstatic, REF_putstatic, REF_invokestatic 的方法句柄时,该类必须初始化
拓展知识
双亲委派如何打破?
parent 是如何指定的,打破双亲委派
- 用
super(parent)
指定 - 双亲委派的打破
- 如何打破:重写
loadClass()
方法 - 何时打破过?
- JDK1.2 之前,自定义 ClassLoader 都必须重写 loadClass()
- ThreadContextClassLoader 可以实现基础类调用实现类代码,通过 thread.setContextClassLoader 指定
- 热启动,热部署
- osgi tomcat 都有自己的模块指定 classloader(可以加载同一类库的不同版本)
- 如何打破:重写
类加载器涉及到了哪些设计模式?
在 Java 虚拟机 (JVM) 中的类加载器 (ClassLoader) 实现中,通常使用了以下两种设计模式:
- 委托模式 (Delegation Pattern):ClassLoader 的实现通常使用了委托模式来处理类加载请求。委托模式指的是当一个对象收到请求时,它将请求委托给其他对象来处理。在类加载器的情况下,当一个类加载器接收到加载类的请求时,它首先会将请求委托给父类加载器进行处理。如果父类加载器无法加载该类,子类加载器才会尝试加载类。这种委托模式的设计方式可以实现类加载器的层次结构,从而实现类加载器的隔离和类加载的委派。
- 单一职责模式 (Single Responsibility Pattern):ClassLoader 的主要责任是加载类文件并定义对应的类。ClassLoader 的设计符合单一职责模式,即一个类应该只负责一项职责。ClassLoader 将类加载的职责封装在一个独立的类中,从而使得类加载的逻辑与其他功能解耦,提高了代码的可维护性和可扩展性。
除了委托模式和单一职责模式,ClassLoader 的实现可能还涉及其他设计模式,具体取决于实际的实现细节和需求。例如,一些类加载器的缓存机制可能使用了享元模式 (Flyweight Pattern) 来提高性能和资源利用效率。
?
在 Java 虚拟机 (JVM) 中的类加载器 (ClassLoader) 实现中,通常使用了以下两种设计模式:
- 委托模式 (Delegation Pattern):ClassLoader 的实现通常使用了委托模式来处理类加载请求。委托模式指的是当一个对象收到请求时,它将请求委托给其他对象来处理。在类加载器的情况下,当一个类加载器接收到加载类的请求时,它首先会将请求委托给父类加载器进行处理。如果父类加载器无法加载该类,子类加载器才会尝试加载类。这种委托模式的设计方式可以实现类加载器的层次结构,从而实现类加载器的隔离和类加载的委派。
- 单一职责模式 (Single Responsibility Pattern):ClassLoader 的主要责任是加载类文件并定义对应的类。ClassLoader 的设计符合单一职责模式,即一个类应该只负责一项职责。ClassLoader 将类加载的职责封装在一个独立的类中,从而使得类加载的逻辑与其他功能解耦,提高了代码的可维护性和可扩展性。
除了委托模式和单一职责模式,ClassLoader 的实现可能还涉及其他设计模式,具体取决于实际的实现细节和需求。例如,一些类加载器的缓存机制可能使用了享元模式 (Flyweight Pattern) 来提高性能和资源利用效率。
相关文章:
JVM基础入门
JVM 基础入门 JVM 基础 聊一聊 Java 从编码到执行到底是一个怎么样的过程? 假设我们有一个文件 x.Java,你执行 javac,它就会变成 x.class。 这个 class 怎么执行的? 当我们调用 Java 命令的时候,class 会被 load 到…...
前端真的死了吗
随着人工智能和低代码的崛起,“前端已死”的声音逐渐兴起。前端已死?尊嘟假嘟?快来发表你的看法吧! 以下方向仅供参考。 一、为什么会出现“前端已死”的言论 前端已死这个言论 是出自于2022年开始 ,2022年下半年疫情…...
前后端分离开发
前期 前后端混合开发 后期 前后端分离开发...
向量数据库——AI时代的基座
向量数据库——AI时代的基座 1.前言 向量数据库在构建基于大语言模型的行业智能应用中扮演着重要角色。大模型虽然能回答一般性问题,但在垂直领域服务中,其知识深度、准确度和时效性有限。为了解决这一问题,企业可以利用向量数据库结合大模…...
【️什么是分布式系统的一致性 ?】
😊引言 🎖️本篇博文约8000字,阅读大约30分钟,亲爱的读者,如果本博文对您有帮助,欢迎点赞关注!😊😊😊 🖥️什么是分布式系统的一致性 ?…...
鸿蒙ArkTS Web组件加载空白的问题原因及解决方案
问题症状 初学鸿蒙开发,按照官方文档Web组件文档《使用Web组件加载页面》示例中的代码照抄运行后显示空白,纠结之余多方搜索后扔无解决方法。 运行代码 import web_webview from ohos.web.webviewEntry Component struct Index {controller: web_webv…...
【Java】网络编程-UDP回响服务器客户端简单代码编写
这一篇文章我们将讲述网络编程中UDP服务器客户端的编程代码 1、前置知识 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。 UDP的特点有:无连接、尽最大努力交付、面向报文、没有拥塞控制 本文讲…...
【设计模式】之工厂模式
工厂模式 1.介绍 工厂模式(创建型模式),是我们最常用的实例化对象模式,是用工厂方法代替new操作的一种模式;在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的…...
70.爬楼梯
题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意: 给定 n 是一个正整数。 示例 1: 输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶…...
【论文解读】ICLR 2024高分作:ViT需要寄存器
来源:投稿 作者:橡皮 编辑:学姐 论文链接:https://arxiv.org/abs/2309.16588 摘要: Transformer最近已成为学习视觉表示的强大工具。在本文中,我们识别并表征监督和自监督 ViT 网络的特征图中的伪影。这些…...
【Redis】AOF 基础
因为 Redis AOF 的实现有些绕, 就分成 2 篇进行分析, 本篇主要是介绍一下 AOF 的一些特性和依赖的其他函数的逻辑,为下一篇 (Redis AOF 源码) 源码分析做一些铺垫。 AOF 全称: Append Only File, 是 Redis 提供了一种数据保存模式, Redis 默认不开启。 AOF 采用日志的形式来记…...
C语言—每日选择题—Day50
一天一天的更新,也是达到50天了,精选的题有250道,博主累计做了不下500道选择题,最喜欢的题型就是指针和数组之间的计算呀,不知道关注我的小伙伴是不是一直在坚持呢?文末有投票,大家可以投票让博…...
[C/C++]——内存管理
学习C/C的内存管理 前言:一、C/C的内存分布二、C语言中动态内存管理方式三、C中动态内存管理方式3.1、new/delete操作符3.1.2、new/delete操作内置类型3.1.3、new/delete操作自定义类型 3.2、认识operator new和operator delete函数3.3、了解new和delete的实现原理3…...
PDF文件的限制编辑,如何设置?
想要给PDF文件设置一个密码防止他人对文件进行编辑,那么我们可以对PDF文件设置限制编辑,设置方法很简单,我们在PDF编辑器中点击文件 – 属性 – 安全,在权限下拉框中选中【密码保护】 然后在密码保护界面中,我们勾选【…...
Linux 中使用 docker 安装 Elasticsearch 及 Kibana
Linux 中使用 docker 安装 Elasticsearch 及 Kibana 安装 Elasticsearch 和 Kibana安装分词插件 ik_smart 安装 Elasticsearch 和 Kibana 查看当前运行的镜像及本地已经下载的镜像,确认之前没有安装过 ES 和 Kibana 镜像 docker ps docker images从远程镜像仓库拉…...
在Flutter中使用PhotoViewGallery指南
介绍 Flutter中的PhotoViewGallery是一个功能强大的插件,用于在应用中展示可缩放的图片。无论是构建图像浏览器、相册应用,还是需要在应用中查看大图的场景,PhotoViewGallery都是一个不错的选择。 添加依赖 首先,需要在pubspec…...
c语言中的static静态(1)static修饰局部变量
#include<stdio.h> void test() {static int i 1;i;printf("%d ", i); } int main() {int j 0;while (j < 5){test();j j 1;}return 0; } 在上面的代码中,static修饰局部变量。 当用static定义一个局部变量后,这时局部变量就是…...
生信算法4 - 获取overlap序列索引和序列的算法
生信序列基本操作算法 建议在Jupyter实践,python版本3.9 1. 获取overlap序列索引和序列的算法实现 # min_length 最小overlap碱基数量3个 def getOverlapIndexAndSequence(a, b, min_length3):""" Return length of longest suffix of a matching…...
springboot 学习网站
Spring Boot 系列教程https://www.docs4dev.com/ Spring Boot 教程汇总 http://www.springboot.wiki/ Spring Cloud 微服务教程 http://www.springboot.wiki/ 1、自定义banner https://www.cnblogs.com/cc11001100/p/7456145.html 2、事件和监听器 https://blog.csd…...
论文笔记:A review on multi-label learning
一、介绍 传统的监督学习是单标签学习,但是现实中一个实例可能对应多个标签。这篇文章介绍了多标签分类的定义和评价指标、多标签学习的算法还有其他相关的任务。 二、问题相关定义 2.1 多标签学习任务 假设 X R d X R^d XRd,表示d维的输入空间&am…...
接口文档 YAPI介绍
YAPI介绍 YAPI使用流程...
LeetCode 300最长递增子序列 674最长连续递增序列 718最长重复子数组 | 代码随想录25期训练营day52
动态规划算法10 LeetCode 300 最长递增子序列 2023.12.15 题目链接代码随想录讲解[链接] int lengthOfLIS(vector<int>& nums) {//创建变量result存储最终答案,设默认值为1int result 1;//1确定dp数组,dp[i]表示以nums[i]为结尾的子数组的最长长度ve…...
Improving IP Geolocation with Target-Centric IP Graph (Student Abstract)
ABSTRACT 准确的IP地理定位对于位置感知的应用程序是必不可少的。虽然基于以路由器为中心(router-centric )的IP图的最新进展被认为是前沿的,但一个挑战仍然存在:稀疏IP图的流行(14.24%,少于10个节点,9.73%孤立)限制了图的学习。为了缓解这个问题,我们将目标主机(ta…...
华为技面三轮面试题
1. 最长回文子串 -- 中心扩散法 给你一个字符串 s,找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 示例 1: 输入:s "babad" 输出:"bab" 解释&…...
Linux arm架构下构建Electron安装包
上篇文章我们介绍 Electron 基本的运行开发与 windows 安装包构建简单流程,这篇文章我们从零到一构建 Linux arm 架构下安装包,实际上 Linux arm 的构建流程,同样适用于 Linux x86 环境,只不过需要各自的环境依赖,Linu…...
【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 NLP 部分
【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 NLP 部分 概述NLP 简介文本处理词嵌入上下文理解 文本数据加载to_device 函数构造数据加载样本数量 len获取样本 getitem 分词构造函数调用函数轮次嵌入 RobertaRoberta 创新点NSP (Next Sentence Prediction…...
推免那些事
平生第一次搞推免,也是最后一次。错失了一些机会,也有幸获得了一些机会,值得祝庆,也值得反思。 以下记录为个人流水账。 个人背景 我的背景可以算不是非常好了,况且今年211受歧视比较严重。 学校:211&…...
华清远见嵌入式学习——QT——作业2
作业要求: 代码运行效果图: 登录失败 和 最小化 和 取消登录 登录成功 和 X号退出 代码: ①:头文件 #ifndef LOGIN_H #define LOGIN_H#include <QMainWindow> #include <QLineEdit> //行编辑器类 #include…...
C# Winfrm 编写一个天气查看助手
#前言# 最近这个北方的天气啊经常下雪,让我想起来我上学时候写的那个天气预报小功能了,今天又复现了一下,哈哈哈,大家当个乐子看哈! 1.创建项目 2.添加引用 上图所示,下载所需天气预报标识,网站…...
基于SpringBoot和微信小程序的农场信息管理系统
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot和微信小程序的农场信息管…...
wordpress底部CSS/app推广平台
据统计,全球范围内被投递的钓鱼邮件每天约达到1亿封,经常会遇到一些邮件发送方,被spammer利用于伪造各种钓鱼/诈骗邮件,如:伪造银行、保险等金融企业,支付宝、Paypal等支付商,知名网站、政府网站…...
环保网站案例/搜索引擎营销流程是什么?
抽象类与接口抽象类定义和语法理解抽象类作用抽象类总结:接口概念接口特性注意事项:实现多个接口接口使用实例Clonable 接口和深拷贝抽象类和接口的区别?抽象类 定义和语法 包含抽象方法的类,叫做抽象类 需要用abstract修饰这个…...
wordpress音乐盒/东莞seo网站排名优化公司
1. 流水线实现多周期指令简图2. MIPS流水线基本流程1. 取指令;2. 读寄存器和译码;3. 执行ALU和地址计算;4. 存储器访问;5. 写结果到寄存器。3. 非流水与流水的对比:非流水的执行时间– 3条指令共需3800=240…...
万网做网站花多少钱/优化大师下载安装
在前面提到过,Starling是Sparrow的姊妹篇,正因为这样,Starling里的touch事件的机制其实是为移动设备的触摸交互设计的,所以当你使用它进行使用鼠标交互的桌面应用开发时,第一眼会感觉有些困惑。 首先,如果你…...
网站内页做排名/自动点击器软件
摘要自簡單來說就是可以將一個 or 多個應用包裝成一個服務,並透過 chart 的形式發佈,讓大家可以方便在 k8s 上安裝特定的服務。幾個元件的名詞Tiller server: 用來與API server溝通,使用chart在k8s cluster上建立服務Helm client: 則是用來操…...
surface go 网站开发/网站收录免费咨询
GIT分支 分支在GIT中相对较难,分支就是科幻电影里面的平行宇宙,如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,我们就需要处理一些问题了! git br…...