深入理解JVM
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解JVM相关的知识,希望对你有一定的参考价值。
概述JVM
JVM简介:
JVM全称是Java VirtualMachine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM也是有这成套的元素,运算器是当然是交给硬件CPU还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU,比如8086系列的汇编也是可以用在8088上的,但是就不能跑在8051上,而JVM的命令集则是可以到处运行的,因为JVM做了翻译,根据不同的CPU,翻译成不同的机器语言。
JVM中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO,NO,JVM是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。
JVM组成
从这个图中可以看到,JVM是运行在操作系统之上的,它与硬件没有直接的交互
我们再来看下JVM有哪些组成部分,如下图所示:
Runtime Data Area 运行时数据区
Stack 栈
Heap 堆
Mathod Area 方法区
PC Register 寄存器
Native Method Stack 本地方法栈
Execution Engine 执行引擎
Native Interface 本地接口
Native Libraies 本地包集合
Class Loader类加载器
类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java程序,然后通过javac编译成class文件,那怎么才能加载到内存中被执行呢?Class Loader承担的就是这个责任,那不可能随便建立一个.class文件就能被加载的,Class Loader加载的class文件是有格式要求,在《JVM Specification》中式这样定义Class文件的结构:
ClassFile{
u4magic;
u2minor_version;
u2major_version;
u2constant_pool_count;
cp_infoconstant_pool[constant_pool_count-1];
u2access_flags;
u2this_class;
u2super_class;
u2interfaces_count;
u2interfaces[interfaces_count];
u2fields_count;
field_infofields[fields_count];
u2methods_count;
method_infomethods[methods_count];
u2attributes_count;
attribute_infoattributes[attributes_count];
}
需要详细了解的话,可以仔细阅读《JVM Specification》的第四章“The class File Format”,这里不再详细说明。
Execution Engine执行引擎
执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。
Native Interface本地接口
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。
Runtime data area运行数据区
运行数据区是整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java生态系统如此的繁荣,得益于该区域的优良自治,下一章节详细介绍之。
整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!
Java运行数据区
PC程序计数器:是一块较小的内存空间(其实就是指针),它的作用可以看作是当前线程所执行的字节码的行号指示器。每一个线程都有自己私有的程序计数器。如果线程正在执行的是一个JAVA方法,该计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,则计数器值为空(undefined)。此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
JAVA虚拟机栈:也是线程私有的,生命周期和线程相同。每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈道出栈的过程。
JAVA堆:JAVA堆是被所有线程共享的一块内存区域,在JVM启动时创建。其作用就是存放对象实例。
JVM规范规定:所有的对象实例及数组都要在堆上分配。
JAVA堆也是垃圾回收管理的主要区域。
由于现在收集器基本采用分代收集算法,所以JAVA堆还可以细分为:新生代和老年代,再细分还有Eden空间、From Survivor空间、To Survivor空间。
根据JVM规范,JAVA堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,可以是固定大小的,也可以是可扩展的(用-Xmx和-Xms控制),如果堆中没有内存,也无法扩展,抛出OutOfMemoryError
方法区:也是所有线程共享的内存区域。它用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
在JVM规范中被描述为堆的一部分,但却又有一个non-heap的别名。
对于HOTSPOT虚拟机,方法区又被称为永久代(Permanent Generation)
这个区域一样不需要连续的内存,可以选择固定大小或者扩展,还可以选择不实现垃圾回收。确实这个区域的数据一般不参与回收,但这些数据并不一定就是永久存在了,常量池和对类型的卸载也可以成为回收的目标。抛出OutOfMemoryError
运行时常量池:是方法区的一部分。
在编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
Class加载过程
类加载按加载,连接,初始化这个顺序进行的,其中连接又可以细分为验证,准备,解析三个阶段,部分解析可以在初始化开始之后再开始,这样可以支持java的运行时绑定。虽然部分解析可以在初始化阶段开始以后再开始,但是这部分的初始化还是需要当前的部分解析以后才可以初始化。java虚拟机规范中严格规定了有且之友中情况必须立即对类进行初始化:
例:
public class TestLoad{
final static String aaa="aaaaaaa";
int a=add(0);
public static void main(String[] args) {
// TODO Auto-generated method stub
TestLoad test=new TestLoad ();
try {
Class bean = Class.forName("synchronize.TestLoad ");
Field f[] =bean.getDeclaredFields();
for(int i=0;i<f.length;i++)
{
System.out.println("f"+i+":"+f[i].getName());
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("a初始值:"+test.a);
int b=test.add(test.a);
System.out.println(b);
}
int add(int a){
a=a+10;
return a;
}}
下面让我们以TestLoad Class加载过程为例:
1、 加载
首先ClassLoad加载器加载Class文件。主要在方法区中保存了如下信息
① 这个类的完整有效名
② 这个类的父类完整有效名
③ 这个类的修饰符
④ 这个类的接口
⑤ 这个类的Method方法信息
⑥ 这个类的Field信息
⑦ 这个类的常量或static
⑧ 等其他信息
方法的图只画出来方法区中的部分信息。
创建一个Class对象,对象都在heap区。
① 首先根据方法区的Field变量创建一个Field类型的对象
② 根据方法区中的方法创建Method类型的对象
③ 根据需要创建需要的SoftReference的对象,SoftReference是一个泛型容器存放Field和Method的对象
④ 创建一个Class对象,里面变量引用③中创建的对象
2、 连接
我只能自己想个大概,说不出来,请会的朋友告诉我
3、 初始化
在加载和连接后,需要对类的全局变量进行初始化。int a=add(0); add方法在main函数开始执行之前就执行了。
运行过程
栈也叫栈内存,是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。问题出来了:栈中存的是那些数据呢?又什么是格式呢?
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则。
那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(LocalVariables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(FrameData),包括类文件、方法等等。
我的理解:Java运行过程,是PC读取方法区的指令,操作栈中的对象的过程。
以上是关于深入理解JVM的主要内容,如果未能解决你的问题,请参考以下文章