当前位置: 首页 > news >正文

JVM(Java Virtual Machine) 详解

1. JVM 内存区域划分

一个 Java 写的程序,跑起来就得到了一个 Java 进程(资源分配的基本单位)= JVM + 上面运行的字节码指令

1) 程序计数器(比较小的空间),保存了下一条要执行的指令的地址

这个不是 CPU 的寄存器,而是内存空间;这里的 “下一条要执行的指令” 是 Java 的字节码(不是 cpu 的 二进制 的机器语言)

2) 堆

JVM 上最大的空间,new 出来的对象都在 堆 上

3) 栈(Stack Overflow)

函数中的局部变量,函数的形参,函数之间的调用关系

Java 虚拟机栈(JVM 之上,运行的 Java 代码的方法调试关系)

本地方法栈(JVM 里,C++ 代码的函数调用关系)

4) 元数据区(方法区)

Java 程序中的指令(指令都是包含在类的方法中)

保存了代码中涉及到的 类的相关信息

类的 static 属性

分析:

在一个 Java 进程中,元数据区和堆都是只有一份的(同一个进程中的所有线程都是共用同一份数据的,共用同一份内存空间)

程序计数器和栈,则可能有多份(当一个 Java 进程中有多个线程的时候,每个线程都有自己的程序计数器和栈)

线程就代表一个 “执行流”,每个线程就需要保存自己的 “程序计数器”,每个线程也需要记录自己的调用关系

例:以下代码中创建的变量处于哪个内存区域

class test {private int a;private Test b = new Test();private static int c;private static Test d = new Test();public static void main(String[] args) {int e = 10;Test f = new Test();}
}

tip:一个变量处于哪个内存区域和变量是不是 “内置类型” 无关,而是和变量的形态有关

1) 局部变量 -> 栈

2) 成员变量 -> 堆

3) 静态成员变量 -> 元数据区(方法区)

2. JVM 类加载的过程

2.1 概述

加载一个 .class 文件就会创建一个对应的类对象

基于该类对象就可以创建该类的实例,类对象其实就是“对象”的说明书/蓝本

类对象例就包含了 .class 文件中的各种信息,如:

类的名字

类里有哪些属性,每个属性名字,每个属性的类型,public/private

类里有哪些方法,每个方法的名字、参数,public/private

继承的父类是啥

实现的接口有哪些

...

2.2 类加载的具体步骤(重要

五个环节 / 三个环节(将中间3个环节合为1个)

1. 加载

把 .class 文件找到,代码中先找到类的名字,再找到对应的 .class 文件(涉及到一系列目录查找的过程),打开并读取文件内容

2. 验证

验证读到的 .class 文件中的数据是否正确合法(Java 标准文档中,明确定义了 .class 文件的格式)

Java 标准文档

3. 准备(分配内存空间)

最终需要得到类对象 => 需要内存

把刚才读取到的内容,确定出类对象需要的内存空间,申请这样的内存空间,并把内存空间中所有的内容都初始化为 0(Java 操作,C/C++ 申请到的内存不会进行置 0 操作)

置 0 操作是为了避免上次残留的数据被当前程序误使用

4. 解析(主要针对类中的字符串常量进行处理)

解析阶段是 Java 虚拟机将常量池内的 “符号引用(字符串常量,已经在 .class 文件中)” 替换为 “直接引用(里面保存了变量的地址)” 的过程,也就是初始化常量的过程

5. 初始化(针对类对象做最终的初始化操作)

执行静态成员的赋值语句

执行类中的静态代码块

针对 父类 也要加载

3. 面试题:双亲委派模型

3.1 概念

是类加载五个步骤中,第一个步骤里面的一个环节:给定 类全限定名,找到 对应的 class 文件位置

类加载器 JVM 中,已经内置了一些类加载器(JVM 中的功能模块),完成上述的 “类加载” 过程

JVM 默认有三个类加载器

(爷爷)BoorsrtapClassLoader 负责加载标准库中的类(标准库的类,也是有一个专门存放位置的)

(爸爸)ExtensionClassLoader 负责加载扩展类(JVM 厂商对 Java 功能做出的一些扩展)

(儿子)ApplicationClassLoader 负责加载第三方库中的类 / 自己写的代码中的类

tip:上面三者不是 Java 中父类子类的继承关系,而是在类加载器中有一个 parent 这样的引用指向父亲

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

输入:类的全限定名(字符串),类似于 java.lang.String

输出:找到对应的 .class 文件

这样设定的最核心目的:防止用户自己写的类把标准库的类给覆盖掉

保证标准库的类被加载的优先级最高(扩展库其次,最后是第三方库)

4. 垃圾回收机制(GC)

4.1 引子

C/C++ 中,malloc/new 一个对象后,都需要手动释放内存 free/delete,如果不释放就可能产生内存泄漏(头号职业杀手,后果可能很严重,排查非常不好搞)

C 中,针对内存泄漏,直接摆烂了

C++ 中,针对内存泄漏,给出了一个方案:“智能指针”(然而它并不太智能)

在 Java 中对内存泄漏给出更系统更完整的解决方案(在 Java 之前也有一些语言使用了这样的方案)

垃圾回收,有了它,程序员可以放心大胆的 new,JVM 会自动识别哪些 new 完的对象再也不用了,就会把这样的对象自动释放掉

其他语言一看都觉得香,纷纷投入到 垃圾回收 的怀抱,如:Go、Python、PHP、JS...主流语言中大部分都是内置了 GC

但是,GC 也是有代价的,C++ 不是搞不了 GC,而是开发 C++ 的大佬们评估了之后,不愿承担这些代价

GC 需要 JVM 中引入额外的逻辑:

1) 消耗不少 CPU 开销,进行垃圾的扫描和释放

2) 进行 GC 的时候可能会触发 STW(Stop The World)问题,导致程序卡顿,对于性能要求高的场景就会影响很大

因此,GC 就会 提高开发效率,影响运行效率

4.2 Java 的垃圾回收

JVM 中有好几个内存区域,GC 主要负责的是 堆

其中 程序计数器和栈 是跟随线程的;元数据区(一个程序里面要加载的类都是有上限的,不会出现无限增长的情况)

垃圾回收,也就是回收内存,是以对象为维度进行回收的(回收对象)

4.3 GC 具体怎样回收

1) 先找出谁是垃圾

需要针对每个对象分别判定,是否为垃圾

在 Java 中使用一个对象,一般都是通过 “引用” 来使用的,如果一个对象没有引用指向了,就可以认为这个对象是垃圾了

方案一:引用计数

给每个对象分配一个计数器,衡量有多少个引用指向

每增加一个引用,计数器 + 1

每减少一个引用,计数器 - 1

当计数器减为 0,此时对象就是垃圾了

这样做是可以回收垃圾,但是假设 Test 类就只有一个 int 成员(4字节),此时为了引入引用计数,少说得搞个 short(2字节),内存多占用了 50%

上述引用计数方案在 Java 中(JVM)没有采纳,因为其存在两个问题:

1) 消耗额外的空间

2) 引用计数可能导致 “循环引用”,使得上述的判定出错(和死锁类似),如下示例:

以上的循环引用也是有解的,但是需要引入更多机制(环路检测),代价就更大了


方案二:可达性分析(用时间换空间)

JVM 中专门搞了一些线程,周期性的扫描代码中的所有对象,判定某个对象是否是 “可达”(可以被访问到),对应的,不可达的对象就是垃圾了

如上图,JVM 中进行可达性分析的线程就是在做这样的事,从 root 出发,尽可能的通过 root 访问到更多的对象,相当于遍历的过程(严格来说,不是 “树的遍历”,而是 “图的遍历”)

可达性分析的起点称为 GC root,一个程序中 GC root 不是只有一个,而是有很多个,例如:

1) 栈上的局部变量(引用类型)

2) 方法区中,静态的成员(引用类型)

3) 常量池引用指向的对象

把所有的 GC root 都遍历一遍,针对每一个都尽可能往下延申...

可达性分析是很消耗时间的,尤其是当程序中对象特别多的情况下


2) 释放垃圾的内存空间

a) 标记-清除(直接针对内存中的对应对象进行释放)

当标记到垃圾位置,并对其清除时,会引入 “内存碎片问题”,哪些对象要释放是随机的,很可能要释放的位置对于整个内存来说不是连续的,虽然将上述内存释放掉了,但是整体这些空闲内存并没有连在一起,后续申请内存的时候,就可能申请不了(申请的内存一定是连续的)

例如:上述回收完垃圾后,空闲内存一共 3M,现要申请 2M 的空间时,就会失败(不考虑未分配空间)

因此,尽量避免内存碎片,是释放内存的关键问题


b)  复制算法

将内存一分为二,同一时刻只使用其中的一半

当要回收垃圾时,将不是垃圾的对象拷贝到另一侧(确保这些被拷贝的对象是连续的)

然后将要回收一侧的空间全部释放掉

虽然能解决 “内存碎片化” 的问题,但是其缺点也很明显:

1) 内存空间利用率低

2) 如果存活下来的对象比较多,复制成本也比较大


c) 标记-整理

非常类似于顺序表中删除中间的元素

缺点:这样搬运的开销依旧不小


d) 分代回收

JVM 中真正的解决方案是把上面几个方案综合一下,取长补短

JVM 根据对象的年龄,把对象区分成:新生代(年轻的)、老年代(年老的)

tip:可达性分析是周期性的,每经过一轮扫描,对象仍然存活(不是垃圾),年龄 + 1

根据经验规律:

1. 绝大部分的新对象活不过第一轮 GC,留存下来的对象拷贝到幸存区

2. 幸存区是两个相等的空间,也是按照复制算法反复进行多次

新生代中,真正要拷贝的对象不多(经验规律),内存利用率低

3. 如果一个对象在幸存区已经反复拷贝多次,不是垃圾,年龄不断增长,达到一定程度之后,对象就要拷贝到老年代了

4. 根据经验规律,老年代中的对象生命周期都比较长,老年代的对象肯定还会进行可达性分析,但是进行 GC 的频率就会降低

另外,老年代也是通过标记整理的(需要整理的次数不多)

4.4 JVM 中的垃圾回收器

分代回收是 JVM 中 GC 的基本思想方法,具体落实到 JVM 的实现层面上,JVM 还提供了多种 “垃圾回收器” 对上述的分代回收做进一步的扩充和具体实现

1. CMS GC

CMS 设计理念:把整个 GC 过程拆分成多个阶段,能和业务线程并发运行的,就尽量并发,尽可能减少 STW 的时间

2. G1 GC

G1 是把内存分成很多块,不同的颜色(字母)表示这一小块是新生代(伊甸区/幸存区)、老年代

进行 GC 的时候,不要求一个周期就把所有的内存都回收一遍,而是一轮 GC 只回收其中的一部分就好(限制一轮 GC 花的时间/工作量),使 STW 的时间在可控范围之内

这些方案的主要目的:降低 STW 的影响(ZGC,目前比较新的垃圾回收器),目前可以把 Java STW 的时间降低到 1ms 以内

相关文章:

JVM(Java Virtual Machine) 详解

1. JVM 内存区域划分 一个 Java 写的程序,跑起来就得到了一个 Java 进程(资源分配的基本单位) JVM 上面运行的字节码指令 1) 程序计数器(比较小的空间),保存了下一条要执行的指令的地址 这个不是 CPU 的…...

【进阶OpenCV】 (4)--图像拼接

文章目录 图像拼接1. 读取图片2. 计算图片特征点及描述符3. 建立暴力匹配器4. 特征匹配5. 透视变换6. 图像拼接 总结 图像拼接 图像拼接是一项将多张有重叠部分的图像(这些图像可能是不同时间、不同视角或者不同传感器获得的)拼成一幅无缝的全景图或高分…...

pg if条件语句

1.语法: 2.区别 IF 语句: 只能在 PL/pgSQL 中使用,不适合在直接的 SQL 查询中使用。没有返回值,仅仅是控制逻辑流程。适合用在存储过程、函数和触发器中。 CASE 语句(在 PL/pgSQL 中): 可以在 P…...

Pikachu-unsafe upfileupload-getimagesize

什么是getimagesize()? getimagesize()是PHP中用于获取图像的大小和格式的函数。它可以返回一个包含图像的宽度、高度、类型和MIME类型的数组。 由于返回的这个类型可以被伪造,如果用这个函数来获取图片类型,从而判断是否时图片的话&#xff…...

SOA是什么

SOA SOA 即 Service-Oriented Architecture(面向服务的架构)。 一、定义 SOA 是一种软件设计方法和架构理念,它将应用程序的不同功能单元(称为服务)通过定义良好的接口和契约联系起来。这些服务可以独立部署、独立运…...

构建高效团队,内部CRM系统的益处详解

内部CRM系统的最大优势之一是它能够集中并系统化客户信息,包括联系方式、购买历史、偏好设置、服务记录等。这种集中式的数据管理使企业能够快速响应客户需求,预测客户行为,提供个性化的服务或产品。更重要的是,它有助于建立一个统…...

Linux文件属性

Linux 文件基本属性 为了保护系统的安全性,Linux 系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定。 在 Linux 中我们通常使用以下两个命令来修改文件或目录的所属用户与权限: - chown (change owner) &#…...

什么是Anaconda

目录 1. [什么是Anaconda](#什么是Anaconda) 2. [安装Anaconda](#安装Anaconda) - [检查系统](#检查系统) - [下载Anaconda](#下载Anaconda) - [安装Anaconda](#安装Anaconda) 3. [启动Anaconda Navigator](#启动Anaconda-Navigator) 4. [创建与管理环境](#创建与管理环…...

ubuntu24开启启动脚本

因为我是在之前装的是windows和ubuntu双系统,所以想在ubuntu中自动挂载和开启时做些自己的脚本处理开发环境。 我的脚本如下: truedei@truedei-code:~$ cat mount.shsudo umount /media/truedei/*#sudo ntfsfix /dev/sda3 #sudo ntfsfix /dev/sda4 #sudo ntfsfix /dev/sda5…...

流浪地球行星发动机

随着电影《流浪地球2》的热播,影片中的行星发动机构想激发了社会各界对科幻与未来科技的广泛讨论。本文深入剖析了行星发动机的科学原理,包括重力助推、行星转移轨道以及重核聚变等核心技术,揭示了其在推动地球逃离太阳系过程中的关键作用。通…...

云岚到家,使用Elasticsearch实现服务的搜索功能,使用Canal+MQ完成服务信息与ES索引同步。MQ

为什么使用elasticsearch?数据很多么? 项目使用Elasticsearch是实现了门户上对服务的搜索。 平台上的服务数据是并不是很多,全国所有区域下的服务信息加一起几千条,之所以使用Elasticsearch是因为: 1、公司架构师在系统架构时…...

【图论】迪杰特斯拉算法

文章目录 迪杰特斯拉算法主要特点基本思想算法步骤示例 实现迪杰斯特拉算法基本步骤算法思路 总结 迪杰特斯拉算法 迪杰特斯拉算法是由荷兰计算机科学家艾兹赫尔迪杰特斯拉(Edsger W. Dijkstra)在1956年提出的,用于解决单源最短路径问题的经…...

四、Python基础语法(数据类型转换)

数据类型转换就是将一种类型的数据转换为另外一种类型的数据,数据类型转换不会改变原数据,是产生一个新的数据。 变量 要转换为的类型(原数据) -> num int(28) 一.int()将其他类型转换为整型 1.整数类型的字符串转换为整型 num1 28 print(type…...

工业物联网的安全与隐私保护—SunIOT

【大家好,我是唐Sun,唐Sun的唐,唐Sun的Sun。一站式数智工厂解决方案服务商】 在当今数字化的时代,工业物联网(IIoT)正以前所未有的速度改变着工业生产的模式和效率。然而,随着工业物联网的广泛…...

二层网络和三层网络的理解与区别(包含通俗理解和归纳总结)

二层网络和三层网络是计算机网络中的两个不同层次,主要区别在于它们所处的OSI参考模型中的层次及其功能。 二层网络 (Layer 2 Network) 1.定义: 二层网络主要涉及数据链路层(Layer 2),这是OSI模型中的第二层。 它负…...

【C++】:lambda表达式的高级应用

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 引言 今天 我们来见见lambda表达式的高级用法 用法1&#xff1a;自定义删除器 有些类型的delete方法并不符合自身的析构方法&#xff0c;这时我们就需要自定义删除器。 unique_ptr<FILE> ptr1(fopen…...

详解正确创建好SpringBoot项目后但是找不到Maven的问题

目录 问题 解决步骤&#xff1a; 找到File->Project Structure... 设置SDK 设置SDKs 问题 刚刚在使用IDEA专业版创建好SpringBoot项目后&#xff0c;发现上方导航栏的运行按钮是灰色的&#xff0c;而且左侧导航栏的pom.xml的图标颜色也不是正常的&#xff0c;与此同时我…...

力扣203.移除链表元素

题目链接&#xff1a;203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6…...

UE4 材质学习笔记05(凹凸偏移和视差映射/扭曲着色器)

一.凹凸偏移和视差映射 1.偏移映射 这需要一个高度图并且它的分辨率很低&#xff0c;只有256*256&#xff0c;事实上&#xff0c;如果高度图的分辨率比较低并且有点模糊&#xff0c;效果反而会更好 然后将高度图输出到BumpOffset节点的height插槽中&#xff0c; 之后利用得到…...

网约班车升级手机端退票

背景 作为老古董程序员&#xff0c;不&#xff0c;应该叫互联网人员&#xff0c;因为我现在做的所有的事情&#xff0c;都是处于爱好&#xff0c;更多的时间是在和各行各业的朋友聊市场&#xff0c;聊需求&#xff0c;聊怎么通过IT互联网 改变实体行业的现状&#xff0c;准确的…...

【Vue】Vue 快速教程

Vue tutorial 参考&#xff1a;教程 | Vue.js (vuejs.org) 该教程需要前置知识&#xff1a;HTML, CSS, JavaScript 学习前置知识&#xff0c;你可以去 MDN Vue framework 是一个 JavaScript framework&#xff0c;以下简称 Vue&#xff0c;下面是它的特点 声明式渲染&#xff…...

SQLite数据库介绍

文章目录 SQLite常用接口 使用示例测试 SQLite SQLite是一个本地化的数据库,不需要客户端服务端什么的配置,主打就是轻量化方便化 他也不是一个独立的进程,而是可以根据应用程序的需求,可以进行静态或者动态的连接 而且他是直接存储在磁盘文件的,提供了简单易用的API接口 需…...

点击label 按钮起作用

要使点击 标签时能够触发与之关联的表单控件&#xff08;如输入框、复选框或单选按钮&#xff09;的作用&#xff0c;你需要正确地设置 标签的 for 属性&#xff0c;并确保该属性值与表单控件的 id 属性值相匹配。这样&#xff0c;当用户点击 标签时&#xff0c;与之关联的表…...

JPA、Hibernate、MyBatis三种ORM框架怎么选择

JPA&#xff08;Java Persistence API&#xff09;、Hibernate和MyBatis都是Java开发中常用的ORM&#xff08;Object-Relational Mapping&#xff0c;对象关系映射&#xff09;框架&#xff0c;它们提供了不同的方式来处理数据库交互。在选择这些框架时&#xff0c;需要考虑项目…...

【C++】map详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…...

力扣206.反转链表

题目链接&#xff1a;206. 反转链表 - 力扣&#xff08;LeetCode&#xff09; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5]输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; …...

如何查看服务器的带宽linux服务器

speedtest-cli ubuntu # 安装speedtest-cli sudo apt install speedtest-cli # 执行测试 speedtest --secure # 在其他博客可以看到使用以下的命令&#xff0c;但是我试了没用 speedtest-cli参考&#xff1a; https://ubuntu-mate.community/t/speedtest-cli-error/25722/2…...

云原生化 - 工具镜像(完整版)

在微服务和云原生环境中,容器化的目标之一是尽可能保持镜像小型化以提高启动速度和减少安全风险。然而,在实际操作中,有时候需要临时引入一些工具来进行调试、监控或问题排查。Kubernetes提供了临时容器(ephemeral containers)的功能,允许在不改变原始容器镜像的情况下,…...

leetcode68:文本左右对齐

给定一个单词数组 words 和一个长度 maxWidth &#xff0c;重新排版单词&#xff0c;使其成为每行恰好有 maxWidth 个字符&#xff0c;且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词&#xff1b;也就是说&#xff0c;尽可能多地往每行中放置单词。必要时可…...

Linux驱动学习——内核编译

1、从官网下载适合板子的Linux内核版本 选择什么版本的内核需要根据所使用的硬件平台而定&#xff0c;最好使用硬件厂商推荐使用的版本 https://www.kernel.org/pub/linux/kernel/ 2、将压缩包复制到Ubuntu内进行解压 sudo tar -xvf linux-2.6.32.2-mini2440-20150709.tgz 然…...