Java虚拟机(JVM)介绍

7a34bba3e436432f98a97feac69e20dc.jpgJVM是什么

 

JVM是Java Virtual Machine的缩写。它是一种基于计算设备的规范,是一台虚拟机,即虚构的计算机。

 

JVM屏蔽了具体操作系统平台的信息(显然,就像是我们在电脑上开了个虚拟机一样),当然,JVM执行字节码时实际上还是要解释成具体操作平台的机器指令的。

 

通过JVM,Java实现了平台无关性,Java语言在不同平台运行时不需要重新编译,只需要在该平台上部署JVM就可以了。因而能实现一次编译多处运行。(就像是你的虚拟机也可以在任何安了VMWare的系统上运行)

 

2. JRE和JDK

JRE:Java Runtime Environment,也就是JVM的运行平台,联系平时用的虚拟机,大概可以理解成JRE=虚拟机平台+虚拟机本体(JVM)。类似于你电脑上的VMWare+适用于VMWare的Ubuntu虚拟机。这样我们也就明白了JVM到底是个什么。

 

JDK:Java Develop Kit,Java的开发工具包,JDK本体也是Java程序,因此运行依赖于JRE,由于需要保持JDK的独立性与完整性,JDK的安装目录下通常也附有JRE。目前Oracle提供的Windows下的JDK安装工具会同时安装一个正常的JRE和隶属于JDK目录下的JRE。

 

3. JVM结构

JVM主要包括:程序计数器(Program Counter),Java堆(Heap),Java虚拟机栈(Stack),本地方法栈(Native Stack),方法区(Method Area)

 

详细的结构如下:

 

 

 

现在我来分别介绍一下每一部分的功能。

 

3.1. 程序计数器(PC, Program Counter)

是一个寄存器,可以看作是代码行号指示器,类似于实际计算机里的PC,用于指示,跳转下一条需要执行的命令。Java的基础操作以及异常处理等都十分依赖PC。

 

JVM多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。在一个确定的时刻,一个处理器(或者说多核处理器的一个内核)只会执行一条线程中的命令。因此,为了正常的切换线程,每个线程都会有一个独立的PC,各线程的PC不会互相影响。这个私有的PC所占的这块内存即是线程的“私有内存”。

 

如果线程在执行的是Java方法,那么PC记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的不是Java方法即Native方法,那么PC的值为undefined。

 

PC的内存区域是唯一的没有规定任何OutOfMemoryError的Java虚拟机规范中的区域。

 

3.2. Java虚拟机栈(Stack,Java Virtual Mechine Stacks)

同PC一样(从工作流程图里我们可以看到,实际上,PC也是存在于JVM Stack上的),也是线程私有的,生命周期与线程相同。虚拟机栈描述Java方法执行的内存模型,每个方法被执行时都会创建一个栈帧(Stack Frame),栈帧会利用局部变量数组存储局部变量(Local Variables),操作栈(Operand Stack),方法出口(Return Value),动态连接(Current Class Constant Pool Reference)等信息。

 

局部变量数组存储了编译可知的八个基本类型(int, boolean, char, short, byte, long, float, double),对象引用(根据不同的虚拟机实现可能是引用地址的指针或者一个handle),returnAddress类型。64位的long和double会占用两个Slot,其余类型会占用一个Slot。在编译期间,局部变量所需的空间就会完成分配,动态运行期间不会改变所需的空间。

 

操作栈在执行字节码指令时会被用到,这种方式类似于原生的CPU寄存器,大部分JVM把时间花费在操作栈的花费上,操作栈和局部变量数组会频繁的交换数据。

 

动态连接控制着运行时常量池和栈帧的连接。所有方法和类的引用都会被当作符号的引用存在常量池中。符号引用是实际上并不指向物理内存地址的逻辑引用。JVM 可以选择符号引用解析的时机,一种是当类文件加载并校验通过后,这种解析方式被称为饥饿方式。另外一种是符号引用在第一次使用的时候被解析,这种解析方式称为惰性方式。无论如何 ,JVM 必须要在第一次使用符号引用时完成解析并抛出可能发生的解析错误。绑定是将对象域、方法、类的符号引用替换为直接引用的过程。绑定只会发生一次。一旦绑定,符号引用会被完全替换。如果一个类的符号引用还没有被解析,那么就会载入这个类。每个直接引用都被存储为相对于存储结构(与运行时变量或方法的位置相关联的)偏移量。

 

对Java虚拟机栈这个区域,Java虚拟机规范规定了两种异常:

 

线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverFlow异常。

对于支持动态扩展的虚拟机,当扩展无法申请到足够的内存时会抛出OutOfMemory异常。

3.3. 本地方法栈(Native Stack)

本地方法栈如其名字,和Java Virtual Machine Stack其实极为类似,只是执行的是Native方法,为Native方法服务。在JVM规范中,没有对它的实现做具体规定。

 

3.4. Java 堆(Heap, Garbage Collection Heap)

 

 

Java堆是被所有线程共享的一块区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存(随着技术的发展,已不绝对)。

 

Java堆是垃圾收集器管理的主要区域,因而也被称为GC堆。收集器采用分代回收法,GC堆可以分为新生代(Yong Generation)和老生代(Old Generation)。新生代包括Eden Space和Survivor Space。但无论哪个区域,如何划分,存储的都是Java对象实例,进一步的划分是为了更好的回收内存或快速的分配内存。

 

根据Java虚拟机规范,堆所在的物理内存区间可以是不连续的,只要逻辑连续就可以。实现时既可以是固定大小,也可以是可扩展的。如果堆无法扩展时,就会抛出OutOfMemoryError。

 

3.5. 方法区(Method Area)

方法区和Java堆类似,也属于各线程共享的内存区域。用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码数据等。它属于非堆区(Non Heap),和Java堆区分开。对于存在永久代(Permanent)概念的虚拟机(HotSpot)而言,方法区存在于永久代。Java虚拟机规范对方法区的规定很宽松,甚至可以不实现GC。不过并非进入方法区的数据就会永久存在了,这块区域的内存回收主要为常量池的回收和类型的卸载。这个区域的回收处理不善也会导致严重的内存泄漏。

 

当方法区无法满足内存分配需求时也会抛出OutOfMemoryError。

 

3.6. 代码缓存(Code Cache)

用于编译和存储那些被 JIT 编译器编译成原生代码的方法。

 

3.7. 类信息(Class Data)

类信息存储在方法区,其主要构成为运行时常量池(Run-Time Constant Pool)和方法(Method Code)。

 

一个编译后的类文件包括以下结构:

 

结构 解释

magic, minor_version, major_version 类文件的版本信息和用于编译这个类的 JDK 版本。

constant_pool 类似于符号表,尽管它包含更多数据。下面有更多的详细描述。

access_flags 提供这个类的描述符列表。

this_class 提供这个类全名的常量池(constant_pool)索引,比如org/jamesdbloom/foo/Bar。

super_class 提供这个类的父类符号引用的常量池索引。

interfaces 指向常量池的索引数组,提供那些被实现的接口的符号引用。

fields 提供每个字段完整描述的常量池索引数组。

methods 指向constant_pool的索引数组,用于表示每个方法签名的完整描述。如果这个方法不是抽象方法也不是 native 方法,那么就会显示这个函数的字节码。

attributes 不同值的数组,表示这个类的附加信息,包括 RetentionPolicy.CLASS 和 RetentionPolicy.RUNTIME 注解。

3.8. 运行时常量池(Run-Time Constant Pool)

运行时常量池是方法区的一部分。Class文件中有类的版本,字段,方法,接口等描述信息和用于存放编译期生成的各种字面量和符号引用。这部分内容将在类加载后存放到方法区的运行时常量池中。Java虚拟机规范对Class的细节有着严苛的要求而对运行时常量池的实现不做要求。一般来说除了翻译的Class,翻译出来的直接引用也会存在运行时常量池中。

 

运行时常量池具备动态性,即运行时也可将新的常量放入池中。比如String类的intern()方法。

 

常量池无法申请到足够的内存分配时也会抛出OutOfMemoryError。

 

3.9. 直接内存(Direct Memory)

直接内存并不在Java虚拟机规范中,不是Java的一部分,但是也被频繁使用并可能导致OutOfMemoryError。Native函数库可以直接分配堆外内存,通过存储在Java堆里的DirectDataBuffer对象作为这块内存的引用进行操作。这样做在一些场景中可以显著提高性能。

 

直接内存是堆外内存,自然不受Java堆大小的限制,但是可能受实体机内存大小的限制。如果内存各部分总和大于实体机的内存时,也会报出OutOfMemoryError。

 

4. Java垃圾回收

将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。

 

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

 

强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)。

软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)。

弱引用:在GC时一定会被GC回收。

虚引用:由于虚引用只是用来得知对象是否被GC。

5. JVM线程与原生线程的关系

JVM允许一个程序使用多个并发线程,Hotspot JVM中Java的线程与原生操作系统的线程是直接映射关系。即当线程本地存储、缓冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程。Java 线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可用的 CPU 上。当原生线程初始化完毕,就会调用 Java 线程的 run() 方法。run() 返回时,被处理未捕获异常,原生线程将确认由于它的结束是否要终止 JVM 进程(比如这个线程是最后一个非守护线程)。当线程结束时,会释放原生线程和 Java 线程的所有资源。

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

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

相关文章

若依官方前端手册 小笔记

提供确认窗体信息 this.$modal.confirm(确认信息).then(function() {... }).then(() > {... }).catch(() > {}); 提供遮罩层信息 // 打开遮罩层 this.$modal.loading("正在导出数据,请稍后...");// 关闭遮罩层 this.$modal.closeLoading(); 验证…

ViDeNN: Deep Blind Video Denoising

ViDeNN: Deep Blind Video Denoising 摘要 We propose ViDeNN: a CNN for Video Denoising without prior knowledge on the noise distribution (blind denoising). The CNN architecture uses a combination of spatial and temporal filtering, learning to spatially deno…

记录stm32c8t6使用TIM4_CH1、TIM4_CH2输出PWM波控制编码电机出现的问题

由于之前是使用PB9、PB7引脚即TIM4_ch3\TIM4_ch4,由于项目更改为c8t6的PB、PB7引脚(TIM4_ch3\TIM4_ch4) 改为配置后发现只有一边的轮子可以转到,明明配置没什么问题,编译也没有报错,最后将pwm的调制模式更改…

git bash设置字体大小

背景 git bash默认字体太小了,每次读信息都要伸头盯着屏幕,很不自在,不符合我的风格,so let’s do it! 修改前的git bash: 正确的打开方式 1、在任意目录下,右键选择“Git Bash Here”&…

【UniApp开发小程序】请求包创建+登录功能实现

文章目录 请求包创建创建文件夹请求工具request.js 登录功能实现请求方法页面涉及知识点错误提示前端校验设置token到客户端缓存中路由跳转 请求包创建 小程序的数据需要向后端发请求进行获取,为了简化后续的开发,需要创建一个包专门存放所有发请求的js…

MySQL环境配置

MySQL在centos7环境安装 一.卸载不要的环境二.获取mysql官方yum源三.安装mysql服务四.mysql登陆五.设置配置文件my.cnf六.设置开机启动【可以不设】七.常见问题 安装与卸载中,⽤⼾全部切换成为root,⼀旦安装,普通⽤⼾也能使⽤。 一.卸载不要…

LVGL开发:配置模拟器学习LVGL V8.3

文章目录 模拟器配置常用控件学习基本知识WidgetEvents 输入设备ImagesScreen 参考 模拟器配置 LVGL支持多种IDE下配置模拟器: 在WINDOWS下面,大家最常使用的是VS2019,为了和大家保持一致,这里也使用VS2019进行配置。 首先&…

[Android 13]Binder系列--获取服务

获取服务 hongxi.zhu 2023-7-8 一、客户端发起获取服务 以SurfaceFlinger进程中获取InputFlinger服务为例 frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp sp<IBinder> input(defaultServiceManager()->getService(String16("inputflinger"…

使用Pycharm

本人没有单独安装python&#xff0c;而是直接安装了anaconda 使用Pycharm创建项目 项目取名为HelloWorld&#xff0c;环境使用前面安装的anaconda pycharm安装模块的方法&#xff1a; 打开Pycharm>File > Settings>Project: Python>Project Interpreter

【大数据之Hive】二十三、HQL语法优化之数据倾斜

1 数据倾斜概述 数据倾斜指参与计算的数据分布不均&#xff0c;即某个key或者某些key的数据量远超其他key&#xff0c;导致在shuffle阶段&#xff0c;大量相同key的数据被发往同一个Reduce&#xff0c;导致该Reduce所需的时间远超其他Reduce&#xff0c;成为整个任务的瓶颈。  …

安装部署rancher2.7.0,然后导入K8S集群,管理集群

1. 安装rancher2.7.0 docker run -d --name rancher --restartunless-stopped --privileged -p 80:80 -p 443:443 -v /var/lib/rancher:/var/lib/rancher/ -v /var/log/rancher/auditlog:/var/log/auditlog rancher/rancher:v2.7.02.浏览器登录 2.1 利用默认账号登…

SOPC之NiosⅡ系统(三)

常用NIOS Ⅱ组件概括 目录 1.定时器Timer 1.1 预定义硬件配置 1.2 超时周期Timeout poriod 1.3 计数器大小Timer counter Size 1.4 寄存器Registers 2.串口UART 2.1 基础设置Basic settings 2.1.1 奇偶校验Parity 2.1.2 数据为Data bits 2.1.3 停止位Stop bits 2.1…