jvm内存区域
1、内存总揽
2、内存区域介绍
2.1、程序计数器
概念: 程序计数器可看成当前程序执行的字节码的行号指示器,其作用不仅指示着程序下一条需要执行的字节码指令,而且 用于保证线程切换后能够正确恢复到下一条需要执行的指令位置;
特性: 哥线程的程序计数器相互独立,互不影响,属于线程私有; 并且其是java中唯一一个没有规范有OutOfMemoryError情况的区域;
2.2、java虚拟机栈
慨念: 虚拟机栈描述的是java方法执行的内存模型,每个方法在执行的同时会创建一个栈帧用于存储局部变量表,操作数栈,动态链接 方法出口等信息,每个方法的从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈的入栈到出栈的过程。
特性: 虚拟机栈是线程私有的,他的生命周期与线程相同。
存储信息:
- 局部变量表:存储了各种基本类型(int,long,byte,char,short,boolean,float等),对象引用(reference类型,可能指向对象的起始地
址或者对象句柄),returnAdress类型(指向下一条指令),其中long,double类型64位会占用2个局部变量空间(slot),其他只占用一个;
局部变量表在类编译机器完成分配,当进入一个方法时,局部变量表的大小已经确定,后期将不再更改;
异常类型:
- 当线程请求的栈深度大于虚拟机栈所允许的深度时,抛出StackOverflowError
- 虚拟机栈扩展或创建时无法申请到足够的内存时,抛出OutOfMemoryError
2.3、本地方法栈
本地方法栈和虚拟机栈很相似,虚拟机栈是为虚拟机执行java方法服务的,而本地方法栈是虚拟机执行本地方法服务的,其同样会抛出StackOverflowError, OutOfMemoryError异常;
2.4、java堆
概念: 虚拟机启动时创建,用于存放对象实例及数组,其主要是垃圾收集器的主要区域,因此也称为”GC堆”
存储: 对象实例以及数组
特性: 在堆中没有内存完成实例分配时,抛出OutOfMemoryError异常,物理内存不需要连续,逻辑上连续即可;
2.5、方法区
概念: 各个线程共享的内存区域;
存储: 已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据;
特性: 方法区里的垃圾回收的目标主要针对常量池的回收和对类型的卸载;其中类型的卸载条件非常的苛刻,当方法区无法满足内存分配需求时,抛出抛出OutOfMemoryError异常
2.6、运行时常量池
概念: 方法区的一部分
存储: 存放编译期生成的各种字面量(文本,final变量),符号引用(类,方法,变量的名称和描述)
特性: 具备动态性,常量并非编译期产生,运行期间也可产生,例如String.intern方法,当常量池无法申请到内存时会抛出OutOfMemoryError异常
2.7、直接内存
概念: 不是虚拟机运行时的数据区域,属于虚拟机外的内存;
特性: jdk1.4增加了NIO类,引入了一种基于通道和缓存区的I/O方式,它可以通过Native函数分配堆外内存,然后通过一种存储在堆中的DirectByteBuffer
对象来作为这块内存的引用来操作该内存,这样在某些场合提高了性能,避免了来回复制数据。内存无法扩展时,也会抛出OutOfMemoryError异常
3、对象的创建过程
3.1、类加载
虚拟机当遇到new指令时,首先会到方法区的常量池中检查该类的符号引用是否存在,以及该符号引用所代表的类是否已被加载,解析和初始化过,若还没有, 则先加载该类。
3.2、内存分配
内存分配即是在堆内存中划分出一块用于创建类的实例。当前内存分配有两种方式:
- 指针碰撞:要求虚拟机堆内存必须是绝对归整的,一部分是已使用内存,一部分是未使用的内存,中间用一个指针作为分界点,内存分配就是将指针向未分配的内存区移动 到所需大小的内存的位置;
- 空闲列表:虚拟机的堆内存不是归整的,而是零散的,虚拟机维护一张空闲列表记录那些内存块是可用的,从可用的内存块中找到一个足够大的内存来分配给类创建实例;
以上两种方式选择哪种是由堆内存是否归整决定的,而内存是否归整则是由垃圾收集器是否有压缩归整的功能决定的;
堆中分配内存创建类的实例是很频繁的事情,需要考虑到并发分配同一块内存时如何解决的,这里有两种方案: - 分配内存空间进行同步处理–实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
- 将内存分配操作按照线程划分在不同的空间进行,即每个线程在java堆中预先分配一小块的内存,作为本地线程的分配缓存(TLAB),线程分配内存时在自己的TLAB上 进行,当TLAB无法满足时,才需要同步锁;是否使用TLAB可以通过**-XX:+/-UseTLAB参数设定;
3.4、内存空间初始化
使用TLAB时,该步骤可以放到TLAB之前将内存空间初始化为0,保证关键字段在不赋值的情况下也可被使用;
3.5、初始化
分配空间后所有的字段值为0,需要调用init方法初始化字段值,达到程序员所要求的,这时对象的创建才算结束;
4、内存中的对象
4.1、对象的内存布局
对象在内存中的布局分为3块:对象头,实例数据,对其填充; * 对象头(header):包含两部分,一部分用于存储运行时的数据如哈希码,GC分代年龄,锁状态标志,偏向线程ID等;这部分数据在32或64位的虚拟机中分别用 32位和64位来存储,该部分被称为”Mark Word”;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例,注意 并不是所有的对象都有类类型指针,例如数组就没有;
- 实例数据:该部分存储的是类的有效信息,也就是程序代码中所定义的各种类型的字段。注意这部分的数据的存储顺序受到虚拟机的分配策略参数(FieldsAllcationStyle) 和字段在源码中的定义顺序影响;
- 对其填充:该部分并不是必须存在的,也没有特殊含义,主要用对齐,计算机要求内存必须是8字节的倍数;
4.2、对象的访问定位
主流的访问方式有两种:句柄方式和直接指针方式;
- 句柄方式:堆内存会划分出一块内存用于存储句柄池,reference中存储的就是句柄地址,而句柄中包含了对象的实例数据和类型数据各自的地址信息。
- 直接地址:堆对象的布局中必须考虑如何放置访问类型数据的相关信息,而reference中存储的就是实例数据的直接地址;
两种方式各有千秋,句柄方式,在对象被移动时(垃圾回收引起),只会改变句柄的中的实例数据,而不用改变reference,直接地址方式的最大的好处是访问速度快, 中间节省了一次指针定位的时间开销,java中对象访问非常频繁,积少成多就是很大一笔的性能节省;
5、虚拟机相关内存参数设定
- 堆内存设置:-Xms30m 最小堆内存值,-Xmx40m 最大堆内存值 -XX:+HeapDumpOnOutOfMemoryError 参数用于出现堆内存溢出时转储当前堆内存快照;
- 虚拟机栈和本地方法栈设置:-Xss20k
- 方法区设置:-XX:PermSize=10M 最小,-XX:PermSize=10M 最大
- 直接内存设置:-XX:MaxDirectMemorySize=10M,默认和堆最大值一样;