认识JVM

news2025/1/19 17:10:20

✏️作者:银河罐头
📋系列专栏:JavaEE

🌲“种一棵树最好的时间是十年前,其次是现在”

目录

  • JVM 内存区域划分
    • 程序计数器
    • 元数据区
  • JVM 类加载机制
    • 加载
    • 验证
    • 准备
    • 解析
    • 初始化
    • 双亲委派模型
  • JVM 垃圾回收机制
    • GC 实际工作过程
      • 找到垃圾
      • 清理垃圾

推荐一本书:《深入理解 Java 虚拟机》。

JVM(Java Virtual Machine)内存区域划分,JVM 类加载机制,JVM 垃圾回收机制。

HotSpot VM : 最主流的 JVM,Oracle 官方 jdk 和开源的 openjdk ,都是使用这个 JVM。占据绝大部分市场份额。

JVM 内存区域划分

举个栗子:买房子,假设买了一套 100 平米的房子,区域划分,主卧,次卧,客厅,阳台,厨房,卫生间,浴室…

JVM 启动的时候,也会申请到一块很大的内存区域,JVM 是一个应用程序,要从操作系统申请内存。JVM 就会根据内存需要,把空间划分成几个部分,每个部分有各自的功能和作用。

image-20230413143637699

此处所说的 “栈”,是 JVM 中的一块特定空间。

对于 JVM 虚拟机栈,这里存储的是 方法之间的调用关系。

整个虚拟机栈空间内部,可以认为是包含很多个元素,每个元素是一个 “栈帧”,每一个栈帧,包含方法的入口地址,方法的参数,返回地址,局部变量…

对于本地方法栈,这里存储的是 native 方法之间的调用关系。

image-20230413152752308

由于函数调用,也是有"后进先出"的特点,此处这里的 “栈”,也是"后进先出"的。

数据结构中的"栈",是一个通用的,更广泛的概念,而此处的"栈"特指 JVM 上的一块内存空间。

调用一个方法,就会创建栈帧,方法执行结束了,栈帧就销毁了。

栈空间有上限, JVM 启动的时候是可以设置参数的,其中有一个参数就可以设置占空间的大小。

这里的栈不是只有一个。每个线程有一个,jconsole, 查看 Java 进程内部情况,就可以看到所有的线程,点击线程就可以看到该线程调用栈的情况。

程序计数器

记录当前线程执行到哪个指令了(很小的一块,存一个地址), 每个线程有一份。

整个 JVM 空间中最大的区域。

new 出来的对象,都在堆上,类的成员变量也是在堆上。

堆,是一个进程有一份。

栈,是一个线程有一份。一个进程,有 N 份。

每个 Java 虚拟机(JVM)就是一个进程。

元数据区

Java8 之前叫做方法区。

类对象,常量池,静态成员,都在这里。

一个进程,一块元数据区。

针对内存区域划分:

考点:给你一段代码,问你某个变量在哪个区域上?

局部变量:栈

普通成员变量:堆

静态成员变量:元数据区/方法区

JVM 类加载机制

类加载,就是 .class 从文件(硬盘)被加载到内存(元数据区)中这样的过程。

.java 通过 javac , 得到 .class 文件

image-20230413164938537

加载

加载:把 .class 文件找到,打开文件,读文件,把文件读到内存中。

注意这只是类加载机制的一小步,最终加载完成是要得到类对象。

验证

检查下 .class 文件格式是否正确。(不正确就加载失败)

.class 是一个二进制文件,这里的格式有严格说明,官方提供了 JVM 虚拟机规范,文档上详细描述了 .class 文件的格式。

Java 官方文档:https://docs.oracle.com/javase/specs/index.html

image-20230413170113543

image-20230413170924609

准备

给类对象分配内存空间(在元数据区占个位置),静态变量被初始化为0

解析

初始化字符串常量,把符号引用转为直接引用。

字符串常量,得有一块内存空间,存字符的实际内容。还要有一个引用,来保存这个内存空间的地址。

在类加载之前,字符串常量此时是处在 .class 文件中的,此时这个"引用"记录的并非是 字符串真正的地址,而是它在文件中的"偏移量"(或占位符)。类加载完成之后,字符串常量被加载到内存中,此时才有"内存地址",这个引用才能真正被赋值为"内存地址"。

初始化

针对类对象里的内容进行初始化,执行静态代码块,加载父类…

image-20230413213009024

一个类,啥时候会被加载呢?

不是 Java 程序一运行就把所有的类都加载了,真正要用到才加载,不用就不加载(“懒汉模式”)。

什么情况才算是"用到"?

1.构造类的实例

2.调用类的静态方法/使用静态属性

3.加载子类,就会先加载其父类

一旦加载过了,就不用再重复加载了。

双亲委派模型

加载阶段,要找到 .class 文件,具体去哪里找?双亲委派模型描述的是这个问题。

JVM 默认提供了 3 个类加载器:

1.BootstrapClassLoader : 负责加载标准库中的类( Java 要求提供哪些类) ,不管是哪一种 JVM 的实现都会提供这些一样的类。

2.ExtensionClassLoader : 负责加载 JVM 扩展库中的类(规范之外,由 实现 JVM 的厂商/组织提供的额外的功能)

3.ApplicationClassLoader : 负责加载用户提供的第三方库/用户项目代码中的类

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

BootstrapClassLoader <- ExtensionClassLoader <- ApplicationClassLoader

每个 ClassLoader 都有一个 parent 属性,指向自己的父 类加载器

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

首先加载一个类,是从 ApplicationClassLoader 开始,

但是 ApplicationClassLoader 会把加载任务交给父亲 ExtensionClassLoader 去执行;

于是 ExtensionClassLoader 要去加载了,但是也不是真加载,而是再委托给自己的父亲;

BootstrapClassLoader 要去加载了,也是想委托给自己的父亲,结果发现自己的父亲是 null,

没有父亲/父亲加载完了,才由自己进行加载。

此时 BootstrapClassLoader 就会搜索自己负责的 标准库目录相关的类,如果找到就加载,没找到就继续由子类加载器加载;

ExtensionClassLoader 真正搜索 扩展库相关的目录,如果找到就加载,没找到就继续由子类加载器加载;

ApplicationClassLoader 真正搜索用户项目相关的目录,如果找到就加载,没找到就继续由子类加载器加载(目前没有子类了,只能抛出"类找不到"这样的异常)。

这个顺序就是为了保证 BootstrapClassLoader 能够先加载,ApplicationClassLoader 后加载。

1.这就可以避免因为用户创建了一些奇怪的类而引起的 bug.

比如,如果用户写了个 java.lang.String 这个类,按照现在这个加载流程,就会先加载标准库的类,而不会加载用户写的这个类。

这样就能保证,即使出现上述问题,也不会让 jvm 已有代码出现混乱,顶多就是用户自己写的类不生效。

另一方面,类加载器是可以用户自定义的,上述 3 个类加载器是 JVM 自带的。

2.用户自定义的类加载器,可以加入到上述流程中,和现有的类加载器配合使用。

类加载,主要是围绕 3 个面试题展开的:

1.类加载的流程

2.类加载的时机

3.双亲委派模型

站在 JVM 的角度,上述 3 个东西都不是类加载的核心,真正的核心应该是 解析.class 文件,解析每个字节是干啥的(验证,准备,解析,初始化)

JVM 垃圾回收机制

垃圾回收机制 GC

啥是垃圾,就是不再使用的内存。

垃圾回收,就是把不用的内存帮我们自动释放了。

GC可以避免内存泄漏问题。

GC 好处:省心,使写代码变得简单,不容易出错。

GC 坏处:需要消耗额外的系统资源,也有额外的性能开销。GC 还有一个 STW(stop the world)问题。

STW:1.如果内存里的垃圾已经很多了,触发一次 GC,开销可能非常大,把系统资源消耗非常多。2.GC可能会涉及到一些锁操作,导致业务代码无法正常执行。这样的卡顿,极端情况下可能是几十ms甚至是上百ms.

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

GC ,是以"对象"为单位进行回收的。

image-20230414144010910

GC 实际工作过程

找到垃圾

关键思路,看这个对象有没有"引用"指向它。

如果一个对象有引用指向他,就有可能会被用到;如果没有引用指向它,就不会再被用到了。

  • 具体如何知道,一个对象是否有引用指向它呢?

两种典型实现:

1.引用计数[不是 Java 的做法,python/php]

给每个对象分配了一个计数器(整数)

每次创建一个引用指向该对象,计数器 + 1;每次引用销毁,计数器 - 1

image-20230414193224044

这个方法简单有效,但 Java 没有使用,原因 :1.内存空间浪费的多,每个对象都要分配一个计数器,如果代码里的对象非常多,占用的额外空间就会很多,尤其是每个对象非常小的情况下。2.存在循环引用的问题。

image-20230414194430865

Python/PHP 使用引用计数,需要搭配其他的机制,来避免循环引用的问题。

2.可达性分析[ Java 的做法]

Java 里的对象,都是通过引用来指向并访问的,经常有一个引用指向一个对象,这个对象的成员又指向另一个对象。

class TreeNode{
    int value;
    TreeNode left;
    TreeNode right;
}
TreeNode root = new TreeNode();
root.left = 

整个 Java 里所有的对象,就通过链式/树形结构,整体给串起来。

这些对象被组织的结构视为树。可达性分析,就是从树根节点出发,遍历树,所有能被访问到的对象,标记为"可达",不能访问到的,就是"不可达",GC把"不可达"的作为垃圾回收了。

可达性分析,需要进行类似于"树遍历"这样的操作,相比于引用计数来说,肯定是要慢一些的。但是速度慢,没关系,可达性分析遍历操作,并不需要一直执行,隔一段时间分析一遍就可以了。

进行可达性分析,遍历的起点,称为 GCroots.

GCroots,有以下几种:

1.栈上的局部变量

2.常量池中的对象

3.静态成员变量

一个代码中有很多个这样的起点,把每个起点都往下遍历一遍,就完成了一次扫描过程。

清理垃圾

主要是 3 种基本做法:

1.标记清除

image-20230414204429909

2.复制算法

解决了内存碎片问题。

image-20230414205353146

每次触发算法, 都是向另外一侧进行复制。

缺点:1.空间利用率低 2.如果垃圾少,有效对象多,复制成本就比较大。

3.标记整理

解决复制算法的缺点。

标记整理,就是类似于顺序表删除中间元素,会有元素搬运的操作。保证了空间利用率,也解决了内存碎片的问题。

这种做法效率也不高,如果要搬运的空间比较大,开销也会比较大。

上述做法并不完美。基于上述这些基本策略,搞了一个复合策略"分代回收"。

把垃圾回收分成不同的场景,有的场景用这个算法,有的场景用那个算法,扬长避短。

Java 中的对象,要么就是生命周期特别长,要么就是特别短。根据生命周期的长短,分别使用不同的算法。给对象引入一个概念,年龄(熬过 GC 的轮次)。年龄越大,这个对象存在的时间就越久。

经过这一轮可达性分析的遍历,发现这个对象还不是垃圾,这就是"熬过一轮 GC"。

image-20230414212031387

新生代:刚 new 出来的,年龄是 0 的对象,放到伊甸区。熬过一轮 GC ,就要放到幸存区。

Java 对象一般都是朝生夕死,生命周期非常短。所以幸存区够放。

伊甸区 -> 幸存区 通过"复制算法"。

到幸存区之后,也要周期性的接受 GC 的考验。如果变成垃圾,就被释放掉;如果不是垃圾就被放到另一个幸存区。这 2 个幸存区同一时刻只用一个。在这两者之间来回拷贝(复制算法)。

如果这个对象在幸存区已经来回拷贝很多次了,这个时候就要进入老年代了。

老年代都是年龄大的对象,生命周期更长,针对老年代,也要周期性的进行 GC 扫描,但是频率更低了。

如果老年代的对象是垃圾了,就使用标记整理的方式进行释放。

上述介绍的是 GC 典型的垃圾回收算法。

如何确定垃圾+如何清理垃圾 这些介绍的都是策略,

实际 JVM 实现的时候会有一定差异,JVM 有很多 垃圾回收实现,称为"垃圾回收器"。

垃圾回收器的具体实现,会围绕上述算法思想展开,会有一些变化。

不同的 垃圾回收器的侧重点不同,有的追求扫的快,有的追求扫的好,有的追求对用户的打扰少(STW 尽量短)

垃圾回收器举例:CMS, G1, ZGC

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/427868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础之哈希表与红黑树

文章目录一、哈希表1.1 JDK1.7版本之前哈希表&#xff08;数组链表&#xff0c;头插法&#xff09;1.2 JDK1.8版本之后哈希表&#xff08;数组链表红黑树&#xff0c;尾插法&#xff09;二、红黑树2.1 使红黑树再次满足红黑规则2.1.1 使红黑树满足红黑规则方法一2.1.2 使红黑树…

JavaSE学习进阶day03_01 多态

第一章 多态 1.1 多态的形式 直接说什么是多态性太抽象了&#xff0c;我们先引入一个例子&#xff1a; 现在我定义了一个feed方法&#xff0c;在不同的类的对象调用这个方法时&#xff0c;都要改变形参&#xff0c;即每当我的对象不同时&#xff0c;都要重载该方法&#xff0…

【Java基础】day13

day13 一、Spring Bean 生命周期是怎样的&#xff1f; 详细过程分为以下几个步骤&#xff1a; ① 初始化 Bean 容器通过获取 BeanDefinition 中的信息进行实例化&#xff0c;这一步仅仅是简单的实例化&#xff0c;并没有进行依赖注入。 实例化的对象被包装在 BeanWrapper 对…

Qt音视频开发38-ffmpeg视频暂停录制的设计

一、前言 基本上各种播放器提供的录制视频接口&#xff0c;都是只有开始录制和结束录制两个&#xff0c;当然一般用的最多的也是这两个接口&#xff0c;但是实际使用过程中&#xff0c;还有一种可能需要中途暂停录制&#xff0c;暂停以后再次继续录制&#xff0c;将中间部分视…

RabbitMq架构设计原理

文章目录1、消息中间件1.1、什么是消息中间件1.2、传统的HTTP请求有什么缺点1.3、MQ的应用场景2、同步、多线程、以及MQ处理业务逻辑的区别2.1、同步发送Http 请求2.2、多线程处理业务逻辑2.3、MQ实现业务逻辑Mq和多线程之间的区别3、Mq消息中间件名词4、简单实现Mq的思路4.1、…

MySQL索引15连问,你能坚持到第几问?

目录 1.索引是什么? 2.MySQL索引有哪些类型 3.索引什么时候会失效? 4.哪些场景不适合建立索引? 5.为什么要用 B树&#xff0c;为什么不用二叉树? 6.一次B树索引树查找过程 7.什么是回表? 如何减少回表? 8.什么是覆盖索引? 9.聊聊索引的最左前缀原则 10.索引下…

Phind——一款面向开发人员的AI搜索引擎

目录前言一、Phind优点二、使用方法总结前言 Phind是一款面向开发人员的AI搜索引擎&#xff0c;它由大语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;驱动 。相比于传统的搜索引擎&#xff0c;Phind有以下优势&#xff1a;自然语言搜索、面向开发者、AI…

【数据结构】期中考试一把梭(通宵版上)

前言 红中(Hong_zhong) CSDN内容合伙人、2023年新星计划web安全方向导师、 吉林师范大学网安大一的一名普通学生、摸鱼拿过大挑校二、 华为MindSpore截至目前最年轻的优秀开发者、IK&N战队队长、 阿里云专家博主、华为网络安全云享专家、腾讯云自媒体分享计划博主、 划了…

URL 和 HandlerMapping建立映射(11)

上一篇https://blog.csdn.net/chen_yao_kerr/article/details/130194864 我们已经分析了Spring MVC的配置&#xff0c;并且说明了如何通过注解的方式去替换各种各样的xml配置文件。本篇将更深入分析&#xff1a; 取代 springmvc.xml 配置 之前我们说过&#xff0c;定义一个类…

简述API(电商数据API)网关的概念和功能

API 网关 ( API gateway ) 前言 在 IOT &#xff08; 物联网 &#xff09;中&#xff0c;当我们的一些设备。例如&#xff08; 监控、传感器等 &#xff09;需要将收集到的数据和信息进行汇总时&#xff0c;我们就需要一个 API。&#xff08;如果你需要Taobao/JD/pinduoduo平台…

OpenAI-ChatGPT最新官方接口《语音智能转文本》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(六)(附源码)

Speech to text 语音智能转文本Introduction 导言Quickstart 快速开始Transcriptions 转录python代码cURL代码Translations 翻译python代码cURL代码Supported languages 支持的语言Longer inputs 长文件输入Prompting 提示其它资料下载Speech to text 语音转文本 Learn how to …

一句话设计模式11:过滤器模式

过滤器模式: 直接看 java8的filter; 文章目录过滤器模式: 直接看 java8的filter;前言一、过滤器模式的作用二、如何实现过滤器模式直接上代码总结前言 过滤器模式一般使用场景是: 过滤集合中的不同元素的一种手段,其实平时开发中你经常用,但是你不知道而已;(心里话: 这也算一种…

C++命名空间

C命名空间 C命名空间是一种用于组织代码的机制&#xff0c;它可以将全局命名空间划分为更小的、独立的部分&#xff0c;从而避免命名冲突和名字空间污染。在本文中&#xff0c;我们将介绍C命名空间的基本概念、使用方法和注意事项。 什么是命名空间&#xff1f; 命名空间是C…

QT

多平台C图形用户界面应用程序框架 集成了很多可以直接运用的图形的库 应用在windowns10系统 新建项目 有三种基类可以选择&#xff0c;开发是基于这三种基类的基础上&#xff0c;利用软件支持的QT语言进行界面元素添加与优化 代码添加&#xff08;添加代码时&#xff0c;大小…

flutter实战(1)-配置安装

目录支持的OS安装SDKwindows找到windows对应的SDK安装LINUXsnapd手动IDEMacLinux 或者 Windows 平台支持的OS 有以下这些OS可以安装配置flutter 安装SDK windows 要想安装和运行 Flutter&#xff0c;你的开发环境至少应该满足如下的需求&#xff1a; 操作系统&#xff1a;W…

组合预测模型 | SSA-LSTM、LSTM麻雀算法优化长短期记忆神经网络时间序列预测(Matlab程序)

组合预测模型 | SSA-LSTM、LSTM麻雀算法优化长短期记忆神经网络时间序列预测(Matlab程序) 目录 组合预测模型 | SSA-LSTM、LSTM麻雀算法优化长短期记忆神经网络时间序列预测(Matlab程序)预测结果评价指标基本介绍程序设计参考资料预测结果 评价指标 SSA-LSTM优化得到的最优…

[TIFS 2022] FLCert:可证明安全的联邦学习免受中毒攻击

FLCert: Provably Secure Federated Learning Against Poisoning Attacks | IEEE Journals & Magazine | IEEE Xplore 摘要 由于其分布式性质&#xff0c;联邦学习容易受到中毒攻击&#xff0c;其中恶意客户端通过操纵其本地训练数据和/或发送到云服务器的本地模型更新来毒…

阿里“通义千问”大模型上线!让生成式AI更贴近中国人生活

阿里版的 ChatGPT 语言大模型来了。 张勇在峰会上表示&#xff0c;阿里巴巴所有产品未来将接入“通义千问”大模型&#xff0c;进行全面改造。他认为&#xff0c;面向AI时代&#xff0c;所有产品都值得用大模型重新升级。 目前&#xff0c;钉钉、天猫精灵等产品已接入通义千问测…

PYQT5学习笔记00——Pycharm环境搭建以及配置项目虚拟环境教程

1、安装基本环境 需要的基本环境有python3.x的解释器、pip包管理工具以及pipenv虚拟环境管理工具。   我们安装了python后&#xff0c;pip包管理工具会自带安装&#xff0c;pipenv虚拟环境管理工具我们使用pycharm即可&#xff0c;无需使用python自带的。 python解释器下载地…

【Git代码仓库托管】上海道宁为您提供构建、扩展和交付安全软件的完整开发人员平台

GitHub是用于 构建、扩展和交付安全软件的 完整开发人员平台 通过提高开发人员速度的工具 推动创新 加快高质量软件开发 GitHub提供无限的存储库 一流的版本控制和 世界上强大的开源社区 因此您的团队可以 更高效地协同工作 开发商介绍 GitHub归属于微软公司&#xf…