深入解析java虚拟机-jvm运行机制

时间:2023-03-08 16:48:21
转自oschina

一:JVM基础概念

JVM(Java虚拟机)一种用于计算设备的规范,可用不同的方式(软件或硬件)加以实现。编译虚拟机的指令集与编译微处理器的指令集非常类似。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 

         Java虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。

带着几个问题我们来阅读下面的文章,希望能够对jvm运行机制不是很清楚的朋友有一点点的帮助。如果对jvm运行机制非常了解可以留言 提下意见和你认为不对的地方。

  1. jvm是怎么运行的?
  2. jvm运行时内存是怎么分配的?
  3. jvm每个区在jvm体系中都充当着什么角色?
  4. java文件里面的代码最终都存储在jvm的那些区?

二:JVM体系结构

Java虚拟机,java源文件(.java)通过编译器生成字节码文件(.class),字节码文件(.class)通过JVM(Java虚拟机)中的解释器再翻译成特定机器上的机器码。 

编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。当一个程序从开始运行一个程序,这时虚拟机就开始实例化了。多个程序启动就会存在多个虚拟机实例。程序退出或者关闭。则虚拟机实例消亡。多个虚拟机实例之间数据不能共享。

如下图所示,JVM的体系结构包含几个主要的子系统和内存区:

  1. 垃圾回收器(Garbage Collection):负责回收堆内存(Heap)中没有被使用的对象,即这些对象已经没有被引用了。
  2. 类装载子系统(Classloader Sub-System):除了要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。
  3. 执行引擎(Execution Engine):负责执行那些包含在被装载类的方法中的指令。
  4. 运行时数据区(Java Memory Allocation Area):又叫虚拟机内存或者Java内存,虚拟机运行时需要从整个计算机内存划分一块内存区域存储jvm需要用到的东西。而这个运行时数据区里面又会分为许多的小区。每个区都有自己不同的职责。后面会讲到每个区的作用和存储的内容。

深入解析java虚拟机-jvm运行机制

三:jvm运行时数据区

  • 程序计数器 (Program Counter Register):

是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实 现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

  • 栈 (stack):

这块区域是线程私有的,它的生命周期和和线程一样。在jvm中。每个方法执行的时候jvm都会创建一个栈帧。用来存储局部变量、操作栈、动态栈和方法返回等信息。每一个方法从调用到结束对应着一个栈帧在虚拟机里面的入栈和出栈。

栈里面存储了编译器已知的八种基本数据类型,String不在里面。后面会对String类型进行详细的介绍。(boolean、char、double、int、long、byte、float、short)。 

在用java关键字 new 一个对象时。比如(ControllerHelper helper = new ControllerHelper ())时。stack内存中存储的是一个helper栈帧地址引用。其地址指向的是heap中的内存区域。如下图: 



                                                          深入解析java虚拟机-jvm运行机制

注意: 存Heap地址引用只对于sun 公司的虚拟机<HotSpot>。其他公司的可能会不一样。存的可能是句柄。

  • 本地方法区 (Native Method Area)



       这块区域在jvm运行内存中职责就相对比较少了。只是执行Native 方法。如果这个区的内存不足也是会抛出*Error 和 OutOfMemoryError 异常。
  • 堆 (Heap)

这块区域可以说是jvm中最大的一块区域了。几乎所有的对象和数据都是存在这个区域。这块区域也是线程共享的。也是 gc 主要的回收区。可以从我第一个图看出Heap区中还可以分为新生代(Young Generation)和老年代(Old Generation)。gc每隔一段时间就会对新生代进行垃圾回收。在分配对象遇到内存不足时,先对新生代进行GC(Young GC);当新生代GC之后仍无法满足内存空间分配需求时, 才会对整个堆空间以及方法区进行GC(Full GC)。而新生代又可以分为一个Eden空间和两个Survivor
(From Survivor 和 To Survivor ) 空间。新生代中的E区和S区又有不同的职责。 

       E区:gc 触发比较频繁的区域。存储的是新new的对象。几乎所有的对象都会经过E区。如果gc过后对象还没死亡就会把未死亡的对象存储到S区。 

       S区:Survivor 区作为Eden区和old(老年代)的缓存。它是可以向老年代转移活动对象的实例。

java 堆它在屋里内存上可以是不连续的内存空间。只要是逻辑上是连续的就可以了。查看程序新生代、老年代信息可以执行命令 jstat -gcutil 6912 500

  • 方法区 (Method Area)

方法区和堆一样也是线程共享的区域,它主要是存储被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。它不属于Heap区域中的一部分。对于sun公司的HotSpot虚拟机来说。gc也会对这块区域进行垃圾回收。但是对于heap的垃圾回收而已。这块区域垃圾回收比较少。String str=”is string”。str就是存在方法区。Sample.class.getSimpleName() 方法就是从方法区中区得的结果。

如果细分方法区还可以细分为运行常量池 (Runtime Constant Pool) 。它主要存储class文件中的版本、字段、方法、接口等描述信息。运行常量池还可以分为信息常量池 (Constant Pool Table)。它主要是存储编译器生成的各种字面量和符号引用。

并不是所有的常量都只能在编译期产生。对于String类型的 intern( )方法就可以在运行时把变量存入方法区。当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。