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

相关文章
|
2天前
|
存储 Java C++
Java虚拟机(JVM)在执行Java程序时,会将其管理的内存划分为几个不同的区域
【6月更文挑战第24天】Java JVM管理内存分7区:程序计数器记录线程执行位置;虚拟机栈处理方法调用,每个线程有独立栈;本地方法栈服务native方法;Java堆存储所有对象实例,垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息;运行时常量池存储常量;直接内存不属于JVM规范,通过`java.nio`手动管理,不受GC直接影响。
14 5
|
2天前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
10 2
|
6天前
|
监控 算法 Java
Java虚拟机(JVM)使用多种垃圾回收算法来管理内存,以确保程序运行时不会因为内存不足而崩溃。
【6月更文挑战第20天】Java JVM运用多种GC算法,如标记-清除、复制、标记-压缩、分代收集、增量收集、并行收集和并发标记,以自动化内存管理,防止因内存耗尽导致的程序崩溃。这些算法各有优劣,适应不同的性能和资源需求。垃圾回收旨在避免手动内存管理,简化编程。当遇到内存泄漏,可以借助VisualVM、JConsole或MAT等工具监测内存、生成堆转储,分析引用链并定位泄漏源,从而解决问题。
17 4
|
4天前
|
存储 监控 Java
JVM:Java虚拟机探秘
JVM:Java虚拟机探秘
8 1
|
8天前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
34 3
|
20小时前
|
存储 Java 机器人
Java中的字节码与JVM指令集详解
Java中的字节码与JVM指令集详解
|
13天前
|
Java 编译器 开发者
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(二)
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(二)
11 0
|
13天前
|
存储 Java 索引
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(一)
Java基础3-JVM层面理解Java继承、封装、多态的实现原理(一)
15 0
|
22小时前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
6 1
|
20小时前
|
缓存 监控 安全
深入理解Java中的线程池和并发编程
深入理解Java中的线程池和并发编程