引言
Java虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
什么是Java虚拟机?
简单的说,一个JVM是一个软件模块,用于执行Java应用字节码并且把字节码转化到硬件,操作系统特殊指令。通过这样做,JVM允许Java程序在第一次编写后可以在不同的环境中执行,并不需要更改原始的代码。Java的可移植性是通往企业应用语言的关键:开发者并不需要为不同平台重写应用代码,因为JVM负责翻译和平台优化。
一个JVM基本上是一个虚拟的执行环境,作为一个字节码指令机器,而用于分配执行任务和执行内存操作通过与底层的交互。
一个JVM同样为运行的Java应用照看动态资源管理。这就意味着它掌握分配和释放内存,在每个平台上保持一致的线程模型,在应用执行的地方用一种适于CPU架构的方式组织可执行的指令。JVM把开发人员从跟踪对象当中的引用,和它们需要在系统中存在多长时间中解放出来。同样的它不用我们管理何时去释放内存——一个像C语言那样的非动态语言的痛点。
你可以把JVM当做是一个专门为Java运行的操作系统;它的工作是为Java应用管理运行环境。一个JVM基本上是一个虚拟的通过与底层的交互的执行环境,作为一个字节码指令机器,而用于分配执行任务和执行内存操作。
JVM组件概述
有很多写JVM内部和性能优化的文章。作为这个系列的基础,我将会总结概述下JVM组件。这个简短的阅览会为刚接触JVM的开发者有特殊的帮助,会让你更想了解之后更深入的讨论。
JAVA虚拟机的体系结构
下图是JAVA虚拟机的结构图,每个Java虚拟机都有一个类装载子系统,它根据给定的全限定名来装入类型(类或接口)。同样,每个Java虚拟机都有一个执行引擎,它负责执行那些包含在被装载类的方法中的指令。
当JAVA虚拟机运行一个程序时,它需要内存来存储许多东西,例如:字节码、从已装载的class文件中得到的其他信息、程序创建的对象、传递给方法的参数,返回值、局部变量等等。Java虚拟机把这些东西都组织到几个“运行时数据区”中,以便于管理。
某些运行时数据区是由程序中所有线程共享的,还有一些则只能由一个线程拥有。每个Java虚拟机实例都有一个方法区以及一个堆,它们是由该虚拟机实例中所有的线程共享的。当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息。然后把这些类型信息放到方法区中。当程序运行时,虚拟机会把所有该程序在运行时创建的对象都放到堆中。
数据类型
Java虚拟机是通过某些数据类型来执行计算的,数据类型可以分为两种:基本类型和引用类型,基本类型的变量持有原始值,而引用类型的变量持有引用值。
Java语言中的所有基本类型同样也都是Java虚拟机中的基本类型。但是boolean有点特别,虽然Java虚拟机也把boolean看做基本类型,但是指令集对boolean只有很有限的支持,当编译器把Java源代码编译为字节码时,它会用int或者byte来表示boolean。在Java虚拟机中,false是由整数零来表示的,所有非零整数都表示true,涉及boolean值的操作则会使用int。另外,boolean数组是当做byte数组来访问的,但是在“堆”区,它也可以被表示为位域。
Java虚拟机还有一个只在内部使用的基本类型:returnAddress,Java程序员不能使用这个类型,这个基本类型被用来实现Java程序中的finally子句。该类型是jsr, ret以及jsr_w指令需要使用到的,它的值是JVM指令的操作码的指针。returnAddress类型不是简单意义上的数值,不属于任何一种基本类型,并且它的值是不能被运行中的程序所修改的。
Java虚拟机的引用类型被统称为“引用(reference)”,有三种引用类型:类类型、接口类型、以及数组类型,它们的值都是对动态创建对象的引用。类类型的值是对类实例的引用;数组类型的值是对数组对象的引用,在Java虚拟机中,数组是个真正的对象;而接口类型的值,则是对实现了该接口的某个类实例的引用。还有一种特殊的引用值是null,它表示该引用变量没有引用任何对象。
Java虚拟机规范中没有任何OutOfMemoryError情况的区域
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型(仅在模型概念里,各种虚拟机可能通过更高效的方式去实现)里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过 线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核),都会只执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行一个是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。
Java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储 局部变量表、操作数栈、动态链接、方法出口 等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
经常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远闭着复杂。这里所指的“栈”就是虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等于对象本身,可能是一个指向对象起止地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress(指向一条字节码指令的地址)。
其中64位长度的 long 和 double 类型的数据会占用2个局部变量空间(Slot),其余数据类型只占用1个。局部变量所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要的在帧中分配的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
在Java虚拟机规范中,对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分虚拟机都可以动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
总结
以 上就是我对大型互联网企业深入理解Java虚拟机结构体系 问题及其优化总结,分享给大家,觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!
最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!
想了解学习Java方面的技术内容以及Java技术视频的内容可加群:722040762 验证码:(666 必过)欢迎大家的加入哟!