JVM内存结构

1、JVM的概念

 

       虚拟机是模拟执行某种指令集体系结构(ISA)的软件,是对操作系统和硬件的一种抽象。java虚拟机就是对计算机系统结构的一种简单模拟。

        Java虚拟机(JVM)是由Java虚拟机规范定义的,其上运行的是字节码指令集。这种字节码指令集包含一个字节的操作码(opcode),零至多个操作数(oprand),虚拟机规范明确定义了每种字节码指令完成的功能是什么以及需要多少个操作数。Java虚拟机上运行的class文件,这个文件中包含字节码指令流以及类定义的信息,所以Java虚拟机规范还定义了class文件的格式(精确到每个字节)。所以实现Java虚拟机的两个要素是字节码指令集和class文件格式,Java虚拟机的实现者只要以正确方式读取class文件中的每一条字节码指令,并按照要求实现字节码指令的功能就可以实现JVM。
        目前常用的商用JVM主要有:Sun HotSpot,BEA JRocket以及IBM J9。其中由于BEA和Sun已经被Oracle收购,所以Oracle拥有当今世界上最流行的两个JVM,并有传言说Oracle将在Java8时将两个虚拟机合并,各取所需,取长补短,打造一个更加精湛的JVM。HotSpot会以解释+即时编译执行代码,HotSpot在解释执行字节码的时候,会探测热点(hotspot)代码,然后将这部分代码编译为本地代码,之后将直接运行本地代码,而不是解释,这样会有效提高虚拟机性能。JRocket主要是定位于服务器应用,所以不关注虚拟机的启动速度,它会将所有代码即时编译为本地代码执行,JRocket的垃圾收集器具有很高的收集效率。J9定位与HotSpot类似,专注于桌面应用和服务器应用,主要是针对IBM的各种Java产品。
 
2、java语言与虚拟机
 
        java源代码,即.java文件,通过javac编译为.class文件。.class文件load到JVM 中,JVM底层会通过字节码解释器或者即时编译器(JIT Compiler)执行.class文件中的字节码指令。JVM是运行在操作系统之上的,操作系统又通过指令集调用底层硬件服务执行其上的各种软件。如下图所示:

JVM内存结构
 
       java语言并不是只能运行在JVM之上,只要实现了对应的编译器,它可以直接运行在各种操作系统平台上;同样JVM也并不是只能运行java语言,JVM关注的只是字节码能否被正确执行,而不会关心是由哪种语言转换而来的字节码。
 
3、Java虚拟机体系结构
JVM内存结构
       当java虚拟机运行一个程序时,它需要内存来存储许多东西。例如,字节码、class文件中的信息、对象、方法参数、返回值及运算中间结果等,而这些东西都存储在运行时数据区,便于管理。不同虚拟机的实现对应的内存分配、管理会有所不同。
        每个java虚拟机实例都有一个方法区和堆,它们由该虚拟机实例共享,当虚拟机装载一个class文件时,它会将其中的类型信息存放到方法区,当程序运行时创建的对象都会放到堆中。当每一个线程被创建时,它都会分配PC计数器、Java栈。PC寄存器用来指向下一条将被执行的指令,Java栈用来存储该线程中java方法调用的状态(参数、返回值、中间结果等)。而本地方法的调用是依赖于具体实现的方式存储在本地方法栈中或其它寄存器、特定实现的存储中。
        1>方法区
        被装载类型的信息都会存储在这块内存中。它会存储以下信息:
        1、类型的全限定名
        2、类型的直接超类的全限定名
        3、类型是类类型还是接口类型
        4、类型的访问修饰符
        5、任何直接超类接口的全限定名的有序列表
        6、常量池、字段信息、方法信息、类静态变量
        7、类ClassLoader的引用
        8、Class类的引用
 
       2> 堆
        Java程序在运行时所创建的所有类实例或数组都放在同一个堆中。一个java虚拟机实例中只有存在一个堆,所以所有线程共享一个堆。所以同一个JAVA程序的多个线程共享着同一个堆空间,此时需要考虑对象的同步问题。虚拟机在红有分配对象的指令,却没有释放对象内存的指令。内存的释放是由垃圾收集器去执行,根据不同的JVM实现,他们的垃圾回收策略不一样。
        一般情况下堆中会存放指向对象池的指针和指向类数据的指针、对象池实例数据,这样就可以找到类数据和对象数据,从而执行对应的实例方法。还有一种特定的虚拟机实现,堆中除了存储上述数据还会额外存储对象的方法表指针,因为方法表可以存储实例方法运行时所需的所有信息,这样便可以提高运行效率,但是需要花费更多的内存空间来存储。
       3> 程序计数器
       每个线程都有它自己的PC寄存器,当线程启动时就会被创建,并且指向下一条指令的地址(也可以是相对方法字节码中起始指令地址的偏移量)。当线程执行的是本地方法时,此时存储的是undefined。 例如java异常的finnally的实现,是通过在对应的程序计数器 中存储一个类型为returnaddress的地址实现的。
       4> Java栈
       每启动一个新线程,Java虚拟机都会为它分配一个Java栈,用来存储线程的运行状态,虚拟机只会对栈进行2种操作:压栈和出栈。当java线程运行时,执行一个Java方法,虚拟机就会在该线程的Java栈中压入一个新帧,这个帧用来存储参数、局部变量、中间结果等,同时跟踪到当前类和当前常量值,再通过弹出帧来执行当前的实例方法。
        5>本地方法栈
        它是用来存储一些跟本地方法相关的数据。而本地方法时依赖于虚拟机设计者的具体实现,它可以自由地决定使用怎样的机制来让java程序调用本地方法。
       6>执行引擎
       任何java虚拟机的实现的核心就是执行引擎,它是用来定义指令集的。对于每条指令如何处理都是由执行引擎来定义并执行的。JAVA程序的每一个线程都是一个独立的虚拟机执行引擎的实例,从线程生命周期的开始到结束,它要么在执行字节码,要么在执行本地方法。 
       7>本地方法接口
       Java虚拟机并不强制必须实现本地方法接口。有些可以根本不支持本地方法接口,有些支持少数几个。而sun的java本地接口称作JNI,是为可移植性准备的。JNI是可以被任何java虚拟机实现支持的,开发者可以扩展或取代JNI,但是新的实现必须跟JAVA虚拟机内部的工作进行某种程度的交互,包括传递返回数据、操作数组、装载新类、抛出异常等。
 
4、JVM执行程序的流程
 
         在命令行执行"java Main"就会开启一个JVM实例,我们可以通过jps,jstat等JVM工具观察JVM的运行状态,下面以运行com.ntes.money.Main这个类为例来描述一下JVM执行一个程序的流程。
       当在命令行执行"java -Xmx=12m -Xms=12m -Dname=value com.ntes.money.Main"这个命令时,JVM的执行流程是,
       (1)加载JVM,主要是加载动态链接库,windows下是jvm.dll,Linux下是libjvm.so;
       (2)设置JVM启动参数,比如命令中的-Xmx=12m -Xms=12m用于设置堆大小。
       (3)初始化JVM。
       (4)调用类加载器子系统,加载com.ntes.money.Main。这里给出的是自定义类,根据类加载器双亲委派链,最后是由系统默认类加载器(Classpath类加载器)进行加载。 bootstrap loader -> 扩展类加载器(ext.jar) ->系统类加载(rt.jar)->用户自定义类加载。
       (5)在方法区com.ntes.money.Main类对应的数据结构中,根据方法描述符及访问标志,查找main方法。这里的描述符,包括了方法的方法名、参数、返回值,也就是public static void main(String[])。如果找不到对应的main方法,会抛出NoSuchMethodError: main异常。
       (6)通过本地方法(JNI)执行main方法。
   最后,JVM的自动内存管理,根据不同JVM的实现有所不同。 关于典型的hotspot虚拟机堆内存分配,参看另一篇文章:http://rainforc.iteye.com/admin/blogs/1993310

 

 

 

你可能感兴趣的:(jvm内存)