1.什么是JVM?
JVM是Java Virtual Machine(Java虚拟机)的缩写,
虚拟机是指通过软件模拟具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统
。JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。 Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
从上图可以看出,JVM是运行在操作系统之上的,它与硬件没有直接的交互。
常见的虚拟机:
VMWare
Visual Box
JVM
VMWare、Visual Box都是使用软件模拟物理机CPU的指令集,JVM使用软件模拟Java字节码的指令集,使用最为广泛的JVM为HotSpot 。
2.JRE/JDK/JVM是什么关系?
JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
JDK(Java Development Kit)是程序开发者用来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
3.JVM执行程序的过程
3.1 JAVA程序运行:
源码chegva.java --> javac编译器 --> 字节码chegva.class --> JVM --> 机器语言(依赖于不同平台) --> 执行
3.2 JVM执行程序:
加载.class文件 --> 管理并分配内存 --> 执行垃圾收集
操作系统装入JVM是通过jdk中Java执行程序来完成,通过下面4步来完成JVM环境:
创建JVM装载环境和配置
装载JVM.dll
初始化JVM.dll并挂接到JNIENV(JNI调用接口)实例
调用JNIEnv实例装载并处理class类。
3.3 JVM的生命周期
JVM实例对应了一个独立运行的java程序它是进程级别
a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点
b) 运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程
c) 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出
JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的
多线程的Java应用程序:
为了让每个线程正常工作就提出了
程序计数器(Program Counter Register)
,每个线程都有自己的程序计数器这样当线程执行切换的时候就可以在上次执行的基础上继续执行,仅仅从一条线程线性执行的角度而言,代码是一条一条的往下执行的,这个时候就是Program Counter Register,JVM就是通过读取Program Counter Register的值来决定该线程下一条需要执行的字节码指令,进而进行选择语句、循环、异常处理等线程:
从OOP而言,相当于一个对象,该对象中具有执行代码,同时也有要处理的数据,数据包含Thread工作时候要访问的数据,同时也包含现在的Stack,在Stack中包含了Thread本地的数据,也包含了拷贝的全局数据;从面向过程的角度而言:
线程 = 代码 + 数据
Main Memory:全局共享内存空间
4.JVM的体系结构
JVM是按照运行时数据的存储结构来划分内存结构的,JVM在运行java程序时,将它们划分成几种不同格式的数据,分别存储在不同的区域,这些数据统一称为运行时数据。 运行时数据包括Java程序本身的数据信息和JVM运行Java需要的额外数据信息。
Class Loader 类加载器
类加载器的作用加载类文件(.class)文件到内存。Class Loader加载的class文件是有格式要求的,Class Loader只管加载,只要符合文件结构就加载,至于能不能运行,则交由Execution Engine负载。
Execution Engine 执行引擎
执行字节码或本地方法,执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。
Native Interface 本地接口
本地接口的作用是调用不同的编程语言为Java所用。比如说融合C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。
Runtime data area 运行数据区
线程私有:程序计数器、Java虚拟机栈、本地方法栈
线程公用:Java堆、方法区
PC Register 程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。
每个线程拥有一个PC寄存器
在线程创建时创建
指向下一条指令的地址
执行本地方法时,PC的值为undefined
Stack 栈
栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部变量,部分返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。栈也叫栈内存,是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则。
那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(Frame Data),包括类文件、方法等等。光说比较枯燥,我们画个图来理解一下Java栈,如下图所示:
图示在一个栈中有两个栈帧,栈帧2是最先被调用的方法,先入栈,然后方法2又调用了方法1,栈帧1处于栈顶的位置,栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,线程结束,栈释放。
Stack区域属于线程私有,每个线程都会包含一个Stack区域,Stack区域中包含基本的数据类型以及对象的引用,其它线程均不能直接访问该区域
分类三大部分:基本数据类型区域、操作指令区域、上下文等
栈由一系列帧组成(因此Java栈也叫做帧栈)
帧保存一个方法的局部变量、操作数栈、常量池指针
每一次方法调用创建一个帧,并压栈
Heap 堆内存
它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。
(1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
(2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
(3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
(4) 所有新创建的Object 都将会存储在新生代Yong Generation中。如果Young Generation的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。新的Object总是创建在Eden Space。
存储的全部是Object对象实例,对象实例中一般都包含其数据成员以及该对象对应的Class信息
一个JVM实例在运行的时候只有一个Heap区域,该区域被所有的线程共享
和程序开发密切相关
应用系统对象都保存在Java堆中
所有线程共享Java堆
对分代GC来说,堆也是分代的
GC的主要工作区间
Method Area方法区
方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。方法区包含Constant Pool。
方法区域又名静态成员区域,包含整个程序的Class,static成员等
方法区域所有的线程共享
保存装载的类信息
类型的常量池
字段,方法信息
方法字节码
通常和永久区(Perm)关联在一起
Runtime Constant Pool 运行时常量池
存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。
Native Method Stack 本地方法栈
JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
参考: