Java虚拟机入门

简介: 前言最近在学习java并发,有些地方涉及到java虚拟机的知识,学习java这么久了,其实还没了解过Java虚拟机,这有点说不过去,所以先来学学习下java虚拟机的基础知识。java虚拟机的概述oracle官方定义的java技术体系包含以下几种:java程序设计语言各种平台的java虚拟机Class文件格式java API类库第三方java类库我们通常所说的JDK(Java Development Kit),他是java开发的最小环境,主要就包含java程序语言设计,java虚拟机与java API类库。

前言

最近在学习java并发,有些地方涉及到java虚拟机的知识,学习java这么久了,其实还没了解过Java虚拟机,这有点说不过去,所以先来学学习下java虚拟机的基础知识。

java虚拟机的概述

oracle官方定义的java技术体系包含以下几种:

  • java程序设计语言
  • 各种平台的java虚拟机
  • Class文件格式
  • java API类库
  • 第三方java类库

我们通常所说的JDK(Java Development Kit),他是java开发的最小环境,主要就包含java程序语言设计,java虚拟机与java API类库。而JRE(Java Runtime Environment),它是java程序运行的标准环境,它其实即使由java API的java SE API子集和java虚拟机组成。
由此我们可以看到java虚拟机在java中式极其重要的。我么可以把java虚拟机看作一个抽象的计算机,他有各种指令集和运行时数据区域。

java虚拟机家族

sun公司发布了许多种虚拟机,这里我们只学习目前比较主流的存活的虚拟机。

HotSpot VM

Oracle JDK 和Open JDK中自带的虚拟机,是最主流和使用最广泛的java虚拟机。一般介绍java虚拟机的文章不作特殊说明,大部分都是介绍HotSpot VM的。HotSpot VM其实不是sun公司开发的,而是一家叫Longview technologies 的公司开发,在1997年被sun公司收购,而sun公司又在2009年被oracle收购

J9 VM

J9 VM 是IBM开发的VM,目前是其主力发展的Java虚拟机。J9 VM的市场定位和HotSpot VM接近,它是一款设计上从服务端到桌面应用再到嵌入式都考虑到的多用途虚拟机,目前J9 VM的性能水平大致跟HotSpot VM是一个档次的

Zing VM

以Oracle的HotSpot VM为基础,改进了许多会影响延迟的细节。最大的三个卖点是:

1.低延迟,“无暂停”的C4 GC,GC带来的暂停可以控制在10ms以下的级别,支持的Java堆大小可以到1TB;
2.启动后快速预热功能。
3.可管理性:零开销、可在生产环境全时开启的、整合在JVM内的监控工具Zing Vision。

java虚拟机的执行流程

当我们执行java程序的时候,他的执行流程可以用下图来说明:
在这里插入图片描述
可以看到,java虚拟机与java语言没什么关系,而只关心特定的.class文件(二进制文件).

java虚拟机的体系结构

这里所讲的体系结构,是指的Java虚拟机的抽象行为,而不是具体的比如HotSpot VM的实现。按照Java虚拟机规范,抽象的Java虚拟机如下图所示。
在这里插入图片描述

Class文件格式

Java文件被编译后生成了Class文件,这种二进制格式文件不依赖于特定的硬件和操作系统。每一个Class文件中都对应着唯一一个类或者接口的的定义信息,但是类或者接口并不一定定义在文件中,比如类和接口可以通过类加载器来直接生成。
ClassFile的文件结构如下所示。
ClassFile {

u4 magic;                                     //魔数,固定值为0xCAFEBABE,用来判断当前文件是能被Java虚拟机处理的Class文件
u2 minor_version;                             //副版本号
u2 major_version;                             //主版本号
u2 constant_pool_count;                       //常量池计数器
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags;                              //类和接口层次的访问标志
u2 this_class;                                //类索引
u2 super_class;                               //父类索引
u2 interfaces_count;                          //接口计数器
u2 interfaces[interfaces_count];              //接口表
u2 fields_count;                              //字段计数器
field_info fields[fields_count];              //字段表
u2 methods_count;                             //方法计数器
method_info methods[methods_count];           //方法表
u2 attributes_count;                          //属性计数器
attribute_info attributes[attributes_count];  //属性表

}

类加载器子系统

类加载器子系统通过多种类加载器来查找和加载Class文件到 Java 虚拟机中。Java虚拟机有两种类加载器:系统加载器和用户自定义加载器。其中系统加载器包括以下三种:

引导类加载器(Bootstrap Class Loader):用C/C++代码实现的加载器,用以加载Java虚拟机运行时所需要的系统类,这些系统类在{JRE_HOME}/lib目录下。Java虚拟机的启动就是通过引导类加载器创建一个初始类来完成的。由于类加载器是使用平台相关的底层C/C++语言实现的, 所以该加载器不能被Java代码访问到。但是,我们可以查询某个类是否被引导类加载器加载过。引导类装载器并不继承java.lang.ClassLoader。
扩展类加载器(Extensions Class Loader):用于加载 Java 的拓展类 ,拓展类一般会放在 {JRE_HOME}/lib/ext/ 目录下,用来提供除了系统类之外的额外功能。
应用程序类加载器(Application Class Loader):该类加载器是用于加载用户代码,是用户代码的入口。应用类加载器将拓展类加载器当成自己的父类加载器,当尝试加载类的时候,首先尝试让拓展类加载器加载,如果拓展类加载器加载成功,则直接返回加载结果Class instance,如果加载失败,则会询问引导类加载器是否已经加载了该类,如果没有,应用类加载器才会尝试自己加载。

用户自定义加载器,则是通过继承 java.lang.ClassLoader类的方式来实现自己的类加载器。
类加载器子系统除了要加载Class文件类到 Java 虚拟机中,还必须负责验证被导入的Class类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。这些动作必须严格按以下顺序进行:
1.装载:查找并加载Class文件。
2.链接:验证、准备、以及解析。

验证:确保被导入类型的正确性。
准备:为类的静态字段分配字段,并用默认值初始化这些字段。
解析:根据运行时常量池的符号引用来动态决定具体值得过程。
3.初始化:将类变量初始化为正确初始值。

数据类型

Java虚拟机与Java语言的数据类型相似,可以分为两类:基本类型和引用类型。Java虚拟机希望编译器在编译期间尽可能的完成类型检查,使得虚拟机在运行期间无需进行类型检查操作。

运行时数据区域

很多人将Java的内存分为堆内存(heap)和栈内存(Stack),这种分发不够准确,Java的内存区域划分实际上远比这复杂。
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为不同的数据区域,根据《Java虚拟机规范(Java SE7版)》的规定,这些数据区域分别为程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区,下面我们来一一的对它们进行介绍。

程序计数器

为了保证程序能够连续地执行下去,处理器必须具有某些手段来确定下一条指令的地址,而程序计数器正是起到这种作用。
程序计数器(Program Counter Register)也叫做PC寄存器,是一块较小的内存空间。在虚拟机概念模型中,字节码解释器工作时就是通过改变程序计数器来选取下一条需要执行的字节码指令,Java虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在一个确定的时刻只有一个处理器执行一条线程中的指令,为了在线程切换后能恢复到正确的执行位置,每个线程都会有一个独立的程序计数器,因此,程序计数器是线程私有的。如果线程执行的方法不是Native方法,则程序计数器保存正在执行的字节码指令地址,如果是Native方法则程序计数器的值则为空(Undefined)。程序计数器是Java虚拟机规范中唯一没有规定任何OutOfMemoryError情况的数据区域。

Java虚拟机栈

每一条Java虚拟机线程都有一个线程私有的Java虚拟机栈(Java Virtual Machine Stacks)。它的生命周期与线程相同,与线程是同时创建的。Java虚拟机栈存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。一个Java虚拟机栈包含了多个栈帧,一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法执行完成,这个栈帧就从Java栈中弹出。我们平常所说的栈内存(Stack)指的就是Java虚拟机栈。
Java虚拟机规范中定义了两种异常情况:

如果线程请求分配的栈容量超过Java虚拟机所允许的的最大容量,Java虚拟机会抛出StackOverflowError。
如果Java虚拟机栈可以动态扩展(大部分Java虚拟机都可以动态扩展),但是扩展时无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的Java虚拟机栈,则会抛出OutOfMemoryError异常。

本地方法栈

Java虚拟机实现可能要用到C Stacks来支持Native语言,这个C Stacks就是本地方法栈(Native Method Stack)。它与Java虚拟机栈类似,只不过本地方法栈是用来支持Native方法服务。如果Java虚拟机不支持Native方法,并且也不依赖于C Stacks,可以无需支持本地方法栈。在Java虚拟机规范中对本地方法栈的语言和数据结构等没有强制规定,因此具体的Java虚拟机可以自由实现它,比如HotSpot VM将本地方法栈和Java虚拟机栈合二为一。
与Java虚拟机栈类似,本地方法栈也会抛出 StackOverflowError和OutOfMemoryError异常

Java堆

Java堆(Java Heap)是被所有线程共享的运行时内存区域。Java堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆存储的对象被垃圾收集器管理,这些受管理的对象无需也无法显示的销毁。从内存回收的角度,Java堆可以粗略的分为新生代和老年代。从内存分配的角度Java堆中可能划分出多个线程私有的分配缓冲区。不管如何划分,Java堆存储的内容是不变的,进行划分是为了能更快的回收或者分配内存。
Java堆的容量可以时固定的,也可以动态的扩展。Java堆的所使用的内存在物理上不需要连续,逻辑上连续即可。
Java虚拟机规范中定义了一种异常情况:

如果在堆中没有足够的内存来完成实例分配,并且堆也无法进行扩展时,则会抛出OutOfMemoryError异常。

方法区

方法区(Method Area)是被所有线程共享的运行时内存区域。用来存储已经被Java虚拟机加载的类的结构信息,包括:
运行时常量池、字段和方法信息、静态变量等数据。方法区是Java堆的逻辑组成部分,它一样在物理上不需要连续,并且可以选择在方法区中不实现垃圾收集。方法区并不等同于永久代,只是因为HotSpot VM使用永久代来实现方法区,对于其他的Java虚拟机,比如J9和JRockit等,并不存在永久代概念。
Java虚拟机规范中定义了一种异常情况:

如果方法区的内存空间不满足内存分配需求时,Java虚拟机会抛出OutOfMemoryError异常。

运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。在2.1 Class文件格式这一小节中我们得知,Class文件不仅包含了类的版本、接口、字段和方法等信息,还包含了常量池,它用来存放编译时期生成的字面量和符号引用,这些内容会在类加载后存放在方法区的运行时常量池中。运行时常量池可以理解为是类或接口的常量池的运行时表现形式。
Java虚拟机规范中定义了一种异常情况:
当创建类或接口时,如果构造运行时常量池所需的内存超过了方法区所能提供的最大值,Java虚拟机会抛出OutOfMemoryError异常

相关文章
|
2月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
71 3
|
24天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
45 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
20天前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
20天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
1月前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
1月前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
81 5
|
1月前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
37 1
|
1月前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
50 3
|
2月前
|
开发框架 IDE Java
java制作游戏,如何使用libgdx,入门级别教学
本文是一篇入门级教程,介绍了如何使用libgdx游戏开发框架创建一个简单的游戏项目,包括访问libgdx官网、设置项目、下载项目生成工具,并在IDE中运行生成的项目。
60 1
java制作游戏,如何使用libgdx,入门级别教学