JVM(Java Virtual Machine)(中)

简介: JVM(Java Virtual Machine)(中)

初始化


针对类对象的内容进行初始化

执行代码块, 静态代码块, 加载父类…


类加载的时机


并非 Java 程序运行, 所有的类就会被加载

而是真正用到该类, 才会被加载

(懒汉模式)

  • 常见的类加载时机
  • 构造类的实例
  • 调用这个类的静态方法 / 静态成员变量
  • 加载子类之前, 需先加载其父类

(加载过一次之后, 后续使用就不必重复加载)

双亲委派模型


加载

找到 .class 文件, 打开文件, 读取文件(将文件内容读取至内存中)

双亲委派模型

描述的是找到 .class 文件的基本过程

  • JVM 默认提供了三个类加载器
  • BootstrapClassLoader(负责加载标准库中的类)
  • ExtensionClassLoader(负责加载 JVM 扩展库中的类)
  • ApplicationClassLoader(负责加载用户提供的第三方库 / 用户项目代码中的类)

上述三个类加载器, 存在"父子关系"

此处所说的父子关系并不是父类子类

(可以简单理解为 Parent 属性)

00a6049f00744224ab52a513f818573d.png


上述类加载器如何配合工作🍭


  1. 从 ApplicationClassLoader 开始加载一个类
  2. ApplicationClassLoader 会将加载任务, 交给其父(ExtensionClassLoader), 让其父去执行
  3. ExtensionClassLoader 会将加载任务, 交给其父(BootstrapClassLoader), 让其父去执行
  4. BootstrapClassLoader 会将加载任务, 交给其父(null), 让其父去执行
    但 BootstrapClassLoader 的父亲是 null, 于是自行加载(自己动手丰衣足食)
    此时 BootstrapClassLoader 就会搜索标准库目录相关的类
    如果找到需要加载的类, 就会去进行加载
    如果未找到需要加载的类, 就由其子类加载器进行加载
  5. ExtensionClassLoader 加载 BootstrapClassLoader 未能加载的 JVM 扩展库中的类
    此时 ExtensionClassLoader 就会搜索 JVM 扩展库目录相关的类
    如果找到需要加载的类, 就会去进行加载
    如果未找到需要加载的类, 就由其子类加载器进行加载
  6. ExtensionClassLoader 加载 BootstrapClassLoader 未能加载的用户提供的第三方库 / 用户项目代码中的类
    此时 ExtensionClassLoader 就会搜索用户提供的第三方库 / 用户项目代码目录中相关的类
    如果找到需要加载的类, 就会去进行加载
    如果未找到需要加载的类, 就会抛出ClassNotFoundException(未找到指定类异常)
  • 加载器自行加载的情况
  • 类加载器没有父
  • 类加载器的父加载完毕后仍未找到所需加载的类

为什么双亲委派模型的执行顺序是这样的?

上述过程是一个递归的过程(保证了 BootstrapClassLoader 最先执行), 避免因用户创建一些奇怪的类从而引起的 Bug

假设用户在代码中创建了一个系统已存在的类

根据上述的加载流程, 此时 JVM 会先加载标准库中的类, 而不是用户自己代码中的类

这样避免了因为类相同从而可能引起 JVM 标准库中的类出现混乱

c34ffb9dc9e74d13996b6655386c3f44.png

破坏双亲委派模型


自己写的类加载器可以遵守上述的执行过程, 也可以不遵守上述的执行过程

看实际的需求

🔎GC(垃圾回收机制)


垃圾

不再使用的内存

垃圾回收

将不用的内存进行释放

如果内存一直占用, 不去释放, 就会导致剩余的空间越来越少, 从而导致后续申请内存失败

  • 对于进程, 这种情况可能会随着进程的结束从而将内存恢复
  • 对于服务器(7 * 24 运行), 这种情况就是致命的

由此, Java 中引入了 GC, 帮助我们自动进行释放"垃圾"

  • GC 的优点: 省心, 能够自动将不用的内存释放
  • GC 的缺点: 消耗额外的系统资源, 额外的性能开销(STW 问题)

STW(Stop The World)


假设内存中的垃圾很多, 此时触发一次 GC 操作

其开销可能非常大, 大到可能将系统资源耗光

另一方面, GC 回收垃圾时, 可能会涉及一些锁操作, 导致业务代码无法正常运行

这样的卡顿, 极端情况下可能是几十毫秒甚至上百毫秒

注意

Scanner sc = new Scanner(System.in);

sc.close();

类似于这种释放的是文件资源, 并非内存

GC的回收单位

0c864f765e414ec8b3706b6a1dd3635e.png


  • JVM 中存在的内存区域
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器
  • 元数据

GC 主要是针对堆进行内存释放的

这是因为堆上的对象存活时间相对较长

而栈上的对象会随着方法的结束而结束

  • 将内存空间大致划分为3类
  • 正在使用的内存
  • 不用的内存(但未回收)
  • 未分配的内存

cfda715b30de4f2baab76a0c76e66ecc.png

  • GC 回收是以"对象"为基本单位, 并非字节
  • GC 回收的是整个对象(整个对象不再使用时回收), 并非一部分使用, 一部分不使用
    (一个对象可能有多个属性 ,其中一部分属性需要使用, 一部分属性用过之后不再进行使用, GC 进行回收是当整个对象不再使用时, 即该对象中所有属性不再使用)

GC的实际工作过程


  • GC 的实际工作过程可以划分为2步
  1. 寻找垃圾(找到不再使用的内存)
  2. 回收垃圾(将不再使用的内存进行释放)

寻找垃圾🍭

  • 寻找垃圾有2种方法
  1. 引用计数(python / php)
  2. 可达性分析(Java)

引用计数🍂

为每个对象分配一个计数器

创建一个指向该对象的引用时, 该对象的计数器 + 1

销毁一个指向该对象的引用时, 该对象的计数器 - 1

举个栗子🌰

Test t1 = new Test();// Test 对象引用计数 + 1
Test t2 = new Test();// Test 对象引用计数 + 1
Test t3 = new Test();// Test 对象引用计数 + 1
t1 = null;// Test 对象引用计数 - 1 
  • 引用计数的不足
  • 内存空间浪费
  • 循环引用

内存空间浪费

  • 引用计数需要为每个对象分配一个计数器
  • 当代码中的对象较少时, 空间浪费率较低
  • 当代码中的对象较多时, 空间浪费率较高
  • 当每个对象的体积(占用的内存空间)较小时, 此时分配的计数器所占空间会较为突出
    (假设计数器所占内存空间为4字节, 当对象的体积为4字节时, 此时所消耗的额外空间相当于一个对象的体积)

循环引用

分析如下伪代码

public class Node {
  Node next = null;
}
Node a = new Node();// 1号对象, 引用计数为1
Node b = new Node();// 2号对象, 引用计数为1 
a.next = b;// 2号对象, 引用计数为2(a.next 指向 b)
b.next = a;// 1号对象, 引用计数为2(b.next 指向 a)

91916f28a0a24a63aafb8ea2e95ca817.png

此时将 a 和 b 进行销毁

a = null;// 1号对象, 引用计数为1(2 - 1 = 1)
b = null;// 2号对象, 引用计数为1(2 - 1 = 1)

此时1号对象和2号对象的引用计数为1, 表示无法释放内存

(引用计数为0时, 释放内存)

但此刻1号对象与2号对象却无法被访问(循环引用)

相关文章
|
1月前
|
Java
jvm复习,深入理解java虚拟机一:运行时数据区域
这篇文章深入探讨了Java虚拟机的运行时数据区域,包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、元空间和运行时常量池,并讨论了它们的作用、特点以及与垃圾回收的关系。
62 19
jvm复习,深入理解java虚拟机一:运行时数据区域
|
1月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
31 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
22天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
1月前
|
存储 算法 Java
深入理解Java虚拟机(JVM)及其优化策略
【10月更文挑战第10天】深入理解Java虚拟机(JVM)及其优化策略
41 1
|
1月前
|
安全 Java API
🌟探索Java宇宙:深入理解Java技术体系与JVM的奥秘
本文深入探讨了Java技术体系的全貌,从Java语言的概述到其优点,再到Java技术体系的构成,以及JVM的角色。旨在帮助Java开发者全面了解Java生态,提升对Java技术的认知,从而在编程实践中更好地发挥Java的优势。关键词:Java, JVM, 技术体系, 编程语言, 跨平台, 内存管理。
35 2
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
41 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
1月前
|
监控 Java
Java的JVM如何优化?
Java的JVM如何优化?
56 3
|
2月前
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
2月前
|
安全 前端开发 Java
浅析JVM invokedynamic指令与Java Lambda语法的深度融合
在Java的演进历程中,Lambda表达式无疑是Java 8引入的一项革命性特性,它极大地简化了函数式编程在Java中的应用,使得代码更加简洁、易于阅读和维护。而这一切的背后,JVM的invokedynamic指令功不可没。本文将深入探讨invokedynamic指令的工作原理及其与Java Lambda语法的紧密联系,带您领略这一技术背后的奥秘。
28 1
|
4月前
|
存储 Java 程序员
Java面试题:方法区在JVM中存储什么内容?它与堆内存有何不同?
Java面试题:方法区在JVM中存储什么内容?它与堆内存有何不同?
70 10