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号对象却无法被访问(循环引用)

相关文章
|
3月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
292 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
4月前
|
存储 运维 Kubernetes
Java启动参数JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError"
本文介绍了Java虚拟机(JVM)常用启动参数配置,包括设置初始堆内存(-Xms512m)、最大堆内存(-Xmx1024m)及内存溢出时生成堆转储文件(-XX:+HeapDumpOnOutOfMemoryError),用于性能调优与故障排查。
422 0
|
6月前
|
存储 监控 算法
Java程序员必学:JVM架构完全解读
Java 虚拟机(JVM)是 Java 编程的核心,深入理解其架构对开发者意义重大。本文详细解读 JVM 架构,涵盖类加载器子系统、运行时数据区等核心组件,剖析类加载机制,包括加载阶段、双亲委派模型等内容。阐述内存管理原理,介绍垃圾回收算法与常见回收器,并结合案例讲解调优策略。还分享 JVM 性能瓶颈识别与调优方法,分析 Java 语言特性对性能的影响,给出数据结构选择、I/O 操作及并发同步处理的优化技巧,同时探讨 JVM 安全模型与错误处理机制,助力开发者提升编程能力与程序性能。
Java程序员必学:JVM架构完全解读
|
7月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
974 4
|
8月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
380 29
JVM简介—1.Java内存区域
|
10月前
|
存储 监控 算法
Java JVM 面试题
Java JVM(虚拟机)相关基础面试题
261 4
|
11月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
11月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
11月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
295 27
|
12月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
271 28