【后端修行之虚拟机】Java内存区域和内存溢出异常

本文用于记录苦啃《深入理解Java虚拟机:JVM高级特性与最佳实践》后,个人认为有价值的笔记,核心内容均围绕标题展开。既做一个学习的记录,同时也做一个沟通交流,欢迎各位大佬互动~

运行时数据区

Java程序运行起来,会将内存区域分成几个区域,虚拟机栈\本地方法栈\堆\方法区\程序计数器,不同区域会有不同的功能,服务不同的对象以及不同的创建\销毁时机.

程序计数器,

作用功能:给字节码解释器提示下一条需要执行的指令.

服务对象是线程,

特点:

  1. 属于是线程私有的(涉及多线程轮流切换处理器的问题),一个处理器或者是多核处理器中的一个核,内核,一次只能执行一个线程中的指令.如果涉及多线程切换,为了确保切换后能够回到原来执行的位置,因此需要确保每个线程都有一个程序计数器.
  2. 值是指令的地址值(java方法),本地方法的话则为空
  3. 创建销毁时间跟随着线程.

Java虚拟机栈,

作用功能:支持方法的执行和调用,

服务对象是线程,方法

特点:

  1. 线程私有,每个线程中的方法被执行,就会在虚拟机栈里面创建一个栈帧入栈,执行结束后栈帧会退栈. 栈帧是一个用于存放局部变量表\操作数栈\动态连接(指向运行时常量池的方法引用/指向方法内被调用方法的符号引用/包含引用的目的是为了支持厂前方法的代码能够实现动态链接)\方法出口等信息.

局部变量表(数字数组)

作用功能:用于存储编译期可知的基础数据类型\对象引用(指向对象的引用指针或对象相关的句柄)\returnAddress;用于方法执行过程中,存储和访问方法局部变量和参数作用.服务对象是方法

特点:

  1. 表中的存储空间以局部变量槽来表示,64位的long和double占两个槽
  2. 由虚拟机决定具体实现的大小
  3. 编译期间就可以确定局部变量表的大小(多少个槽)了(多少参数 多少局部变量),具体分配内存是在方法执行的过程中动态分配.
  4. 这个内存区域异常有两种,线程申请超出栈的深度,抛出StackOverflowError异常;栈容量可动态拓展的话,无法申请足够内存OutOfMemoryError

本地方法栈

作用功能:与Java虚拟机相似

服务对象:本地方法

特点:虚拟机可以自由实现这个区域,有的虚拟机甚至把它和java虚拟机栈合二为一

Java堆

作用功能:存储对象实例

服务对象:实例对象\线程

特点:

  1. 所有线程共享的区域,虚拟机启动的时候就创建
  2. Java堆可以划分出多个线程私有的本地线程分配缓冲区TLAB
  3. 可以处于不连续的物理空间
  4. 可固定大小,也可以变成可拓展的

方法区

作用功能:存储类型信息\常量\静态变量\即时编译器编译后的代码缓存,  类加载器把类字节码加载到方法区,完成类的解析\验证\准备和初始化准备

服务对象:类加载器

特点:

  1. 不要求连续内存和固定大小(可扩展)
  2. 对此区域的回收主要针对类型的写在和常量池的回收

运行时常量池

作用功能:存储Class文件类加载后的各种字面量符号引用(类的\字段的\方法的符号引用, 符号引用转直接引用),字面量和符号引用形式上都是字符串,但语意用途不一样,字面量表示常量的值,符号引用表示符号的名称,运行时解析为实际的或方法.

服务对象:JVM虚拟机

特点:

  1. 对比Class文件里的常量池,具有动态性. 非预置入Class文件常量池内容的东西也可以进入运行时常量池,例:String intern()方法

##常量池(Class文件里的部分)

字面量(数量值\字符串值),符号引用(类引用\方法引用\属性引用),看做一张表,虚拟机指令根据这张常量表找到要执行的类名\方法名\参数类型\字面量等

直接内存

作用功能:某些场景能大幅提升性能

服务对象:JVM虚拟机

特点:

  1. 非Java虚拟机运行时数据区的一部分,但是可以通过Java堆里的对象作为这块内存的引用去访问.
  2. 其大小不受Java堆限制,但受本机总内存约束

对象创建分布和访问

对象创建

创建过程: 类加载(判断是否已加载/运行常量池的符号引用是否被转换为直接引用)-分配内存-内存空间初始化零值(不包括对象头)-设置对象头信息(运行时数据+类型指针)-构造方法初始化设置-返回引用

涉及细节

内存分配的方式:指针碰撞\空闲列表 由是否有空间压缩整理能力的垃圾收集器决定

内存分配还需要注意一个问题,就是关于内存分配的多线程冲突问题.解决方式有两种,一种是保持操作原子性,另一种是提前给不同的线程分配不同的本地线程分配缓冲TLAB

对象内存布局

构成:对象头+实例数据+对其补充

对象头两部分:运行时数据(哈希码\锁相关信息\GC分代年龄)+类型指针+数组长度(如果是数组类型)

对象起始地址需要时8个字节的整数倍

对象的访问:

通过栈中的reference数据访问堆中的对象.

实现访问的方式有两种:句柄和直接指针,两种方式各有优劣.

句柄,在内存划分句柄池,同时句柄池内存储两个指针:对象指针和类型指针.  使用他的好处在于,由于gc存在对象可能会移动,用句柄的话reference存储的数据比较稳定,只有修改对应句柄中的对象地址.  坏处就是访问对比直接指针多一次指针定位时间开销

直接引用,对象内还包含了对象类型指针.好处在于节省了一次指针定位时间开销. 访问也是常见的操作.

2.3OOM异常

Java堆OOM

限制堆内存最大最小值:-Xms/-Xmx

排查异常过程:

找出占用内存最多的对象,看看是不是必须得对象,即检查是否内存泄漏.

如果不是内存泄漏则是内存溢出,需要看看是否需要扩大一下堆内存空间,对象是否生命周期过长,存储结构是否合理

虚拟机栈和本地方法栈SOM OOM

如果虚拟机支持栈的动态拓展,在申请栈空间时如内存不足可能导致OOM

如果虚拟机不支持栈的动态拓展(栈的大小固定),就只有在创建新线程是内存不足导致OOM;

内存溢出还有两种情况导致StackOverflowError,第一个是栈容量小,新的栈帧无法分配;第二个是栈帧太大,栈空间不足(局部变量表很大);

注意:

内存溢出异常和栈空间是否足够没有直接关系

在一个进程里如果因为线程过多(而且不能删除)情况下,内存溢出了,可以通过降低最大堆空间以及降低线程中栈的容量来换取更多线程

方法区和运行时常量池溢出OOM

常量池溢出,JDK7之后字符串常量池转移到java堆,而且没有永久代的概念,取而代之的是元空间

类溢出和类卸载

直接内存OOM

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值