快速上手JVM系列(一)——进一步认识Java与JVM
快速上手JVM系列(二)——JVM类加载机制
快速上手JVM系列(三)——JVM内存结构与堆区GC机制
🚀 引言:学习JVM的必要性
通常情况下,一个程序员只要了解了必要的Java类库API、Java语法,学习适当的第三方开发框架,就已经基本满足日常开发的需要了。虚拟机会在用户不知不觉中完成对硬件平台的兼容及对内存等资源的管理工作
因此,了解虚拟机的运作并不是普通开发人员必备的,或者说首要学习的知识
然而,凡事都具备两面性。随着Java技术的不断发展,它已被应用于越来越多的领域之中。其中一些领域,如互联网、能源、金融、通信等,对程序的性能、稳定性和扩展性方面会有极高的要求。一段程序很可能在10个人同时使用时完全正常,但是在10000个人同时使用时就会缓慢、死锁甚至崩溃。毫无疑问,要满足10000个人同时使用,需要更高性能的物理硬件,但是在绝大多数情况下,提升硬件性能无法等比例提升程序的运行性能和并发能力,甚至有可能对程序运行状况没有任何改善。这里面有Java虚拟机的原因:为了达到“所有硬件提供一致的虚拟平台”的目的,牺牲了一些硬件相关的性能特性。更重要的是人为原因:如果开发人员不了解虚拟机诸多技术特性的运行原理,就无法写出最适合虚拟机运行和自优化的代码
其实,目前商用的高性能Java虚拟机都提供了相当多的优化参数和调节手段,用于满足应用程序在实际生产环境中对性能和稳定性的要求。如果只是为了入门学习,让程序在自己的机器上正常工作,那么这些特性可以说是可有可无的;但是,如果用于生产开发,尤其是大规模的、企业级的生产开发,就迫切需要开发人员中至少有一部分人对虚拟机的特性及调节方法具有很清晰的认识。所以在Java开发体系中,对架构师、系统调优师、高级程序员等角色的需求一直都非常大学习虚拟机中各种自动运作特性的原理也成为Java程序员成长路上最终必然会接触到的一课
———摘录自《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
文章目录
进一步认识Java
相信各位来阅读JVM之前已经对Java语言有了不浅的理解,所以这里对于Java的安装、环境变量配置、语法特性等基础就不再多做描述
Oracle JDK与Open JDK
背景:
在编写这篇文章时,Oracle JDK
已经更新到JKD18,最新的LTS(长期支持)版本为JDK17
,市场最多使用的仍然是JDK8(主流的 JDK 8 在2019年01月之后就被宣布停止更新了)
Oracle JDK
Oracle JDK 由 Oracle 维护和开发。它符合 OpenJDK 规范,但不是开源代码。Oracle JDK 在 JVM 响应能力和生产力方面要好得多。由于其对企业客户的重要性,它更注重稳定性
OpenJDK
OpenJDK是开放源代码,由Oracle维护和开发,但允许社区和其他公司参与开发,如Red Hat、Azul Systems、IBM、Apple Inc等。OpenJDK既是一种JDK产品,也是一种规范,任何想要使用OpenJDK创建新变体的公司或组织都必须遵守这些规范
Oracle JDK和OpenJDK的异同点
Oracle JDK
比OpenJDK
更稳定,在JDK11中,我们可以认为OpenJDK和OracleJDK代码实质上已经完全一致的程度OpenJDK
和Oracle JDK
的代码几乎相同,但Oracle JDK
有更多的类和一些错误修复- 二者共同使用
Hotspot
虚拟机(它采用解释器与编译器并存的架构) - 许可协议不同:Oracle JDK 根据二进制代码许可协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可
如何对二者做出选择?
Oracle JDK的特点是单版本长期支持,如开发企业/商业软件,一般建议选择Oracle JDK,因为它经过了彻底的测试和稳定OpenJDK的特点是更新频繁,实现快速迭代和高效试错,为Oracle JDK LTS版本打下基础
商用收费,学习研究免费。如需在开源基础上开发及问题优化维护或不那么注重稳定性,则可以选择OpenJDK
Java语言的地位
每年都有很多新、旧编程语言的兴起躁动与消失,说明必然有其需求动力所在,譬如互联网之于JavaScript、人工智能之于Python,微服务风潮之于Golang等。大家都清楚不太可能有哪门语言能在每一个领域都尽占优势,Java已是距离这个目标最接近的选项,但若“天下第一”还要百尺竿头更进一步的话,似乎就只能忘掉Java语言本身,踏入无招胜有招的境界
附:TIOBE Index for April 2022(编程语言排行榜,每个月更新,只截取前10名)
Java上层框架与JVM的关系
Java是目前用户最多、使用范围最广的软件开发技术,Java的技术体系主要由支撑Java程序运行的虚拟机、提供各开发领域接口支持的Java类库、Java编程语言及许许多多的第三方Java框架(如Spring、MyBatis等)构成。在国内,有关Java类库API、Java语言语法及第三方框架的技术资料和书籍非常丰富,相比而言,有关Java虚拟机的资料却显得异常贫乏
这种状况很大程度上是由Java开发技术本身的一个重要优点导致的:在虚拟机层面隐藏了底层技术的复杂性以及机器与操作系统的差异性。运行程序的物理机千差万别,而Java虚拟机则在千差万别的物理机上面建立了统一的运行平台,实现了在任意一台Java虚拟机上编译的程序,都能在任何其他Java虚拟机上正常运行。这一极大的优势使得Java应用的开发比传统C/C++应用的开发更高效快捷,程序员可以把主要精力放在具体业务逻辑,而不是放在保障物理硬件的兼容性上
即便如此,Loki认为正如大树扎根一样,对于内功和底层的修炼,才是我们能立足于越来越内卷的环境的根本
- 数据结构与算法
- 计算机网络
- 计算机组成原理
- 操作系统原理
- 数据库系统
Java代码的执行流程
初识JVM——跨语言的平台
首先理解JVM的两个特性
跨平台
通过不同平台指令集、不同机器码下的虚拟机,可以运行同样的字节码文件,达到同样的结果,以此达到虚拟机跨平台的特性
语言无关性
通过不同的编译器,可以编译出符合统一字节码规范的字节码文件,交由JVM处理,屏蔽了不同编程语言的特性
解释Java为什么是半编译半解释型语言
我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是半编译半解释型语言
虚拟机是什么
虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。
Visual Box
,VMware
就属于系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台- 程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令
无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中
JVM发展历程
由于OracleJDK/OpenJDK
在市场占有率上的绝对优势,它默认的HotSpot虚拟机不可避免地成为我们主要学习的对象,因此Loki的这篇JVM学习博客将以HotSpot虚拟机为学习目标,进行展开学习
(注:下文中JVM默认指Hotspot虚拟机)
来简单看一看JVM虚拟机家族的其他部分成员
-
虚拟机始祖:
Sun Classic/ExactVM
以今天的视角来看,Sun Classic虚拟机的技术已经相当原始,这款虚拟机的使命也早已终结。但仅凭它“世界上第一款商用Java虚拟机”的头衔,就足够有令历史记住它的理由
-
武林盟主:
HotSpot VM
它是Sun/OracleJDK和OpenJDK中的默认Java虚拟机,也是目前使用范围最广的Java虚拟机,通过热点代码探测技术在响应时间与执行性能中取得平衡
-
小家碧玉:
Mobile/EmbeddedVM
Sun/Oracle公司面对移动和嵌入式市场所研发的虚拟机,目前国内市场几乎看不到应用了 -
专注于服务器端应用:
JRockit
(已经被Oracle收购,整合到Hotspot中了)它可以不太关注程序启动速度,因此JRockit内部不包含解释器实现,全部代码都靠即时编译器编译后执行
大量的行业基准测试显示,JRockit JVM是世界上最快的JVM
JVM架构模型
Java编译器输入的指令流基本上是一种基于栈的指令集架构(我们学习的Hotspot虚拟机就是这个架构),另外一种指令集架构则是基于寄存器的指令集架构。具体来说:这两种架构之间的区别
基于栈式架构的特点
设计和实现更简单,适用于资源受限的系统
避开了寄存器的分配难题:使用零地址指令方式分配
指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现
不需要硬件支持,可移植性更好,更好实现跨平台
基于寄存器架构的特点
典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虚拟机
指令集架构则完全依赖硬件,可移植性差
性能优秀和执行更高效
花费更少的指令去完成一项操作
在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主
举个栗子:int a = 2 + 3
基于栈的计算流程
iconst_2 //常量2入栈
istore_1
iconst_3 // 常量3入栈
istore_2
iload_1
iload_2
iadd //常量2/3出栈,执行相加
istore_0 // 结果5入栈
基于寄存器的运算流程
mov eax,2 //将eax寄存器的值设为1
add eax,3 //使eax寄存器的值加3
由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令
初探JVM内存结构
JVM规范指出:JVM的内存空间分为 5 个部分,不同虚拟机的实现有所不同
注:
- 方法区和堆线程共享
- Java栈、本地方法栈、程序计数器线程私有
JVM的生命周期
虚拟机的启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。
虚拟机的执行
一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。
程序开始执行时他才运行,程序结束时他就停止。
执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程。
虚拟机的退出
有如下的几种情况:
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统用现错误而导致Java虚拟机进程终止
某线程调用Runtime类或system类的exit方法,或Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作。
除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载 Java虚拟机时,Java虚拟机的退出情况。