概述
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示。
运行时数据区
程序计数器
一个较小的内存空间
当前线程所执行的字节码的行号指示器
线程私有
Java虚拟机栈
线程私有,生命周期与线程相同
存储局部变量表,操作数栈,动态连接,方法等信息
局部变量表的存储空间一局部变量槽表示
本地方法栈
与Java虚拟机栈类似,区别在于服务对象的不同,服务本地方法
对本地方法栈中方法所使用的语言,数据结构无强制要求
Java堆
虚拟机所管理的内存中最大的一块
所有线程共享,在虚拟机启动时创建
唯一的目的时存放对象实例,“几乎”所有的对象都是在这里分配内存的
是垃圾管理器管理的内存区域
线程共享的Java堆可以分配出多个线程私有的分配缓冲区
Java堆可以存储在物理上不连续的内存上,但是逻辑上连续
方法区
各线程共享,用于存储已被虚拟机加载的类型信息,常量,静态变量等
运行时常量池
方法区的一部分,用于存放编译期生成的各种字面量和符号引用
直接内存
将本地Naative函数库直接存储到堆外内存
HotSpot虚拟机 内存管理
对象创建
(1)检查new指令的参数是否能在常量池中定位到一个类的引用符号,并检查该符号引用代表的类是否已被加载、解析和初始化
(2)分配内存,内存的大小在类加载完成后便可以完全确认,内存分配实际就是从Java堆中划分出一块确定大小的内存,分配方式有两种
{指针碰撞:java内存规整,一边是已使用,一边是未使用的,直接移动指针空闲列表:Java内存不规整,维护一个列表记录哪些以用/未用\begin{cases} 指针碰撞:java内存规整,一边是已使用,一边是未使用的,直接移动指针\\ 空闲列表:Java内存不规整,维护一个列表记录哪些以用/未用 \end{cases}{指针碰撞:java内存规整,一边是已使用,一边是未使用的,直接移动指针空闲列表:Java内存不规整,维护一个列表记录哪些以用/未用
java内存规整与否有垃圾收集器是否带有空间压缩整理能力决定
内存分配并不是线程安全的,解决方式有两种
{分配内存空间动作同步本地线程分配缓冲(TLAB)\begin{cases} 分配内存空间动作同步\\ 本地线程分配缓冲(TLAB) \end{cases}{分配内存空间动作同步本地线程分配缓冲(TLAB)
(3)分配内存空间初始化,将分配空间(不包括对象头)初始化为零值
(4)对象设置,将对视时那个类的实例,GC分代等信息添加到对象头
(5)构造函数
对象的内存布局
对象在堆内存中的存储布局可以划分为三个部分
(1)对象头header:存储两类信息,一类为对象自身的运行式数据,哈希码,GC等;一类是类型指针,对象指向它的类型的元数据的指针
(2)实例数据:对象真正存储的有效信息
(2)对齐填充:占位符,使得对象的大小为8字节的整数倍
对象的访问定位
Java程序会通过栈上的reference数据量操作堆上的具体对象,也有两种操作方式
(1)句柄访问:Java堆中划分出一块内存来作为句柄池,存储对象的句柄池句柄中宝航对象的实例数据和类型数据
(2)直接访问:reference中存储的是对象地址
句柄访问存储稳定,直接访问会减少访问开销
常见内存溢出
java堆溢出
虚拟机栈和本地方法栈溢出
方法区和运行时常量池溢出
本机直接内存溢出