我们在谈Java内存区域的划分时,事实上指的就是JVM内存区域的划分。在了解JVM内存之前,先看一下Java程序具体的执行过程。

        如上图所示,首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。因此,在Java中我们常常说到的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)。

什么是JVM?

        JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM是运行所有Java程序的虚拟计算机,好比是街机游戏的模拟器

        JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的字节码文件(.class),就可以在多种平台上不加修改地运行(字节码文件具有平台无关性)。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

与JRE、JDK的关系?

        JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。

        JDK(Java Development Kit,Java开发工具),是程序开发者用来来编译、调试java程序用的开发工具包。如javac、java等,JDK中包含JRE。

        JVM是JRE的一部分。JDK>JRE>JVM

JVM的体系结构

        类装载器(ClassLoader)(用来装载.class文件)

        执行引擎(执行字节码,或者执行本地方法)

        运行时数据区(方法区、堆、java栈、程序计数器、本地方法栈)


JVM运行时数据区

        根据《Java虚拟机规范》的规定,运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

一、程序计数器(Program Counter Register)

        一块较小的内存空间,它是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的字节码指令,分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。

        当线程在执行一个Java方法时,该计数器记录的是正在执行的虚拟机字节码指令的地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。另外,该内存区域是唯一一个在Java虚拟机规范中么有规定任何OOM(内存溢出:OutOfMemoryError)情况的区域。

二、Java虚拟机栈(Java Virtual Machine Stack)

        与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期与线程相同。Java栈是Java方法执行的内存模型。Java栈中存放的是一个个的栈帧,每个方法被执行的时候都会同时创建一个栈帧,它是用于支持续虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。

        1.局部变量表:局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和returnAddress类型(它指向了一条字节码指令的地址)。

        2.操作数栈:操作数栈和局部变量表在访问方式上存在着较大差异,操作数栈并非采用访问索引的方式来进行数据访问的,而是通过标准的入栈和出栈操作来完成一次数据访问。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。由于程序计数器无法被程序指令直接访问,Java虚拟机的指令是从操作数栈中取得操作数,所以它的运行方式是基于栈而不是基于寄存器。虚拟机把操作数栈作为它的工作区,因为大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈

        3.动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。我们知道Class文件的常量池有存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),这种转化称为静态解析。另外一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。

        4.方法返回地址:当一个方法被执行后,有两种方式退出该方法,执行引擎遇到返回指令或者是遇到异常,并且异常未在方法内处理。不管哪种情况,一旦方法返回,肯定要获得返回的地址,来保证正常的执行。而且在返回时可能会保存一些信息,来完成上层的处理。正常退出时,会将程序计数器的值来作为返回的地址;出现异常时则会通过异常处理器来确定。

三、本地方法栈(Native Method Stack)

        本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。

四、Java堆(Java Heap)

        Java堆是Java虚拟机所管理的内存中最大的一块,它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。

五、方法区(Method Area)

        方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、字段信息)、常量、静态变量、即时编译器编译后的代码等数据。

        方法区域又被称为“永久代”,但这仅仅对于Sun HotSpot来讲,JRockit和IBM J9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分,而且它和Java Heap一样不需要连续的内存,可以选择固定大小或可扩展,另外,虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。

        运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的是String类的intern()方法。

        既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError 异常。

 

关于 Object obj = new Object();

        首先obj作为引用类型(reference)保存在java栈的局部变量表中,其次new Object() 会在堆中开辟一块内存空间保存obj引用的实例化对象,而该对象实例对应的类的相关信息对象类型数据则保存在方法区,通过指向对象类型数据的指针(指向对象类型数据的指针保存在堆中)访问方法区中的对象类型数据。


 

参考资料:http://blog.csdn.net/ns_code/article/details/17565503

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐