【JVM】深入解析Java虚拟机

目录

[1. 区分JDK,JRE 和 JVM](#1. 区分JDK,JRE 和 JVM)

[1.1 JVM](#1.1 JVM)

[1.2 JRE](#1.2 JRE)

[1.3 JDK](#1.3 JDK)

[1.4 关系总结](#1.4 关系总结)

[2. 跨平台性](#2. 跨平台性)

[3. JVM中的内存划分](#3. JVM中的内存划分)

[4. JVM的类加载机制](#4. JVM的类加载机制)

[5. 双亲委派模型](#5. 双亲委派模型)

[6. 垃圾回收机制(GC)](#6. 垃圾回收机制(GC))

[6.1 识别垃圾](#6.1 识别垃圾)

[6.1.1 单个引用](#6.1.1 单个引用)

[6.1.2 多个引用](#6.1.2 多个引用)

[6.2 释放垃圾](#6.2 释放垃圾)

[6.2.1 标记---释放](#6.2.1 标记—释放)

[6.2.2 复制算法](#6.2.2 复制算法)

[6.2.3 标记---整理](#6.2.3 标记—整理)

[6.2.4 分代回收](#6.2.4 分代回收)


1. 区分JDK,JRE 和 JVM

1.1 JVM

JVM是 Java 程序运行的核心,负责执行 Java 字节码(.class文件),使其能在不同操作系统上运行。

特点:

  • 可以将.class文件编译为机器码执行(跨平台能力)
  • 自动垃圾回收机制(GC),管理堆、栈、方法区等内存区域。
  • 不包含开发工具或核心类库,仅负责运行编译后的程序。

1.2 JRE

JRE 是 Java 程序运行的最小环境,包含 JVM 和运行所需的核心类库(如 java.lang、java.util、java.io)。

特点:

  • 仅支持运行已编译的 Java 程序(.jar 或 .class 文件)。
  • 不包含编译器(javac)或开发工具,无法用于开发。

1.3 JDK

JDK是Java开发的完整工具包,包含JRE和一些开发工具

特点:

  • 支持编写、编译、调试和运行 Java 程序(.java → .class → 执行)。
  • 提供开发工具如 javac(编译器,可以将 .java文件编译为 .class 文件),javadoc(文档生成),jbd(调试器)等。

1.4 关系总结

  • JDK = JRE + 开发工具
  • JRE = JVM + 核心类库
  • JVM 是执行引擎,依赖 JRE 提供的类库运行程序
  • JRE是Java程序运行的最小环境配置

2. 跨平台性

JVM类似一个翻译官,是实现跨平台性的关键

java文件先通过 javac 编译为 .class 文件,JVM又会将 .class文件转换为cpu可以识别的机器指令

因此,发布一个java程序,本质上发布 .class文件即可,JVM拿到 .class文件,就会自动进行转换

  • windows上的JVM,就会把 .class文件转换为 windows 操作系统可以识别的机器指令
  • Linux 上的JVM,就会把 .class文件转换为 Linux 操作系统可以识别的机器指令
  • 不同操作系统上面的JVM是不同的

3. JVM中的内存划分

JVM在运行的过程中,相当于一个进程,进程在运行的过程中,需要从操作系统中申请一块空间(内存资源,CPU资源等),这些内存空间,支撑了后续java程序的执行,比如定义变量需要内存空间,这里获取的内存空间,并不是直接给操作系统要,而是JVM给的

JVM从操作系统中申请一大块内存空间,给java程序使用,这一大块内存空间又会根据实际的使用用途划分出不同的空间出来,这样的好处就是便于管理和提供调用效率

JVM将申请到的空间,进行区域划分,不同的区域具有不同的作用

  • 代码中new出来的对象,都存放在堆中
  • 堆也是垃圾回收的主要区域

虚拟机栈

  • 虚拟机栈的生命周期和线程相同,线程启动时创建,线程结束时销毁
  • 负责管理Java方法的调用和执行
  • 由栈帧组成,每个方法调用对应一个栈帧
  • 栈帧中存储局部变量表、操作数 、动态链接、方法返回地址等信息。

栈帧

  • 局部变量表:存放方法参数和局部变量。
  • 操作数栈:执行方法时的工作区(先进后出)
  • 动态链接:指向运⾏时常量池的方法引用(在方法中调用另一个方法)
  • 方法返回地址:PC寄存器的地址

本地方法栈

和虚拟机栈的功能类似,只不过java虚拟机站是给JVM使用,本地方法栈是给本地方法使用。(常见的本地接口JNI调用非java代码)

程序计数器

只会占用较小的空间,用于存储下一条要执行的java指令的地址

元数据区

元数据一般指的是一些辅助性质的数据

元数据区:存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。

一个程序有哪些类,每个类中有哪些方法,每个方法包含哪些指令都会被记录在元数据区中


区分一个变量在那个内存区域中,主要根据变量的类型

  • 成员变量------存储在堆中
  • 静态成员变量------存储在元数据区中
  • 局部变量------存储在虚拟机栈中
  • 方法------存储在元数据区
  • 方法在运行时------存储在虚拟机栈中

4. JVM的类加载机制

类加载:java进程在运行的时候,需要把 .class 文件从硬盘,读取到内存中,并进行一系列的校验解析,最终转换成可执行代码的过程的过程

类加载的过程大体分为5个步骤,加载、验证、准备、解析、初始化五个阶段

1)加载

  • 将硬盘上的 .class文件找到并打开,读取文件中的内容(二进制字节流)
  • 将字节流表示的静态数据结构转为方法区中的运行数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区中访问这个类中各种数据的入⼝。

2)验证

保证读取到的数据是合法的,符合虚拟机的约束要求,确保这些数据被运行后不回危害虚拟机自身的安全

3)准备

给读取到的类对象(static变量),分配内存空间,这时候内存空间中,默认值都为0或null

4)解析

主要针对类中的字符串常量进行处理,主要是将符号引用转换为直接引用

  • 在硬盘存储中,并没有地址这个概念,虽然没有地址这个概念,但是存在类似地址"偏移量"的概念,也可以表示数据在文件中的具体位置
  • 当数据被加载到内存中,此时就会分配地址,这时候会从符号引用变为直接引用

5)初始化

Java虚拟机真正执行类中编写的Java程序代码,完成后续的类对象初始化(触发父类的加载,初始化静态成员,执行静态代码块等)

5. 双亲委派模型

双亲委派模型是Java类加载机制中的一种重要设计原则,它定义了类加载器如何协作加载类的规则。

在Java中,存在一个模块专门执行类加载操作,称为"类加载器",主要负责将.class文件加载到内存中,并转换为可执行的java.lang.Class类的实例

JVM中的类加载器默认存在三个,也可自定义一个

  • 启动类加载器(Bootstrap ClassLoader):负责查找标准库的目录

  • 扩展类加载器(Extension ClassLoader): 负责查找扩展库的目录

  • 应用程序类加载器(Application/System ClassLoader):负责加载用户编写的程序代码和查找第三方库的目录

这三个类加载器,存在类似二叉树的父子关系(不是父子继承关系)


双亲委派模型的工作流程:

  1. 从应用程序类加载器作为入口,开始工作,先加载用户编写的程序代码,但是不会立即搜索自己负责的目录,会把搜索任务交给父亲(扩展类加载器)
  2. 代码进入扩展类加载器的范畴,但是也不会立即开始搜索任务,而是将自己的搜索任务交给父亲(启动类加载器)
  3. 代码进入启动类加载器的范畴,但是也不会立即开始搜索工作,而是将自己的搜索任务交给父亲
  4. 但是启动类加载器不存在父亲,那么就会真正的开始搜索工作,尝试在标准库中查找符合的.class文件,如果找到了,就进入后续的验证,解析等流程中,如何没有找到,就会回到孩子(扩展类加载器)中继续尝试加载
  5. 扩展类加载器收到父类返回的任务后,开始在扩展类中查找,如果找到了,就进入后续的验证,解析等流程中,如何没有找到,就会回到孩子(应用程序类加载器)中继续尝试加载
  6. 应用程序类加载器收到父类返回的任务,开始在第三方库中查找,如果找到了进入后续的流程中,如果没有找到,会继续到孩子(自定义类加载器)中继续进行加载,但是默认不存在孩子(自定义类加载器),这时候类加载过程就会失败,抛出ClassNotFoundException异常

双亲委派模型的优点:

  • 避免重复加载类,这样严格规定查找的范围,避免重复加载类
  • 安全性,使⽤双亲委派模型也可以保证了Java的核心API不被篡改,如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现⼀些问题(如果多个类加载器独立加载同一个类,会出现类冲突)

上述的规则是JVM中默认类加载器,遵守的规则,这样的规则,其实也可以被打破

如果我们使用自定义类加载器,指定某个目录进行查找并加载,自定义类加载器不和系统再带的类加载器进行关联(不调用默认类加载器的引用),那么自定义加载器就是独立的,不会涉及双亲委派算法

6. 垃圾回收机制(GC)

定义一个变量会使用一块内存空间,如果这个变量长时间用不到又不及时释放掉,那么内存空间会为占据,导致后面想申请内存申请不到,还有可能导致内存泄漏问题,所以垃圾回收本质上是回收内存空间,更准确的是回收对象

在JVM中存在多个区域,有些区域不需要垃圾回收,对于程序计数器、虚拟机栈、本地方法栈这三部分区域, 其⽣命周期与相关线程有关,线程销毁,会自动销毁,这里垃圾回收的主要区域是方法区和堆

6.1 识别垃圾

判断哪些对象后续还需要继续使用,那些不需要继续使用

6.1.1 单个引用

对象的使用一定伴随着引用,如果一个对象没有任何引用指向他,那么就可以视为垃圾。

匿名对象被使用完后应该自动销毁,也应该被视为垃圾。

6.1.2 多个引用

如果存在多个引用指向同一个对象,必须确保每一个对象的引用都被销毁了,才能将这个对象视为垃圾。

1)引用计数器

原理:

给每个对象再分配一个额外的空间,空间里面存储这个对象存在几个引用,可以设置一个专门的扫描线程,去获取每个对象的引用计数情况,如果发现对象的引用为0,表示这个对象可以被释放。

问题:

  • 每个对象分配额外的空间,即使每个对象安排的计数器占很小的内存,如果程序中的对象数目很多,总消耗的空间也会很多。
  • 如果出现循环引用问题,会导致引用计数器无法工作。

2)可达性分析

可达性分析会消耗更多的时间,但是不会产生循环引用这样的问题。

如果出现:6.right = null ,则会导致7不可达,7不可达也会导致8不可达。

原理:

  • 从一些特别的变量作为起点进行遍历,沿着这些变量所持有的引用。逐层往下访问,能被访问到的对象就不是垃圾,如果访问不到就是垃圾。
  • JVM中存在扫描线程会不断的对代码中已有的变量进行遍历,尽可能地在一定时间内访问到更多的对象。

特殊的变量:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象

6.2 释放垃圾

6.2.1 标记---释放

在遍历的过程中检测到标志为垃圾的对象,直接释放掉,但是一般不采用这种方法,会出现内存空间碎片化问题。

6.2.2 复制算法

将标志为垃圾的对象不直接释放掉,而是将不是垃圾的对象复制到内存的另一半里,然后将垃圾的那一半空间整体释放掉。

特点:

  • 可以解决内存碎片化问题。
  • 但是可用的内存空间变少了。
  • 如果复制的对象比较多,会导致复制的开销会很大。

6.2.3 标记---整理

类似于顺序表。删除掉标志为垃圾的对象,将正在使用的对象进行搬运,集中放在一端。

特点:

  • 可以解决内存碎片化问题。
  • 不会浪费太多的内存空间。
  • 如果存活的对象很多,搬运的开销会很大。

6.2.4 分代回收

根据不同种类的对象,采用不同的方式

  • JVM中引入了对象年龄的概念并存在专门的线程负责周期性的扫描
  • 如果一个对象被扫描的一次并可达,那么年龄就加1(初始年龄都为0)
  • JVM会根据年龄的差异将整个堆内存分为两个部分:新生代和老年代

1)当代码中new出一个新的对象,这个对象会先存储在伊甸区,创建出的新对象大部分都活不过第一轮GC,生命周期非常短。

  1. 第一轮GC扫描完成后,伊甸区中少数的对象会通过复制算法拷贝到生活区中,后续gc的扫描线程会持续进行扫描,生存区中的大部分对象也会在扫描中被标记为垃圾,少数存活的会继续使用复制算法拷贝到另一个生活区中,只要这个对象能在生活区中继续存活,就会被复制算法来回拷贝到另一半的生活区中,每经历一次GC扫描,对象的年龄都会加一

3)如果这个对象在生活区中经历了很多次GC仍然存活,JVM就会认为这个对象的生命周期会很长,然后通过复制算法从生活区拷贝到老年代。

4)老年代中的对象也会被GC扫描。但是扫描的频率会大大的降低。

5)老年代中的对象,如果被GC标记为垃圾,就会按照整理的方式释放内存。

相关推荐
YueiL8 分钟前
Linux文件系统基石:透彻理解inode及其核心作用
linux·网络·数据库
神即道 道法自然 如来29 分钟前
如何在linux(CentOS7)上面安装 jenkins?
linux·运维·jenkins
花小璇学linux2 小时前
imx6ull-驱动开发篇16——信号量与互斥体
linux·驱动开发·嵌入式软件
苹果醋32 小时前
React Native jpush-react-native极光推送 iOS生产环境接收不到推送
java·运维·spring boot·mysql·nginx
老华带你飞2 小时前
数码论坛|基于SprinBoot+vue的数码论坛系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·数码论坛系统
程序猿七度3 小时前
【FastExcel】解决ReadSheet在Map中获取对象不准确问题(已提交PR并合并到开源社区)
java·开源·fastexcel
Johny_Zhao3 小时前
Rsync + Sersync 实时数据同步方案
linux·网络安全·信息安全·云计算·rsync·系统运维·sersync
花小璇学linux4 小时前
imx6ull-驱动开发篇15——linux自旋锁
linux·驱动开发·嵌入式软件
zhangxiaomm4 小时前
Ubuntu 搭建 yolov5
linux·yolo·ubuntu