了解JVM

news2024/12/27 1:05:24

PS:本文以下部分,默认都是使用HotSpot,也就是Oracle Java 默认的虚拟机为前提来进行介绍的。

1.JVM执行流程

程序在执行之前先要把Java代码转换成字节码(.class文件),JVM首先需要把字节码通过一定的方式类加载器(ClassLoader)把文件加载到内存中 运行时数据区(Runtime Data Area),而字节码文件是JVM的一套指令集规范,并不能直接交割底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine)将字节码翻译成底层系统指令再由CPU去执行,而这个过程中需要调用其他语言本地库接口(Native Interface)来实现整个程序的功能,这就是这4个主要组成部分的职责与功能

 总结来看,JVM主要通过分为以下4个部分,来执行Java程序的,它们分别是:

  1. 类加载器
  2. 运行时数据区
  3. 执行引擎
  4. 本地库接口

2.JVM运行时数据区

JVM运行数据区也叫做内存布局,但需要注意的是他和Java内存模型完全不同,属于完全两个不同的概念,它由以下5大部分组成:

 2.1堆(线程共享)

堆的作用:程序中所有对象都保存在堆中

堆里面分为两块区域:新生代和老年代,新生代放新建的对象,当经过一定GC次数后还存活的对象会放入老年代。新生代还有3个区域:一个Endn + 两个 Survivor (S0/S1)

新创建的对象会放到Eden中,经过少数几个回合,会进入S0或者S1(这两个部分只一次使用一个),在经过较长的几个回合,会进入Old区。同时系统判定这几个区域中的对象是否垃圾的频率也会逐渐减少的

垃圾回收的时候会将Endn中存活的对象放到一个未使用的Survivor中,并把当前的Endn和正在使用Survivor清除掉

 2.2Java虚拟机栈(线程私有)

Java虚拟机栈的作用:Java虚拟机栈的声名周期和线程相同,Java虚拟机栈的描述是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于储存局部变量表、操作数栈、动态链接、方法出入口等信息。咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈

Java虚拟机栈主要包含了以下4个部分

  1.  局部变量表:存放了编译器可知的各种基本数据类型、对象引用。局部变量表所需的内存空间在编译时期完成分配,当进入一个方法时,这个方法需要在栈中分配多大的局部变量是完全确定的,在执行期间不会改变局部变量表大小。简单来说就是存放方法参数和局部变量。
  2. 操作栈:每个方法都会生成一个先进后出的操作栈
  3. 动态链接:只想运行时常量池的方法引用
  4. 方法返回地址:PC寄存器的地址

什么是线程私有?

由于JVM的多线程是通过线程轮盘切换分配处理器执行时间的方式来实现的,因此在任何一个确定的时刻,一个内核都只会执行一个线程中的指令。因此为了切换线程后能够恢复到正确的执行位置,每个线程都有独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称为“线程私有”的内存。

2.3本地方法栈(线程私有)

本地方法栈和虚拟机栈类似,只不过Java虚拟机栈是给JVM使用的,而本地方法栈是给本地方法使用的

2.4程序计数器(线程私有的)

程序计数器的作用:用来记录当前线程执行到的行号

程序计数器是一块比较小的内存空间,可以看作线程所执行的字节码的行号指示器。

如果当前线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正常执行的是一个Nativa方法,这个计数器值为空

程序计数器内存区域是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域

2.5方法区(线程共享)

方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量,即使编译器编译后的代码等数据的

方法区也被叫做“元空间”

2.5.1JDK1.8元空间的变化

  1. 对于HotSpot来说,JDK 8元空间的内存属于本地内存,这样元空间的大小就不在受JVM最大内存的参数影响了,而是与本地内存大大小有关了
  2. JDK 8中元字符串常量池移动到了堆中

2.5.2运行时产量池

运行时常量池时方法区的一部分,存放字面量与符号引用

字面量:字符串(JDK 8 移动到堆中)、final常量、基本数据类型的值

符号引用:类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符

3.JVM类加载

3.1类加载过程

对于一个类来说,它的生命周期是这样的:

 其中前5步是固定的顺序并且也是类加载的过程,其中中间的3步属于连接,所以对类加载来说总共分为以下几个步骤:

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化

3.2加载

“加载”阶段是整个“类加载”过程中的一个阶段,它和类加载Class Loading是不同的,一个是加载Loading,另外一个类加载Class Loading,所以不要把二者混淆了

在加载阶段,Java虚拟机需要完成以下三件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态储存结构转化为方法区运行时的数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

 3.3验证

验证是连接阶段的第一步,这个阶段目的是确保Class文件的字节流包含的信息符合《Java虚拟机规范》的全部约束要求,确保这些信息被当作代码运行后不会危害虚拟机自身的安全

【验证选项】

  • 文件格式验证
  • 字节码验证
  • 符号引用验证...

3.4准备

准备阶段是正为类中定义的变量(即静态变量)分配内存并设置类变量初始化值的阶段。

比如此时有这样的代码

public static int value = 123;

它是初始化value的int值为0,而非123

3.5解析阶段

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程

符号引用:一组字符来表示描述引用的目标,只要能定位到目标即可

直接引用:需要这个对象的时候,我们直接填写这个对象真实的物理地址来定位目标

在Java中,一个Java类将会编译成一个class文件。在编译的时候,并不知道一个类的真实地址,只能用符号引用代替。比如在编译阶段,Student类需要一个Teacher类,但是Student并不知道Teacher类的真实地址,只能用一段特殊且唯一的字符串表示,通过这个字符串能找到Teacher类

3.6初始化

初始化阶段,Java虚拟机整整开始执行类中编写的Java程序代码,将主导权交给应用程序。初始化阶段就是执行类构造器方法的过程。

作用:初始化静态成员变量,执行静态代码块,类要有父类还需要加载父类...

站在Java虚拟机的角度看,只存在两种不同的类加载器:一种是启动类加载器,这个类加载器使用C++语言实现,是Java虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都是由Java语言来实现的,独立存在与虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader

站在Java开发人员的角度看,类加载器应当划分的更加细致一些。自从JDK1.2依赖,Java一直保持着三层类加载器、双亲委派的类加载器。

什么是双亲委派模型?

如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求最终都应传送到最顶层的启动类加载器中,自由当父类加载器反馈自己无法完成这个加载请求(它的所有范围中没有找到所需要的类)时,子加载器才会尝试自己去完成加载。

在JVM中,内置了三个类加载器

  • BootStrap ClassLoader 负责加载Java标准库中的类
  • Extension ClassLoader 负责加载一些非标准但是Sun/Oracle扩展的库的类
  • Application ClassLoader 负责加载在项目中自己写的类以及第三方库中的类

它们三者,有着继承关系,BootStrap ClassLoader为Extension ClassLoader的父类,Extension ClassLoader 为Application ClassLoader的父类

当加载自己写的类时,会调用Application ClassLoader类加载器,但是它不会直接去搜索,而是交给它的父类,Extension ClassLoader也不会搜索,而是交给BootStrap ClassLoader,它会搜索自己管理的范围,没有找到它会交给自己的子类Extension ClassLoader,让它去搜索相应的范围,找不到,就会返回给Application ClassLoader,让它搜索。(双亲委派模型时可以打破的)

4.垃圾回收相关

主要讲内存分配和回收关注的Java堆和方法区这两个区域。在堆中放着各种各样的对象,但是有些已经不使用了,我们称它们为“死亡对象”,我们要对死亡对象进行回收。

这就牵扯出了连个问题:怎么判定死亡对象?如何回收死亡对象?

4.1死亡对象判定的算法

4.1.1引用计数算法

引用计数算法为:给对象增加一个引用计数器,每当有一个地方引用它时,这个计数器就会加1;当这个计数失效时,就会-1;在这个计数器为0时,我们就能销毁这个对象。

引用计数法实现简单,判定小路高,在大多数情况下是一个不错的选择,如Python语言就采取这种垃圾回收机制。(注意Java不采用此算法)

但是引用计数法无法解决循环引用的问题。

比如:a的对象的地址是0x11, b对象的地址是0x22,我们让堆中0x11的对象的内容指向了0x22,又让0x22的对象的内容指向了0x11,这样每个对象的程序计数器都为2

 此时这个ab都被销毁了,0x11和0x22对象程序计数器都-1,变为1。我们发现我们使用不了这两个对象,但是却也不能被销毁,这就是循环引用的问题

 4.1.2可达性分析算法

可达性分析算法:通过一系列称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到GC Root没有任何引用链连相连的时候,证明这个对象不可用

 如上图,从GC roots出发,object5、object6没有办法到达,所以它们两个可以被判定为可回收对象。

但是这种方法也有不好的方面:

比如我们使用这种方法搜索的时候,程序是暂停的(如果不暂停,就会导致新的对象不在这颗树上,导致错误回收,也有可能导致建立这颗树的时候这个对象在这颗树上,但是搜索过程中实际上不存在了,导致对象回收不到),这导致程序的运行效率比较低。

这个是Java所采用的方法,经过这麽多年的发展,垃圾回收也在不断的优化,已经尽量减少暂停的时间。

4.2垃圾回收算法

通过上面的算法,我们已经讲死亡对象标记出来了,标记出来以后,我们就可以对这些对象进行回收了。我们来了解下如何回收吧.

4.2.1标记-清除算法

我们刚刚已经判定了什么是死亡对象,接下来我们就直接进行清除了

 标记—清除算法的问题:

  1. 效率不高:每次清除只是清除一小片区域,然后再跳转到下一个区域进行清除
  2. 空间问题:清除后,导致空间非常的零散,在申请大的对象的空间时候,虽然总的空间够用,但是由于空间不是连续的,但是无法正常的利用

4.2.2复制算法

复制算法:将空间分为两部分,每次使用只能使用其中的一块空间,在进行垃圾回收后,把对象复制到另一块空的空间内

缺点:我们发现这种算法浪费空间比较大

 4.2.3标记-整理算法

标记-整理算法:就是在标记清除算法的基础上,清除过后,再进行整理,这样就能腾出大量连续空闲空间了

 缺点:有些对象可能永久不会销毁,这导致了一些对象每次垃圾回收都会进行复制

4.2.4分代算法(重点)

当前JVM的垃圾回收采用的是分代算法,这个算法将内存划分了几个不同的区域,即新生代和老年代。新生代又分为一个较大的伊甸区和两块较小的幸存者区

  •  新创建的对象都会进入伊甸区,触发第一次Minor gc时,大部分对象都会被销毁,仍然活着的对象进入正在使用的幸存区;
  • 进入幸存区后经过Minor gc会将幸存的对象转移到另一块空着的幸存区,并清空此幸存区;在经过很多回合的Minor gc后,依然存活的对象会进入老年代;同时扫描判定是否为可回收对象的频率比伊甸区低
  • 老年代可以采取标记-整理的垃圾回收策略,同时被扫描判定是否为可回收对象的频率很低

【回顾一个对象的一生】

一头小鹿(对象)出生在一块叫做伊甸区的地方,这个地方每年会发生一次自然灾难(Minor gc),大部分鹿都死了;第一年幸存的鹿进入到了一个叫做幸存区的地方,自然灾难不是每年发生,但是也是经常发生,在发生自然灾难后幸存的鹿会去到另一块幸存区;经过多年的灾难,同一时期产生的鹿可能只有一个了,进入到了一个叫做老年代的区域,这个区域自然灾难很少,它们就生活在这里

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

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

相关文章

python调用oepnai API

目录 apiAI官网介绍([Introduction](https://platform.openai.com/docs/api-reference/introduction))安装官方SDK认证(Authentication)申请API KEY请求组织(Requesting organization) 发送请求关于chat to…

CAJ文献如何转成PDF?分享两个免费的方法!

CAJ格式是中国知网(CNKI)常见的电子文献格式,但有时我们可能更倾向于将其转换为PDF格式以便于查阅、存储和共享。为了帮助大家完成这个任务,下面将分享两种免费的方法来将CAJ文献转换为PDF格式。无论您是研究学者、学生还是对特定…

学校招生报名小程序开发笔记(一)

背景 这是一个以报名为核心的职业学校招生小程序,目的是方便想要系统学习技能,入门某项技能或者领域的初高中毕业生,了解该学校的基本情况及各个专业,并提供报名路径,致力于技能型人才培养 功能规划 主要功能包括专…

使用 Pytest 运行 yaml 文件来驱动 Appium 自动化测试

目录 前言: 获取 yaml 文件 YamlTest 测试类 Appium 初始化 Pytest 测试类 自定义 runtest demo: 自定义错误输出 Yaml 使用方式规则 前言: 使用Pytest来运行yaml文件来驱动Appium自动化测试是一种方便且灵活的方法。通过将测试数据…

【异常解决】postman请求提示Full authentication is required to access this resource

Full authentication is required to access this resource解决办法 报错问题:在使用 postman 测试接口时,该接口需要在 Header 中传入 access_token,实际上也在请求的 Header 中添加上了 access_token 参数,但是服务端还是返回4…

【STM32零基础入门教程01】STM32入门基础知识

本篇内容为STM32零基础入门教程的第一篇,网上STM32的教程很多,有些初学者还是望而却步。其实STM32并不难,只是一个新的事物出现在我们面前一时间不适应,思来想去我打算写点东西一方面自己有点知识的积累,另一方面希望可…

Python教程(4)——Python开发工具PyCharm的下载与安装

PyCharm是一种专业的Python集成开发环境(IDE),由JetBrains公司开发和维护。它提供了丰富的功能和工具,帮助开发人员更高效地编写、调试和测试Python代码。如果是一些大型Python项目强烈推荐用这个来开发。今天我们来介绍一下PyCha…

【实战】 七、Hook,路由,与 URL 状态管理(中) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(十二)

文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…

Docker基础(二)

1、Docker工作原理 Docker是一个Clinet-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。 容器,是一个运行时环境,就是我们前面说的…

论文笔记:Deep Spatio-Temporal Residual Networks for Citywide Crowd FlowsPrediction

2017 AAAI 使用时空残差网络ST-ResNet 进行 城市区域流入流出客流量预测 1 研究对象 城市客流流入流出 根据经纬度将城市划分为网格 IJ 1.1 难点 空间依赖性 时间依赖性 外部影响 2 模型 3 实验 北京出租车数据纽约自行车数据 评价指标:RMSE

Java List中通过对象属性排序,可实现多条件排序

直接上代码: import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.Data;import java.util.Comparator; import java.util.List; import java.util.stream.Collectors;/*** List 对象属性排序*/Data AllArgsConstructor clas…

[java安全]动态代理

文章目录 【java安全】动态代理前言本质重要方法Proxy#newProxyInstance()InvocationHandler#invoke() 举例 【java安全】动态代理 前言 java中代理分为两种:静态代理、动态代理 而动态代理又分为:jdk动态代理、CGLIB动态代理 本文我们来谈谈jdk动态代…

Iceberg从入门到精通系列之十七:Apache InLong往Iceberg同步数据

Iceberg从入门到精通系列之十七:Apache InLong往Iceberg同步数据 一、概览二、版本支持三、依赖项四、SQL API 用法五、多表写入六、动态表名映射七、动态建库、建表八、动态schema变更九、Iceberg Load 节点参数十、数据类型映射 一、概览 Apache Iceberg是一种用…

【机器学习算法】主成分分析(PCA)

主成分分析(PCA) PCA(Principal Component Analysis) 是实现数据降维的一种算法。正如其名,假设有一份数据集,每条数据的维度是d,PCA通过分析这d个维度的前k个主要特征(这k个维度在原有d维特征的基础上重新构造出来,且是全新的正交…

SpringBoot+React学科竞赛管理系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码 一、项目演示 项目演示地址: 视频地址 二、项目介绍 项目描述:这是一个基于SpringBootReact框架开发的学科竞赛管理系统。首先,这是一个前后端分离的项目,代码简洁…

初学者怎么学习c++(合集)

学习c方法1 找一本好的书本教材,辅助看教学视频。好的教材,可以让你更快更好的进入C/C的世界。在校学生的话,你们的教材通常都是不错的。如果是自学,推荐使用谭浩强出的C/C经典入门教材。看视频是学习比较直观的方式。建议先看课本…

从零开始理解Linux中断架构(20)--关于二级中断控制-链式chained Handler

二级中断控制器是个双重角色,在上级中断控制器看来他是个中断设备,在连接到他的下级设备来看,他是个中断控制器。所以处理完成基本的中断控制器管理功能:映射本地中断,还要多个动作:修改上级中断的默认irq Handler,向上级中断设置自己的链式中断处理函数。 中断控制的层…

Springboot实现过滤器

一、导言 在Spring Boot中,过滤器是一种用于对HTTP请求进行预处理和后处理的组件。相较于拦截器,过滤器属于Servlet规范的一部分,它能够在请求进入Web容器之前或返回给客户端之前进行操作。 要在Spring Boot中实现过滤器,可以按…

指针进阶(万字深层次指针解析)

❤️ 作者简介 :对纯音乐情有独钟的阿甘 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识,对纯音乐有独特的喜爱 📗 日后方向 : 偏向于CPP开发以及大数据方向,如果你也感兴趣的话欢迎关注博主,期待更新 指针进阶 …

Java正则表达式,不定期更新

Java正则表达式 1. 匹配数字(包含负数、小数)2. 匹配不是纯数字和纯字母且需要8位以上的密码3. 密码:字母、数字、符号(_-*.,!#符号可自定义)三选二4. 密码:必须包含大写、小写、数字、符号(_-*…