深入理解JVM

概述JVM

JVM简介:

JVM全称是Java VirtualMachineJava虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM也是有这成套的元素,运算器是当然是交给硬件CPU还处理了,只是为了适应一次编译,随处运行的情况,需要做一个翻译动作,于是就用了JVM自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU,比如8086系列的汇编也是可以用在8088上的,但是就不能跑在8051上,而JVM的命令集则是可以到处运行的,因为JVM做了翻译,根据不同的CPU,翻译成不同的机器语言。

JVM中我们最需要深入理解的就是它的存储部分,存储?硬盘?NONOJVM是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。

JVM组成

 

从这个图中可以看到,JVM是运行在操作系统之上的,它与硬件没有直接的交互

深入理解JVM_第1张图片

我们再来看下JVM有哪些组成部分,如下图所示:

 深入理解JVM_第2张图片

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加载过程为例:

 深入理解JVM_第3张图片

1、 加载

首先ClassLoad加载器加载Class文件。主要在方法区中保存了如下信息

① 这个类的完整有效名

② 这个类的父类完整有效名

③ 这个类的修饰符

④ 这个类的接口

⑤ 这个类的Method方法信息

⑥ 这个类的Field信息

⑦ 这个类的常量或static

⑧ 等其他信息

方法的图只画出来方法区中的部分信息。

创建一个Class对象,对象都在heap区。

① 首先根据方法区的Field变量创建一个Field类型的对象

② 根据方法区中的方法创建Method类型的对象

③ 根据需要创建需要的SoftReference的对象,SoftReference是一个泛型容器存放FieldMethod的对象

④ 创建一个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)