一.JAVA内存
1.运行时数据区域
线程隔离区:程序计数器,虚拟机栈,本地方法栈
线程共享区:方法区,堆
1.1程序计数器
多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各个线程直接计数器互不影响,独立存储。
- 当前线程所执行的字节码的行号指示器,是一块较小的内存空间
- 如果是Java代码,则记录的是正在执行的虚拟机字节码指令的地址,如果是native方法,则为空
- 唯一没有任何规定OOM的区域
1.2虚拟机栈
粗糙的将Java内存分为堆和栈,那这里的“栈”指的就说虚拟机栈,或者说是虚拟机栈中的局部变量表部分
- 描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧,用于存储 局部变量表、 操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程就对应一个栈帧在虚拟机栈中入栈道出栈的过程
- 局部变量表存放了编译期可知的各种基本数据类型、对象引用、和returnAdrdress类型
- 当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间时完全确定的,在方法运行期间不会改变局部变量表的大小
- 栈溢出 线程请求的栈深度大于虚拟机所允许的深度
- OOM 如果拓展时无法申请到足够的内存
1.3本地方法栈
与虚拟机栈的区别是,为虚拟机所用到的Native方法服务
1.4堆
是虚拟机所管理的内存中最大的一块,是线程共享的,在虚拟机启动时创建。
- 堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
- 堆是垃圾收集器管理的主要区域,因此被称为“GC堆”
- 堆可以是物理上不连续的内存空间,只要逻辑上说连续的即可
- 堆无法拓展时会OOM
1.5 方法区
线程共享,用于存储意已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 垃圾收集行为出现较少,主要回收是针对常量池和类型的卸载
- 当无法满足内存分配需求时会OOM
1.5.1运行时常量
是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在,类加载后进入方法区的运行时常量池中,存放
- 动态性:Java语言并不要求常量一定只有编译期才能产生,也就说并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量池放入池中,这种特效被开发人员利用得比较多的便是String类的intern()方法
2.HotSpot虚拟机对象
2.1对象的创建
1.new关键字开始
2.类加载检查
3.分配内存
4.内存空间初始化为零值(不包括对象头)
5.设置
6.执行Init
类加载检查
- 检查new指令的这个参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加、解析、和初始化过,如果没有,则必须先执行对应的类加载过程
分配内存
- 垃圾收集器是否带有压缩整理功能决定Java堆是否规整
- Java堆不规整-分配方式-空闲列表-虚拟机维护一个列表,记录哪些内存块说可用的
- Java堆规整-分配方式-指针碰撞-用过的内存在一边,空闲的在一边,中间放着指针作为分界点的指示器,分配内存仅仅是把指针移动对象大小相等的距离
- 同步方案1-虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
- 同步方案2-把内存分配的动作按照线程划分在不同的空间中进行(本地线程分配缓冲)
初始化为零值
- (疑问)保证了对象实例字段在Java代码中可用不赋值就直接使用,程序能访问到这些字段的数据类型所对应下来的零值
设置
- 设置这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希、对象的GC分代年龄等信息。这些信息保存在对象头中(Object Header)
执行Init
- 执行new关键字后,会执行init方法,把对象按照程序等意愿进行初始化,这样一个真正的对象才算完成产出
2.2对象的内存布局
在HotSpot虚拟机中,对象在内存中的存储布局可以分为3块区域:对象头、实例数据、对齐填充
对象头
对象头包括两部分信息
- 用于存储对象自身的运行时数据:哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等
- 存储类型指针,即指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。(查找元数据并不一定要经过对象本身 )
注:如果是Java数组,那么对象头中还必须要有一块区域用于记录数组长度的数据,因为Java虚拟机可以通过普通Java对象的元数据信息来确定Java对象的大小,但是从数组的元数据中无法确定数组的大小
实例数据
实例数据是对象真正存储的有效信息,也就说程序代码中所定义的各种类型的字段内容,无论是从父类继承下来的,还是本身定义的,都要记录下来
对齐填充
对齐填充部必然存在,因为对象的大小必须说8字节的倍数,当对象实例数据部分没有对齐时,就要通过对齐填充来补齐
2.3对象的访问定位
Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型值规定来一个指向对象的引用,而具体引用的方式目前有两种:使用句柄、直接指针
tip:reference类型存储在虚拟机栈的栈帧中的局部变量表中
二.outOfMemoryError异常
虚拟机规范中描述:除了程序计数器外,其他的运行时区域都有发生OOM的可能
1.Java堆溢出
Java堆用于存储对象实例,只要不要地创建对象,并且保证GC Root到对象之间有可达路径来避免垃圾回收机制来清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常
2.虚拟机栈和本地方法栈溢出
由于HotSpot虚拟机并不区分虚拟机栈和本地方法栈(只是分别执行Java方法和native方法),因此虽然设置本地方法栈大小存在,但实际上上无效的,栈容量由虚拟机栈容量确定
- 如果线程请求的栈深度大于虚拟机允许的最大深度,将会抛出StackOverflowError异常
- 2.如果虚拟机栈的拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
3.方法区和运行时常量池溢出
运行时常量池上方法区的一部分
- 如果虚拟机栈的拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
4.本地直接内存溢出
- 如果虚拟机栈的拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常