您的位置:首页 > 编程语言 > Java开发

【JVM学习】Java的家园----JVM内存区域分析与对象的内存分配和访问

2016-10-11 09:32 681 查看
Java内存管理是每个使用Java语言的编程人员必须了解的知识,最近阅读了周志明老师的《深入理解Java虚拟机》以及其他相关的资料,将相关内容简要的整理一下作为备忘。

一、Java内存分区及其作用

JVM在执行Java程序时会将内存区域划分为不同的功能区块,各个区块有各自的用途及其特性。下图是维基百科中给出的JVM内存分区情况。

从图可看出JVM内存区域分了五部分,下面我们分别来进行讲解。



1.Program Counter Register — 程序计数器

JVM在执行方法时,如果不是Native方法,程序计数器会记录JVM当前执行指令的地址,换句话说,就是记录了字节码执行的地址。如果是Native方法

则计数器为空。另外,JVM是支持多线程运行的,每个线程都有自己的程序计数器。在JVM执行程序的过程中,字节码解释器通过程序计数器的值来确定下一条将要执行的字节码执行,因此程序计数器也可以看做是程序执行的指路人,告诉JVM接下来应该执行哪一条程序。程序计数器是JVM规范中唯一没有指定任何OutOfMemoryError的区域。

2.JVM Language Stack — Java虚拟机栈

每个JVM线程都会拥有一个随该线程一起创建的Java虚拟机栈用来存储方法的内存模型。每个方法在开始执行执行时会创建一个栈帧,来存储局部变量表、操作数栈、动态链接和方法的调用与返回信息等数据。其中对于局部变量表的内存空间是在编译期间分配的,在方法运行时期不会改变其大小。一个方法从被调用到执行结束的过程就是一个栈帧在Java虚拟机栈中从入栈到出栈的过程。JVM规定了该区域有两种异常情况:

* 如果线程请求的栈深度大于了JVM所允许的深度会报StackOverflowError异常

* 如果虚拟机栈在动态扩展时无法申请到足够的内存会报OutOfMemoryError异常

3.Native Method Stacks — 本地方法栈

其作用与JVM栈类似,Java虚拟机栈为执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。在Sun的HotSpot虚拟机中这两个区域是合在一起的。这个区域也会报StackOverflowError异常和OutOfMemoryError异常

4.Heap — Java堆

堆内存应该是我们最熟悉的了,它是JVM所管理的内存中最大的一块区域,同时是被线程共享的。Java堆内存主要用来存放对象实例。在JVM文档的描述是所有的Java对象和数组都要在堆中分配内存。这里也是垃圾收集器管理的主要区域。具体到堆中的分代以及线程私有的缓冲区都是为了便于垃圾回收而作的划分 ,但根本上与存放内容无关。JVM中规定:Java堆内存在物理上可以不连续的,只要在逻辑上连续即可。既可以设置固定大小,也可以动态的增大或缩小。当堆中没有内存完成实例分配并且堆也无法在进行再扩展时将会抛出OutOfMemoryError异常。

5.Method Area — 方法区

方法区主要用来存储已被JVM加载的类信息、运行时常量池、静态变量、即时编译器编译后的代码等数据。和堆一样,方法区也是线程共享的而且被垃圾收集器所管理。当方法区无法满足内存分配需求时会抛出OutOfMemoryError异常。

6.Runtime Constant Pool — 运行时常量池

运行时常量池是方法区的一部分,用来存放编译期生成的各种字面量和符号引用。它们在类加载之后进入方法区的运行时常量池中存放。除了编译期可以将字面量预置入Class文件外,运行期间也可以将新的常量放入运行时常量池。例如String类的intern方法。

以上就是JVM的内存区域的简单介绍,想详细了解的同学可以查看Java虚拟机规范进行深入学习。

二、Java对象的内存分配和访问

1.Java对象的内存分配

这里以HotSpot虚拟机为例,其对象的内存区域主要分了三块:对象头、实例数据和对齐填充。

对象头

HotSpot的对象头包括两部分信息:一部分用于存储对象自身的运行时数据,如哈希吗、线程持有的锁、锁状态标识、GC分代年龄等。这部分在数据的长度在32位和64位虚拟机中分别为32bit和64bit。官方称为Matr Word。

另一部分是类型指针,JVM通过类型指针确定这个对象是哪个类的实例。另外如果对象是一个数组,还会在对象头中有一块用来记录数组长度的数据

实例数据

该区域是对象真正存储的有效信息,即在程序代码中所定义的各种类型的字段内容,无论是从父类继承下来的还是在子类中定义的。

对齐填充

仅仅起占位符的作用,并不一定必然存在。HotSpot虚拟机要求对象起始地址必须是8字节的整数倍,也就是说对象的大小必须是8字节的整数倍,而

对象头的大小正好是8的整数倍,因此当实例数据部分不是8字节的整数倍没有对齐时就需要通过对齐填充来补全。

2.对象的访问定位

对象创建好了就应该是访问了,Java程序需要通过栈上的reference数据来操作堆上的具体对象。那么怎样才能知道对象的位置然后访问呢。目前主流有使用句柄和直接指针两种方式

使用句柄

如果使用句柄那么JVM堆中就划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息

直接指针

如果使用直接指针访问,则Java堆在对象布局中必须考虑如何放置访问类型数据的相关信息。而reference中存储的直接就是数据地址。

两种访问方式对比

使用句柄的最大好处在于reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针而reference本身不需要修改。而

直接指针方式最大的好处就是速度更快,节省了一次指针定位的时间开销。这对于需要频繁访问对象的Java程序非常有意义。HotSpot就是使用直接指针进行对象访问的。

上面就是对Java虚拟机内存分区以及对象存储的简单介绍,下篇将介绍Java对象从创建到被回收的生死旅程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: