1.1  概述

对于java来说,虚拟机是采用的自动管理内存机制,不需要手动去写delete/free代码,但是常在河边走哪有不湿鞋,程序不可避免会遇到内存溢出或泄漏的问题,因此知道内存区域分布情况对于内存管理是很有必要的。

1.2  运行时数据区域

java虚拟机在执行java程序的过程中把它管理的内存划分为若干个区域:程序计数器、虚拟机栈、本地方法栈、堆区和方法区。

1.2.1  程序计数器

程序计数器是一块线程私有的内存空间,指代当前线程所执行字节码的行号指示器。java虚拟机的多线程是通过线程轮流切换并分配处理器的执行时间的方式实现的,对于每个线程在执行时都需要有自己的程序计数器,为了线程切换恢复后能找到自己正在执行字节码的位置。程序计数器中记录的值,如果执行的是java方法则为字节码的指令地址,如果执行的是Native方法则计数器的值为空。

1.2.2  虚拟机栈

虚拟机栈也是伴随线程的一块私有的内存空间。虚拟机栈描述的java方法执行的内存模型:方法被调用时会创建一个栈帧(局部变量表、操作数栈、动态链接、方法出口等),每个方法调用至完成对应一个栈帧的入栈到出栈。局部变量表中包括:基本数据类型(byte、short、boolean、char、int、long、float、double)、对象引用(对于HotSpot虚拟机来说是一个指向对象起始地址的引用,其他虚拟机可能是执行堆区的一个句柄地址)和returnAddress类型(一条字节码指令地址)。局部变量表所需的内存时编译期完成的,64位长度的long和double数据类型会占用2个Slot,其余的数据类型只占用1个Slot。

1.2.3  本地方法栈

本地方法栈和虚拟机栈很类似也是线程私有的,当执行java方法时使用虚拟机栈,当执行Native方法时使用本地方法栈。

1.2.4  堆区

堆区是一块线程共享的内存区域,主要存放对象实例以及数组。堆区是内存管理的主要区域,内存回收的角度看,堆区可分为:新生代和老年代;细分的话新生代可分为:Eden空间、From Survivor空间、To Survivor空间。内存回收的角度看,可以分为多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。由于堆区不断进行着对象的创建和回收,很有可能出现内存问题,可以通过(-Xmx:初始堆大小 和-Xms:最大堆大小)来控制。

1.2.5  方法区

方法区也是线程共享的内存区域,主要有虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码。由于方法区存储内容的关系,垃圾收集行为较少出现。

1.3  java对象

1.3.1  对象的创建

当new一个对象时,首先要判断这个指令的参数(全限定名)能否在常量池中被找到,检查(全限定名)代表的类是否已被加载,解析和初始化。如果没有则要先执行类加载过程。类加载完后,需要分配内存,对象所需的内存在类加载完就确定了,只需要从内存中划分一块区域给对象。划分时要根据内存是否规整采用不同的方式(指针碰撞和空闲列表)分配,是否规整又取决于垃圾收集器是否带有整理过程,像Serial、ParNew等收集器带有整理过程时采用“指针碰撞”,CMS基于“标记-清除”算法的采用空闲列表分配。内存分配完成后,需要对内存空间进行初始化零值以及对象头的设置,最后才是java对象的初始化操作。

1.3.2  对象的内存布局

对象布局中分3块区域:对象头、实例数据和对齐填充。对象头分两部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;第二部分为类型指针,该指针指代该对象在方法区类元数据的地址,确定这个对象时通过那个类来创建的(不适用于句柄访问对象的方式)。实例数据部分是对象真正存储的有效信息,在程序代码中定义的这种类型的字段内容。对齐填充不是必然存在的,由于对象的大小必须是8字节的整数倍,当实例数据没有对齐时,需要通过对齐填充来补全。

1.3.3  对象的访问定位

java程序从栈去的局部变量表的对象引用(reference)引用堆区的对象时,有两种方式:句柄和直接指针。句柄是在堆区创建一块内存作为句柄池,存放对象实例数据与类型数据的具体地址信息,优点是在对象移动时reference引用的句柄地址是不会变的;直接指针是reference指向堆区对象的地址,堆区对象中还有一个类型指针指向对象的类型数据,优点是节省了一次指针定位的开销。

posted on 2019-01-05 09:43  嵩之恋  阅读(194)  评论(0编辑  收藏  举报