深入理解jvm — 内存篇

3/8/2017来源:ASP.NET技巧人气:1005

我们都知道java是由虚拟机管理内存,那么jvm是怎么管理内存的呢?下面先从内存划分看起

虚拟机在执行java程序的过程中会把它管理的内存划分为若干个不同的数据区,运行时数据区包括5个部分:程序计数器、方法区、虚拟机栈、堆 如下图所示 这里写图片描述

下面分别介绍

1、程序计数器 程序计数器是一块较小的内存空间,可以看成当前线程锁执行的字节码的行号指示器 在虚拟机的模型概念里,字节码的解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码命令,分支、循环、跳转、异常处理、线程恢复等基础功能的都需要依赖这个计数器来完成 java中每个线程都有一个独立的程序计数器,使得线程切换后能恢复到正确的执行位置,这类内存成为“线程私有内存” 如果线程执行的是java方法,计数器记录的是指令地址,如果执行的是Native方法,计数器值为空(Undefined) 此内存区域是唯一一个没有oom异常的区域

2、Java虚拟机栈 他描述的是Java方法执行的内存模型:每个方法执行时会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法从调用到完成的过程,对应栈帧在虚拟机中入栈出站的过程,是线程私有的,生命周期与线程相同 经常有人把Java内存分为堆内存(Heep)和栈内存(Stack),这里所指的栈就是虚拟机栈中局部变量表部分 局部变量表存放基本数据类型、对象引用、returnAdress三种类型,局部表内存是在编译期间完成分配,是完全确定的,运行时不会改变

3、本地方法栈 作用与虚拟机栈相似,不同的是虚拟机栈为执行的Java方法服务,本地方法栈为使用到的Native方法服务 并且本地方法栈没有强制规定,可以由虚拟机自由实现

4、方法区 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码,所有线程共享。 一般不受GC管理,中间储存常量的叫常量池,其中运行时常量池是用于存放编译期生成的各种字面量和符号引用

5、Java堆 会在虚拟机启动时创建,用来存放对象实例,所有线程共享。 它是垃圾收集器管理的主要区域,也成为GC堆 java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可,可扩展

下面用一个小例子,来说明堆、栈、方法区之间的关系的

public class Test { public static void main(String[] args) { public Test test = new Test(); } } // JVM将Test类信息加载到方法区,new Test()实例保存在堆中,Test引用保存在栈中

最后说一下直接内存,他不是虚拟机运行时数据区域的一部分,有兴趣的可以自己去了解一下


知道了jvm的内存分配,下面就new个对象的过程,说一下对象在内存中是怎么创建、存储和引用的

1、对象的创建 虚拟机遇到new指令时,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代码的类是否已被加载、解析和初始化过,如果没有就必须执行类加载过程。然后会为新对象分配内存,所需内存大小在类加载完成后便可完全确定。之后会将分配到的内存空间都初始化为零值。然后对对象进行设置,设置信息放在对象头中。最后执行方法,按程序员意愿初始化

2、对象的存储 如果Java堆中的内存是绝对规整的,所有用过的内存放在一边,空闲的内存放在另一半,中间放一个指针作为分界点的指示器,对象分配内存就是把指针像空闲的那边移动一段需要的距离,这种分配方式为“指针碰撞”(Bump the Pointer)

如果Java堆内存并不是规整的,虚拟机就必须维护一个列表,记录那些内存块是可用的,在分配的时候,从列表中找到一块足够大的内存空间划分给对象实例,并更新表上的记录,这种分配方程成为“空闲列表”(Free List)

选择哪种分配方式是由Java堆是否规整决定,而Java堆是否规整是由所采用的垃圾收集器是否带有压缩整理规定

3、对象的内存布局 在HotSpot虚拟机中,对象在内存中的存储布局可以分为3块区域:对象头 (Header)、实例数据 (Instance Date)、对齐填充(Padding)

对象头包含两部分信息:第一部分用于存储对象自身运行时数据,成为“Mark Word” 如哈希码、GC分代年龄、锁状态标识等等。另一部分是类型指针,执行类元数据,通过这个指针来确定这个对象是哪个类的实例。另外,如果对象是一个Java数组,对象头中必须有一块记录数组长度的数据

实例数据:记录代码中定义的各种类型的字段内容,父类继承的,和子类中定义。存储顺序会受虚拟机的分配策略影响

对齐填充:起着占位符的作用,对象的其实地址必须是8字节的整数倍,没有对齐时就要填充

4、对象的访问定位 java程序需要通过栈上的reference数据来操作堆上的具体对象。如何定位访问,取决于虚拟机的实现,目前主流的访问方式有:句柄,直接指针

句柄访问:java堆中划分一部分内存作为句柄池,reference中存储的就是对象的句柄地址,句柄中包括了对象的实例数据和类型数据的各自具体地址信息。优点是 对象被移动时只会改变句柄中的实例数据指针,reference本身不用修改

指针访问:reference中存储的就是对象地址,java堆对象中就必须考虑如何放置访问类型数据的相关信息。优点是速度更快,节省了一次指针定位的开销

介绍完了内存分配,下篇博客将介绍内存清理 http://blog.csdn.net/ying1414058425/article/details/60144058