深入了解Java虚拟机(JVM)

Java虚拟机(JVM)是Java程序运行的核心组件,它负责解释执行Java字节码,并在各种平台上执行。JVM的设计使得Java具有跨平台性,开发人员只需编写一次代码,就可以在任何支持Java的系统上运行。我们刚开始学习Java时就下载了JDK(Java开发工具包),它提供了编译、调试和运行Java应用程序所需的工具和库。JDK包括了JRE(Java运行时环境),JRE包含了Java虚拟机(JVM)。由于不同的CPU的指令集可能不同,所以相同的代码可能不能在不同的系统上都正常运行,而JVM就是为了解决这个问题,Java会先通过javac把  .java 文件编译为 java字节码(相当于Java自己的一套CPU指令)然后再由具体系统平台上的的JVM(不同系统上的JVM可能存在差异),把上述字节码转化为对应的CPU能识别的机器指令。

1. JVM中的内存区域划分 

 JVM其实也是一个进程(任务管理器中看到的java进程),Java程序的执行时申请的内存就是JVM从系统这边申请到的内存,JVM会先申请一块大的内存,这块内存在给Java程序使用时,又会根据实际的用图划分出不同的区域,每个区域都有不同的作用。

  1. Java堆(Java Heap): Java堆是Java虚拟机管理的最大一块内存区域,用于存放对象实例,数组,类的成员变量。Java堆是所有线程共享的内存区域,是垃圾回收的重点区域。

  2. Java虚拟机栈(Java Virtual Machine Stacks): Java虚拟机栈也称为栈内存,用于存储线程的方法调用、局部变量、部分结果等。每个方法在被调用时都会创建一个栈帧,并入栈;方法执行完毕后栈帧出栈。栈帧包括局部变量表、操作数栈、动态链接、方法返回地址等。

  3. 本地方法栈(Native Method Stack): 本地方法栈类似于Java虚拟机栈,但是它为Native方法服务,即JVM内部使用C、C++等编写的本地方法。

  4. 程序计数器(Program Counter Register): 程序计数器是一块较小的内存区域,用于记录当前线程下一条执行的字节码指令地址。在多线程环境下,每个线程都有独立的程序计数器。

  5. 元数据区(Metaspace)/ 方法区(Method Area):元数据指的是一些辅助性质的描述性质的属性,元数据区主要用于存储类的元数据信息,例如类的结构、方法信息、字段信息等。一个程序有哪些类,每个类中有哪些方法,每个方法里要包含哪些指令,都会记录在元数据区中,即元数据区储存了Java代码编译后的Java字节码

 注意:一个JVM进程中,堆和元数据区只有一个,栈和程序计数器可能有多份(每个线程都有一个自己的程序计数器和栈,即每个线程都有自己的执行流)。

public class Test{private int n;//堆区private static int m;//static修饰的变量为类属性储存在元数据区public static void main(String[] args) {Test t = new Test();//t为局部变量储存在栈区,类的实例储存在堆区}
}

2.JVM的类加载机制 

2.1 类加载过程 

JVM的类加载机制是指JVM在运行时将类的字节码加载到内存中并进行验证、准备、解析和初始化的过程。JVM的类加载机制主要包括以下几个步骤:

  1. 加载(Loading):查找并加载类的字节码文件。这个过程可以通过类加载器来完成,类加载器会根据类的全限定名在文件系统、网络或其他地方找到对应的字节码文件,并将其读入内存。

  2. 验证(Verification):确保被加载的类的字节码是合法、符合JVM规范的。包括文件格式验证、元数据验证、字节码验证、符号引用验证等步骤。具体验证依据,在Java虚拟机规范中有明确的格式说明

  3. 准备(Preparation):为类的静态变量分配内存并设置默认初始值,这些变量所使用的内存都将在方法区中进行分配。

  4. 解析(Resolution):将类中的符号引用转换为直接引用,这个过程可以在运行时进行也可以在编译时进行。

    class Test{String s = "hello";
    }

    上面代码编译后"hello"会储存在常量池中,s中相当于保存了“hello"的字符串常量的地址,但是代码没有运行时,s和"hello"都在字节码文件中,文件中没有地址这样的概念,所以在代码运行前s中存储的是一个类似于”偏移量"的概念记录了“hello”的相对位置,就是这里的符号引用。

  5. 初始化(Initialization):对类进行初始化,包括执行类构造器<clinit>()方法,静态变量赋值等操作。在初始化阶段,JVM会根据程序中对类的主动使用情况来触发初始化,例如创建类的实例、访问类的静态成员、调用类的静态方法等。

2.2 双亲委派模型

在类加载过程中,JVM采用了双亲委派模型,即由多个不同层次的类加载器组成一个层次结构,每个类加载器都有自己的责任范围,当一个类需要加载时,先由最顶层的类加载器尝试加载,如果无法加载再交由下一层的类加载器,依次类推,直到最底层的类加载器。

JVM中进行类加载是由一个专门的模块“类加载器(ClassLoader)”完成的,类加载器的作用是通过“全限定类名”(带有包名的类名,例如java.land.String,可以类比为文件路径中的绝对路径)查找 .class文件,把 .class文件的数据转化为运行时需要的类对象并加载到JVM中。

JVM中默认提供了三种类加载器,分别是:

  1. 启动类加载器(Bootstrap ClassLoader):负责加载Java的核心类库,如java.lang包下的类。它是JVM自身的一部分,通常由C++编写,并不继承自java.lang.ClassLoader类。

  2. 扩展类加载器(Extension ClassLoader):负责加载Java的扩展类库,位于jre/lib/ext目录下的类库。

  3. 应用程序类加载器(Application ClassLoader):也称为系统类加载器,负责加载当前项目的代码目录,以及第三方库的目录。

除了这三个默认的类加载器,开发者也可以自定义类加载器来实现特定的类加载需求,比如从网络中动态加载类、加密类加载等。自定义类加载器需要继承自java.lang.ClassLoader类,并重写其中的findClass()方法来实现类的加载逻辑。 

注意:这三个类加载器之间存在父子关系,上面的为父加载器,下面的为子加载器,即1是2的父亲,2是3的父亲。

双亲委派流程:当一个类加载器收到类加载请求时,它首先将这个请求委托给它的父类加载器处理。如果父类加载器无法完成此加载请求,子加载器才会尝试自己去加载。这个过程会一直递归下去直到启动类加载器。这样做的目的是保证Java核心API的稳定性,防止用户自定义的类替换掉核心类库中的类。

1. 从Application ClassLoader作为入口

2. Application ClassLoader不会立刻搜索自己负责的目录,会把任务交给父类加载器Extension ClassLoader。

3. Extension ClassLoader也不会立刻搜索自己负责的目录,也会把任务交给父类加载器Bootstrap ClassLoader。

4.Bootstrap ClassLoader没有父类加载器,就会搜索自己负责的目录查找需要的 .class文件,如果找到了就直接进入打开文件/读文件等流程中,如果没找到,则把任务交给下一级类加载器Extension ClassLoader继续尝试寻找。

5.Extension ClassLoader 接受到任务此时就会在自己负责的目录中开始寻找,如果找到了就直接进入打开文件/读文件等流程中,如果没找到,则同样把任务交给下一级类加载器Application ClassLoader继续尝试寻找。

6.Application ClassLoader 也会在自己负责的目录中开始寻找,如果找到了就直接进入打开文件/读文件等流程中,如果没找到,也会尝试把任务交给下一级,但是默认情况下Application ClassLoader没有下一级类加载器了,于是就会类加载失败抛出ClassNotFoundException异常

上述流程就保证了类加载的顺序,防止用户自定义的类替换掉核心类库中的类。例如用户自己定义了一个java.lang.String如果这个类先被加载了,java核心库中的String类就不会被加载。

3. 垃圾回收机制(GC) 

垃圾回收(GC)是自动内存管理的关键技术之一。它负责清理不再使用的对象,释放内存空间。垃圾回收,回收的是堆的内存 ,所以也可以说是回收对象。

3.1 识别垃圾 

判定对象后续是否会继续使用,不会继续使用的就会被视为垃圾,如果一个对象没有任何引用指向它,那么这个对象就无法被继续使用了,也就会被视为垃圾。

3.1.1 引用计数

引用计数方法并没有在JVM中使用,但是广泛运用在其他主流语言的垃圾回收机制中,如Python,PHP。

引用计数是通过给每个对象安排一个额外的空间,记录当前有几个引用指向该对象。每有一个引用指向该对象时,就把值加一,反之则减一,当这个值为0时则视为垃圾,当负责垃圾回收的扫描线程获取到这个对象的引用计数情况时,发现为0就会释放这个对象的空间。

引用计数存在两个关键的问题:

  1. 问题一:要给每个对象安排计数器,就会消耗额外的空间,如果对象数量很多,总的空间浪费也就很多。
  2. 问题二:可能产生循环引用问题,例如两个对象互相引用,但没有一个外部的引用指向它们,此时这两个对象是无法被获取到的,但他们的引用计数又不为0,也就不会被释放。
3.1.2 可达性分析 

JVM采用的就是可达性分析来识别对象是否是垃圾。

可达性分析采用的方法是 遍历所有变量,JVM会遍历所有能够被直接或者间接访问到的对象,能访问到的自然不是垃圾,遍历一圈后不能访问到的就视为垃圾。

3.2 释放垃圾 

找到垃圾以后就需要把垃圾对象所占的内存空间进行释放。

3.2.1 标记-清除算法

把标记为垃圾的对象直接释放。这种释放方式会导致内存碎片问题。

如图所示,释放后,会导致出现很多大大小小的内存碎片,而内存申请都是一次申请一段连续的内存空间,这就导致了部分内存碎片可能无法使用到,也就导致了空间浪费。

3.2.2 复制算法

将内存分为两个相等的部分,每次只使用其中一半。当这一半的内存用完后,就将还在使用的对象复制到另一半,然后再清除掉已经使用过的那一半内存中的所有对象。

这种方法避免了内存碎片,但是总的可用空间变少了,同时复制对象也会消耗时间。

3.2.3 标记-整理算法

将所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

 这样也可以避免内存碎片问题,但是移动对象也要消耗时间。

3.2.4 分代回收算法

JVM中采用的是分代回收算法。给每个对象引入年龄的概念,JVM中存在专门的线程负责周期性的扫描/释放对象,如果一个对象被线程扫描了一次,并且不是垃圾,该对象的年龄就会+1(初始年龄为0)。

JVM中会根据对象年龄的差异,把整个内存分成两个大的部分,新生代(年龄较小的对象)/ 老年代(年龄较大的对象),新生代又被划分为三个区域,其中大的一部分区域为 伊甸区 ,剩下两块大小相同的区域叫做  生存区  或者  幸存区 。

 

 新的对象都是从伊甸区中被创建的,第一轮GC扫描后,没有被清除的对象就会被通过复制算法移动到生存区(即生存区相当于未被使用的那块内存),生存区中的对象下次被GC扫描后,存活的对象又会被通过复制算法移动到另一个生存区(注意两个生存区完全是对等的),每经历一次GC,对象的年龄就会+1,如果某个对象在生存区中经过了若干轮GC任然没有被清除,JVM就会认为这个对象的生命周期很长,就会把这个对象移动到老年代,老年代的对象也会被GC扫描,只不过扫描的频率较低。老年代的对象被视为垃圾时会按照标记-整理算法释放内存。

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

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

相关文章

【leetcode】用队列实现栈

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 点击查看题目 思路: 在做此题之前&#xff0c;我们先要实现队列&#xff0c;这在上个博客中已经写过&#…

学习人工智能的方法及方向!

目录 一、第一部分&#xff1a;了解人工智能 二、人工智能学习路线图 三、职业规划 四、未来展望 五、总结 在这个信息爆炸的时代&#xff0c;想要系统性地学习人工智能&#xff08;AI&#xff09;并找到对应方向的工作&#xff0c;你需要一个明确的学习路径和职业规划。本…

复合机器人是一种集成了移动机器人

复合机器人是一种集成了移动机器人、协作机器人和机器视觉等多项功能的新型机器人。它的开发目的是为了解决工厂物流中最后一米的问题&#xff0c;提供智能搬运解决方案。复合机器人不仅集成了自主移动机器人&#xff08;AMR&#xff09;、机械臂等工作单元&#xff0c;还使用了…

Java电梯模拟

Java电梯模拟 文章目录 Java电梯模拟前言一、UML类图二、代码三、测试 前言 此程序为单线程简单模拟电梯(初版)&#xff0c;如果存在问题或者设计不合理的地方&#xff0c;请大家帮忙指出。 一、UML类图 二、代码 电梯调度器 package cn.xx.evevator;import java.util.*;pub…

#LLM入门|Prompt#2.1_第二部分:搭建基于 ChatGPT 的问答系统_简介_Introduction

《第二部分&#xff1a;搭建基于 ChatGPT 的问答系统》&#xff01; 本部分基于吴恩达老师与OpenAI合作开发的课程《Building Systems with the ChatGPT API》创作&#xff0c;旨在指导开发者基于ChatGPT的API进行智能问答系统的构建。 课程内容 课程背景&#xff1a; 使用C…

Web3游戏基础设施提供商Stardust为Sui上的游戏开发者提供支持

Stardust将其在钱包服务&#xff08;wallets-as-a-service&#xff09;基础设施和用户获取平台方面的专业知识带到了Sui&#xff0c;为游戏开发者提供了关键的帮助&#xff0c;以吸引玩家。近日&#xff0c;Stardust公司宣布将为Sui游戏开发者调整其成熟的钱包服务&#xff08;…

MySQL:开始深入其数据(四)select子查询

select眼熟吧?(都三节了) 又开始学习了 在 MySQL 中&#xff0c;子查询&#xff08;subquery&#xff09;是指在一个查询内嵌套另一个完整的 SELECT 语句。子查询可以嵌套在 SELECT、INSERT、UPDATE、DELETE 语句中&#xff0c;用于从内部查询结果中获取数据&#xff0c;进而完…

vue3 的await async

在 Vue 3&#xff08;以及大多数现代的 JavaScript 环境中&#xff09;中&#xff0c;async 和 await 是用来处理异步操作的关键字。这些关键字使你能够以同步的方式编写异步代码&#xff0c;使代码更加易读、易写&#xff0c;并且有助于管理异步流程。 async async 关键字用…

基于springboot的宠物咖啡馆平台的设计与实现论文

基于Spring Boot的宠物咖啡馆平台的设计与实现 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于Spring Boot的宠物咖啡馆平台的设计与实现的开发全过程。通过分析基于Spring Boot的宠物咖啡馆平台的设计与…

每日一题——LeetCode1566.重复至少K次且长度为M的模式

方法一 暴力枚举 var containsPattern function(arr, m, k) {const n arr.length;for (let l 0; l < n - m * k; l) {let offset;for (offset 0; offset < m * k; offset) {if (arr[l offset] ! arr[l offset % m]) {break;}}if (offset m * k) {return true;}}r…

k8s 网络概念与策略控制

一、Kubernetes 基本网络模型 Kubernetes 的容器网络模型可以把它归结为约法三章和四大目标。 1、约法三章 约法三章确保了Kubernetes容器网络模型的基本特性&#xff1a; ① 任意两个 pod 之间可以直接通信&#xff1a;在Kubernetes中&#xff0c;每个 Pod 都被分配了一个…

题记(47)--连续因子

目录 一、题目内容 二、输入描述 三、输出描述 四、输入输出示例 五、完整C语言代码 一、题目内容 一个正整数 N 的因子中可能存在若干连续的数字。例如 630 可以分解为 3567&#xff0c;其中 5、6、7 就是 3 个连续的数字。给定任一正整数 N&#xff0c;要求编写程序求出…

React-router的创建和第一个组件

需要先学react框架 首先&#xff1a;找到一个文件夹&#xff0c;在文件夹出打开cmd窗口&#xff0c;输入如下图的口令 npx create-react-app demo 然后等待安装 安装完成 接下来进入创建的demo实例 cd demo 然后可以用如下方式打开vscode code . 注意&#xff1a;不要忽略点号与…

Vue--》打造简易直播应用平台项目实战

今天开始使用 vue3 + ts 搭建一个简易直播应用平台项目,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的github上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关注本…

AWS对文本进行语言识别

AWS提供了名为Amazon Comprehend 的服务&#xff0c;它支持对文本进行语言识别。Amazon Comprehend 是一项自然语言处理&#xff08;NLP&#xff09;服务&#xff0c;它可以用于分析文本并提取有关文本内容的信息。 我们可以通过使用 Amazon Comprehend API 轻松地集成这些功能…

机械臂与tftp

机械臂&#xff1a; #include<myhead.h> #define SER_IP "192.168.126.2" #define SER_PORT 8888#define CLI_IP "192.168.252.165" #define CLI_PORT 9999int main(int argc, const char *argv[]) {int cfdsocket(AF_INET,SOCK_STREAM,0);if(cfd-…

支持向量机 SVM | 线性可分:公式推导

目录 一. SVM的优越性二. SVM算法推导小节概念 在开始讲述SVM算法之前&#xff0c;我们先来看一段定义&#xff1a; 支持向量机(Support VecorMachine, SVM)本身是一个二元分类算法&#xff0c;支持线性分类和非线性分类的分类应用&#xff0c;同时通过OvR或者OvO的方式可以应用…

安装Docker及DockerCompose

0.安装Docker Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道…

10.轮廓系数-机器学习模型性能的常用的评估指标

轮廓系数&#xff08;Silhouette Coefficient&#xff09;是评估聚类算法效果的常用指标之一。它结合了聚类的凝聚度&#xff08;Cohesion&#xff09;和分离度&#xff08;Separation&#xff09;&#xff0c;能够量化聚类结果的紧密度和分离度。 背景 1.聚类分析的背景 在…

NDK介绍

NDK&#xff08;Native Development Kit&#xff09;是一个用于在Android平台上开发C和C代码的工具集。它允许开发人员使用C和C编写部分代码&#xff0c;并将其编译为本机代码库&#xff08;.so文件&#xff09;&#xff0c;然后通过JNI在Java应用程序中调用这些本地代码。 ND…