JVM学习-内存区域划分

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,他们分别是程序计数器、Java虚拟机栈、Java堆、方法区等。JVM本身的设计是多线程的,Java程序会有一个公用的Java堆和方法区,然后线程各自有自己的程序计数器和虚拟机栈。

程序计数器

程序计数器就是当前线程所执行的字节码的行号指示器,所以它占用的内存空间较小,线程的分支、跳转和循环等功能都需要靠这个计数器来完成。可以很容易地想到程序计数器被每个线程所独有,各线程之间的计数器互不影响。

虚拟机栈

虚拟机栈也是线程私有的,其生命周期和线程相同,它描述的是Java方法执行的内存模型,与c语言的栈含义类似。其实每个Java方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈,动态链接和方法出口等信息。每一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

上述局部变量表所需的内存空间在编译期间就已经确定,而且在运行期间这个局部变量表不会改变,一般人们常说的Java堆栈中的栈就是虚拟机栈。

堆是java内存管理中最重要的一部分,也是最大的一块,Java堆供所有线程共享,它在虚拟机启动的时候创建。

既然Java堆那么重要,那堆里面到底存的是什么?答案:所有的对象实例和数组。虚拟机栈中对对象的调用一般情况下是使用对象的句柄来完成。堆也是垃圾收集器管理的主要区域,即也被称为GC堆。

方法区

与堆一样,方法区也是各个线程共享的一块区域,它存储着已被虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等数据。对于HotSpot程序员来说,方法区一般被称为“永久代”,这里一般不需要垃圾回收,如果需要GC对这部分空间进行管理,主要是针对常量池的回收和对类型的卸载。

运行时常量池是方法区的重要组成部分,它除了存放着类的版本、字段、方法和接口等描述信息,还存放着编译期生成的各种字面量和符号引用,它们都存在于Class文件中,当类加载的时候就会进入方法区的运行时常量池中存放。

何为运行时常量池,与Class文件常量池有别的一个重要特性是动态性,在java程序运行期间也 可能将新的常量放入池中,利用这种特性的最常见的例子就是String类的intern()方法。

直接内存

我们上面所说的都是JVM能控制的内存区域,直接内存看名字显然不是虚拟机运行时的数据区,它就是宿主机的物理内存。在进行某些IO操作的时候,将缓冲区设为直接分配堆外内存,然后通过堆中对象作为这块内存的应用进行操作,可以显著提高性能。

对象的创建

Java是一门面向对象编程的语言,在Java程序运行过程中无时无刻都有对象被创建和销毁,Java对象的创建有如下过程:

  1 JVM遇到一条new指令时,去检查是否能在常量池中对位到一个类的符号引用,并检查这个类是否被加载过。
  2. 类加载检查通过后,虚拟机将为新生对象分配内存。在内存分配过程中,基本方法有“指针碰撞”和“空闲列表”,具体选择哪种方法是由垃圾收集器是否带有压缩整理功能决定的。在内存分配的过程中,也有并发冲突的问题,一般有两种解决方案,一是:对分配内存空间的动作进行同步处理,二是:采用本地线程分配缓冲,即每个线程在Java堆中预先分配一小块内存。
 3. 内存分配完成后,虚拟机会把分配到的内存空间都初始化为0,然后进行必要的设置,如:对象是哪个类的实例、对象的GC分代年龄等信息。最后再进行必要的初始化。

Java对象的结构

以前总是以为对象就是将类简单地存储在内存中,包括方法等,那样每个对象存储一遍它的方法岂不是极大的浪费。Java对象的结构如下:

 1. 对象头。对象头分为两部分,一部分是对象自身的运行时数据如:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类型指针,即虚拟机通过这个指针来确定对象是哪个类的实例。
2. 实例数据部分。是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
3.对齐填充。如果要求对象必须是8个字节的整数倍时,如果对象所占的空间不够8个字节的整数倍,那么就需要填充。

对象存在于堆上,但是在栈中需要对对象进行访问的时候,有两种方式:一种是句柄,一种是直接指针。句柄的好处是当对象被移动时,只需改变句柄表中的实例数据指针,而reference本身不需要修改,但是这样需要两次指针才能完成对象的索引,使用直接指针就可以提高性能,sun的HotSpot使用的就是直接指针。



Previous     Next
zhing /
Published under (CC) BY-NC-SA in categories java  tagged with java