JVM详解

文章目录

  • 一、JVM 执行流程
  • 二、类加载
  • 三、双亲委派模型
  • 四、垃圾回收机制(GC)

一、JVM 执行流程

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

  1. 类加载器(ClassLoader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地库接口(Native Interface)

JVM 运行时数据区
VM 运行时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型((Java Memory Model,简称 JMM)完全不同,属于完全不同的两个概念,它由以下 5 大部分组成:
在这里插入图片描述

  1. Nartiye Method Stacks就表示是JVM内部的C++代码,就是给调用native方法(JVM内部方法)准备的栈空间。(也就是说C++根本不需要虚拟机的,他是直接把代码编译成native code,也就是cpu能识别的机器指令。但是java因为有跨平台需求,需要用jvm)
  2. JVM Stacks给java代码使用的栈
    注意:
    这里的栈和数据结构里面的栈并不是同一个意思,此处所说的栈,指代的就是JVM一块特殊的存储空间,对于JVM虚拟栈而言,是存储的就是方法与方法之间的调用关系。本地方法栈存储的就是native方法的调用关系。
    整个栈空间内部,包含很多元素,一个元素称之为一个栈帧,一个栈帧里包含这个方法的入口地址、方法参数、返回地址、局部变量等
    这个栈也是先进后出的,但是和数据结构里面的栈是更广泛通用的概念
  3. progarm counter pegister(程序计数器)这是记录当前执行到那一条指令
  4. 堆是JVM中空间最大的区域,new出来的对象就是放在堆上的,类的成员变量也是放在堆上的,一个进程对应一份堆对应N个栈,而栈是每个线程都有独立的栈(一个进程对应一个虚拟机,两个进程就是两个JVM)
  5. 元数据去(方法区):主要是常量池,静态成员变量,类对象就存在这
  6. 判断某个变量在啥区域
    局部变量在栈上
    普通成员变量在堆上
    静态成员变量在方法区(元数据区)

二、类加载

类加载简单理解就是.class文件,从文件(硬盘)被加载的内存中(元数据)这样的过程
在这里插入图片描述
1.加载
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
简单来说就是:把.class文件找到,打开文件,读取文件,把文件内容读到内存中。

2.验证
这一阶段的目的是确保Class文件的字节流中包含的信息符合java虚拟机规范

这里在说一下类对象实际究竟是什么
在这里插入图片描述
这是java虚拟机规范里面标准的类对象结构,也就是说在我们java代码写好之后,点击运行,首先要做的就是将我们代码里面的定义类进行重新定义(书写)书写的格式就是按照这种类似(C++)结构体的方式去书写,这就是我们.class文件。
在这里插入图片描述注意.class文件和类对象是同一个东西的不同形态,类对象是我们描述内存里实际存储的对象,class文件是这个对象以文件的形式打开后呈现的样子。

3.准备:
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值 的阶段
简单来说:给类对象分配内存空间(先在元数据占个位置),将静态成员变量赋值为0。

4.解析:
针对字符串常量进行初始化(将符号引用转化为直接引用)。一个字符串常量得有一块内存空间,存这个字符的实际内容,还得有一个引用,来保存这个内存空间的起始地址。
在类加载之前,字符串常量,此时处于.class文件中,此时这个引用记录的并不是字符串常量正在的地址,而是他在文件中的偏移量(或者说占位符,或者说符号引用)
只有在类加载之后,才真的把这个字符常量放到内存中,才有了内存地址,这个引用才被真正的赋值成内存地址(直接引用)。
(就像看电影之前我只知道自己相对位置,只有坐下来之后才知道自己的实际位置)

5.初始化:
初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程。(加载父类,执行静态代码块的代码等)

但是一个类啥时候会被加载,并不是java程序一运行就会加载,而是真正用到的时候才会去加载(懒汉模式)

常见的类加载时机

1.构造类的实例:new 了一个对象
2.调用这个类的静态方法,使用静态属性(因为静态的都和类绑定在一起,只有类被加载了,静态属性才会赋值)
3.如果加载一个子类,需要先加载其父类
4.如果加载过,后续就不需要重新加载

还不太明白的同学可以去看这篇文章
https://blog.csdn.net/Strange_boy/article/details/125717606?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169200970816800226573234%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=169200970816800226573234&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-125717606-null-null.268v1koosearch&utm_term=%E7%B1%BB%E5%8A%A0%E8%BD%BD%E7%9A%84%E8%BF%87%E7%A8%8B&spm=1018.2226.3001.4450

三、双亲委派模型

双亲委派模型描述的是在加载中找.class文件怎么去找的问题

JVM默认提供了三个类加载器
BootstrapClassLoader:负责加载标准库中的类。(这是java规范要求提供哪些类,无论哪种jvm的实现,都会提供这些类)
ExtensionClassLoader:负责加载JVM扩展库中的类(规范之外,由实现JVM厂商、组织提供的额外功能)
ApplicationClassLoader:负责加载用户提供的第三方库、用户项目代码中的类
三种构成父子关系,这个并不是说父类子类的那种继承关系,单纯只是比如说ApplicationClassLoader有一个parent引用指向ExtensionClassLoader
在这里插入图片描述
在这里插入图片描述

上述类是如何配合工作的呢?

首先加载一个类的时候,先从ApplicationClassLoader开始,但是他并不是真加载,而是委托给自己的父亲ExtensionClassLoader去加载,但是ExtensionClassLoader也委托给自己的父亲去加载BootstrapClassLoader,当BootstrapClassLoader发现没有上层了,那么就开始自己加载,去所有自己的标准库目录里面的类,如果找到就加载,如果没找到,就有子类加载进行加载。ExtensionClassLoader也是一样,最后才是ApplicationClassLoader加载用户定义的类。在ApplicationClassLoader加载完如果还有类没有加载,那么ApplicationClassLoader下面也没有子类了就会抛出异常。

之所以这样安排,是因为JVM实现这个功能的逻辑是用递归写的,目的是为了防止用户创建了一些奇怪的类,比如说用户写了个java.lang.String类,这样就保证JVM先加载的一定是JVM标准库里的java.lang.String类,而不是用户自定义的这个。这样就保证起码标准库和三方库的类不会加载错误,所以最多也就是用户自己定义的类加载错误。

四、垃圾回收机制(GC)

垃圾回收机制就是帮我们回收不再使用的内存。

在C或者C++中,我们new或者malloc一块空间,实际上是在堆上申请了一块内存空间(JAVA类似),堆上申请和栈上申请是不同的,因为堆申请的内存空间,必须手动释放(C++用free 或者delete),但是栈实际上方法执行结束了就自动释放了。堆的这个特性在个人电脑上可能没有太大影响,随着进程结束,堆的空间即使没回收也会回收。但是如果是服务器就需要考虑这个问题了,因为服务器的进程是一直存活的,会运行很长时间,如果我们用完堆不及时回收的话,可能会导致剩余空间越来越少。

GC运行虽然很省心,可以帮我们自动回收一些不用的空间,但是GC也会带来更大的系统开销,对程序的执行效率肯定是会有影响的。因为C++追求极致的性能,所以并不引入GC机制

注意我们在之前的编程中比如说释放scanner 释放statement,这些不是释放内存,而是释放文件。

所以通过上面的背景我们知道GC是针对堆中的数据进行垃圾回收,GC是以对象为基本单位进行回收的,而不是字节等这样设定的目的就是为了简单。

GC实际工作过程

1.找到垃圾、判定垃圾
关键思路
抓住这个对象,看他到底有没有“引用”指向他。
如果一个对象有引用,那么就有可能被使用但是如果么有引用,那么就一定不会再被使用了。
那么怎么去做就能判断对象是不是被引用了呢?

  1. 引用计数(这不是java的做法,而是python/php的方式)
    给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。引用计数法实现简单,判定效率也比较高。
    但是这个方法的缺点在于内存空间浪费的空间是比较大的。在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题。
  2. 可达性分析
    java中的对象,都是通过引用来指向并访问的
    一般是一个引用指向一个对象,这个对象里的成员,又指向别的对象。
    实际上整个java中的对象都是通过这样的链式或者树形结构给串起来的。
    可达性分析就是把所有这些对象组织的结构看做树,从根节点区去遍历,所有能被访问到的对象,标记为可达,不能可达的就会作为垃圾进行回收。
    上述操作类似与树遍历,这种操作相对于计数来说,效率上会慢一点,但是会解决循环引用问题,此外,这个可达性分析不是每时每刻都需要做,隔一段时间分析一下就可以了。
    可达性遍历的起点(一个代码中可能由多个起点)可能是:
    1.栈上的局部变量,
    2.常量池中的对象,
    3.静态变量。

如何清除垃圾

标记清除
"标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段 : 首先标记出所有需要回收的 对象,在标记完成后统一回收所有被标记的对象。后续的收集算法都是基于这种思路并对其不足加以改进而已。
标记-清除"算法的不足主要有两个 :

  1. 效率问题 : 标记和清除这两个过程的效率都不高
  2. 空间问题 : 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中
    需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次圾收集。

复制算法
"复制"算法是为了解决"标记-清理"的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使 用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后 再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配 时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运 行高效。算法的执行流程如下图 :
在这里插入图片描述

这样是解决了标记算法里面碎片空间的问题,但是也有缺点,就是空间利用率低,如果垃圾少,有效对象多,复制成本就会加大。

3.整理标记
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用 复制算法。
针对老年代的特点,提出了一种称之为"标记-整理算法"。标记过程仍与"标记-清除"过程一致,但后续步 骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。流程图如下:
在这里插入图片描述
但是这个做法效率也不高,如果搬运空间比较大,开销也还是比较大的。

分代回收
基于上述,我们可以将垃圾回收分为不同的场景,不同场景使用不同的算法

分代咋分的
实际就基于经验规律:如果一个东西,存在的时间比较长,那么大概率还会存在很长时间。这个经验会与java中的对象也是存在的(有相关的实验证明)所以可以根据对象生命周期的长短来使用不同的算法。
此时我们对对象引入一个概念:对象的年龄,对象的年龄用GC扫过的轮次为基本单位,扫过一轮没有被销毁,就是一岁,扫过两轮没有被销毁,就是两岁。
所以JVM按照对象的年龄将堆划分为多个区域

在这里插入图片描述
刚刚new出来的放入伊甸区,年龄是0岁,经过一轮之后被放入幸存区。幸存区相对于伊甸区来说要小很多,这是因为大部分的对象都是朝生夕死的,生命周期是很短的。从伊甸区到幸存区用的就是复制算法,到了幸存区之后还是还是要接受周期性的GC考验,如果变成垃圾,就会被释放,如果不是垃圾,就拷贝到另外一个幸存区,这 两个幸存区同一时刻只会用一个,对象在一轮轮的GC扫描中在两个幸存区中来回拷贝,由于幸存区体积不大,此处空间浪费也可以接受。如果这个对象已经在两个幸存区中拷贝多次,就会进入老年代,针对老年代也会周期性的GC扫描,但频率会更低,如果老年代对象扫描为垃圾,就用标记整理的方式进行释放。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【nodejs】用Node.js实现简单的壁纸网站爬虫

1. 简介 在这个博客中,我们将学习如何使用Node.js编写一个简单的爬虫来从壁纸网站获取图片并将其下载到本地。我们将使用Axios和Cheerio库来处理HTTP请求和HTML解析。 2. 设置项目 首先,确保你已经安装了Node.js环境。然后,我们将创建一个…

【前端vue升级】vue2+js+elementUI升级为vue3+ts+elementUI plus

一、工具的选择 近期想将vuejselementUI的项目升级为vue3tselementUI plus,以获得更好的开发体验,并且vue3也显著提高了性能,所以在此记录一下升级的过程对于一个正在使用的项目手工替换肯定不是个可实现的解决方案,更优方案是基于…

大数据 算法

什么是大数据 大数据是指数据量巨大、类型繁多、处理速度快的数据集合。这些数据集合通常包括结构化数据(如数据库中的表格数据)、半结构化数据(如XML文件)和非结构化数据(如文本、音频和视频文件)。大数据…

国内常见的几款可视化Web组态软件

组态软件是一种用于控制和监控各种设备的软件,也是指在自动控制系统监控层一级的软件平台和开发环境。这类软件实际上也是一种通过灵活的组态方式,为用户提供快速构建工业自动控制系统监控功能的、通用层次的软件工具。通常用于工业控制,自动…

python 打印人口分布金字塔图

背景 今天介绍一个不使用 matplot,通过DebugInfo模块打印人口金字塔图的方法。 引入模块 pip install DebugInfo打印人口金字塔图 下面的代码构建了两个人口数据(仅做功能演示,不承诺任何参考价值),男性人口和女性…

FirmAE 工具安装(解决克隆失败 网络问题解决)

FirmAE官方推荐使用Ubuntu 18.04系统进行安装部署,FirmAE工具的安装部署十分简单,只需要拉取工具仓库后执行安装脚本即可。 首先运行git clone --recursive https://kgithub.com/pr0v3rbs/FirmAE命令 拉取FirmAE工具仓库,因为网络的问题&…

交叉熵--损失函数

目录 交叉熵(Cross Entropy) 【预备知识】 【信息量】 【信息熵】 【相对熵】 【交叉熵】 交叉熵(Cross Entropy) 是Shannon信息论中一个重要概念, 主要用于度量两个概率分布间的差异性信息。 语言模型的性能…

Java之继承详解二

3.7 方法重写 3.7.1 概念 方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。 3.7.2 使用场景与案例…

【Gitee提交pr】

Gitee提交pr 什么是pr怎样提交一个pr嘞? 什么是pr pr:指的是将自己的修改从自己的账号仓库dev下提交到官方账号仓库master下; 通俗来讲就是Gitee线上有属于自己的分支,然后本地在自己地分支修改完代码之后,提交到自己的线上分支&a…

Docker容器无法启动 Cannot find /usr/local/tomcat/bin/setclasspath.sh

报错信息如下 解决办法 权限不够 加上--privileged 获取最大权限 docker run --privileged --name lenglianerqi -p 9266:8080 -v /opt/docker/lenglianerqi/webapps:/usr/local/tomcat/webapps/ -v /opt/docker/lenglianerqi/webapps/userfile:/usr/local/tomcat/webapps/u…

[JavaWeb]【五】web后端开发-Tomcat SpringBoot解析

目录 一 介绍Tomcat 二 基本使用 2.1 解压绿色版 2.2 启动TOMCAT 2.3 关闭TOMCAT 2.4 常见问题 2.5 修改端口号 2.6 部署应用程序 三 SpringBootWeb入门程序解析 前言:tomcat与SpringBoot解析 一 介绍Tomcat 二 基本使用 2.1 解压绿色版 2.2 启动TOMCAT 2…

万字长文带你快速了解整个Flutter开发流程

文章目录 背景1.简介与优势Flutter是什么?为什么选Flutter? 2.开发环境搭建安装Flutter SDK配置开发环境 3.创建项目项目结构概览: 4.UI 构建与布局什么是Widget:StatelessWidget和StatefulWidget:Widget的组合&#x…

《树莓派4B家庭服务器搭建指南》第二十期:在树莓派运行rsnapshot, 实现对服务器数据低成本增量本地备份

title: 020《树莓派4B家庭服务器搭建指南》第二十期:在树莓派运行rsnapshot, 实现对服务器数据低成本增量本地备份 我的天翼云服务器有/opt 和 /usr/share/nginx两个目录, 用来存储网站的内容, 数据无价, 为了避免珍贵的数据丢失,我决定使用树莓派运行 …

数据结构算法--4堆排序

堆排序过程: >建立堆(大根堆) >得到堆顶元素,为最大元素 >去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整使堆重新有序 >堆顶元素为第二大元素 >重复步骤3,直到堆变空 此时是建立堆后的大根堆模型 将…

凯迪正大—直流电阻测试仪

一、产品概述 武汉凯迪正大直流电阻测量仪是变压器制造中半成品、成品出厂试验、安装、交接试验及电力部门预防性试验的必测项目,能有效发现变压器线圈的选材、焊接、连接部位松动、缺股、断线等制造缺陷和运行后存在的隐患。 为了满足变压器直流电阻测量的需要&a…

企业网三层构架实验

实验题目如下: 实验拓扑如下: 实验要求如下: 【1】内网IP地址172.16.0.0/16 合理分配 【2】SW1/2之间互为备份 【3】VRBP/STP/VLAN/TRUNK均使用 【4】所有PC通过DHCP获取IP地址 实验思路如下: (1)合理…

基于X86六轮差速移动机器人运动控制器设计与实现(一)软件与硬件架构

本文研究的六轮差速移动机器人 (Six-Wheeled Differential Mobile Robot , SWDMR) 为了满足资源站到资源站点对点的物资运输,对机器人的跨越障碍能力 有较高的要求。对比传统的四轮移动机器人,六轮移动机器人能够提供更强的驱动 力&#…

pytest自动化框架运行全局配置文件pytest.ini

还记得在之前的篇章中有讲到Pytest是目前主要流行的自动化框架之一,他有基础的脚本编码规则以及两种运行方式。 pytest的基础编码规则是可以进行修改,这就是今日文章重点。 看到这大家心中是否提出了两个问题:pytest的基础编码规则在哪可以…

认识docker+LNMP架构

目录 一、docker 1.安装,启动 2.docker相关命令 3.如何使用? 二、LNMP 1.认识LNMP 2.sql注入漏洞挖掘 3.如何绕过检测进行注入 一、docker 1.安装,启动 2.docker相关命令 docker search nginx 搜索镜像 docker pull docker.io/ngin…

皮爷咖啡基于亚马逊云科技的数据架构,加速数据治理进程

皮爷咖啡(Peet’s Coffee)是美国精品咖啡品牌,于2017年进入中国,为中国消费者带来传统经典咖啡饮品,并特别呈现更加丰富的品质咖啡饮品体验。通过深入应用亚马逊云科技云原生数据库产品Amazon Redshift以及Amazon DMS等…
最新文章