本文用于记录苦啃《深入理解Java虚拟机:JVM高级特性与最佳实践》后,个人认为有价值的笔记,核心内容均围绕标题展开。既做一个学习的记录,同时也做一个沟通交流,欢迎各位大佬互动~
运行时数据区
Java程序运行起来,会将内存区域分成几个区域,虚拟机栈\本地方法栈\堆\方法区\程序计数器,不同区域会有不同的功能,服务不同的对象以及不同的创建\销毁时机.
程序计数器,
作用功能:给字节码解释器提示下一条需要执行的指令.
服务对象是线程,
特点:
- 属于是线程私有的(涉及多线程轮流切换处理器的问题),一个处理器或者是多核处理器中的一个核,内核,一次只能执行一个线程中的指令.如果涉及多线程切换,为了确保切换后能够回到原来执行的位置,因此需要确保每个线程都有一个程序计数器.
- 值是指令的地址值(java方法),本地方法的话则为空
- 创建销毁时间跟随着线程.
Java虚拟机栈,
作用功能:支持方法的执行和调用,
服务对象是线程,方法
特点:
- 线程私有,每个线程中的方法被执行,就会在虚拟机栈里面创建一个栈帧入栈,执行结束后栈帧会退栈. 栈帧是一个用于存放局部变量表\操作数栈\动态连接(指向运行时常量池的方法引用/指向方法内被调用方法的符号引用/包含引用的目的是为了支持厂前方法的代码能够实现动态链接)\方法出口等信息.
局部变量表(数字数组)
作用功能:用于存储编译期可知的基础数据类型\对象引用(指向对象的引用指针或对象相关的句柄)\returnAddress;用于方法执行过程中,存储和访问方法局部变量和参数作用.服务对象是方法
特点:
- 表中的存储空间以局部变量槽来表示,64位的long和double占两个槽
- 由虚拟机决定具体实现的大小
- 编译期间就可以确定局部变量表的大小(多少个槽)了(多少参数 多少局部变量),具体分配内存是在方法执行的过程中动态分配.
- 这个内存区域异常有两种,线程申请超出栈的深度,抛出StackOverflowError异常;栈容量可动态拓展的话,无法申请足够内存OutOfMemoryError
本地方法栈
作用功能:与Java虚拟机相似
服务对象:本地方法
特点:虚拟机可以自由实现这个区域,有的虚拟机甚至把它和java虚拟机栈合二为一
Java堆
作用功能:存储对象实例
服务对象:实例对象\线程
特点:
- 所有线程共享的区域,虚拟机启动的时候就创建
- Java堆可以划分出多个线程私有的本地线程分配缓冲区TLAB
- 可以处于不连续的物理空间
- 可固定大小,也可以变成可拓展的
方法区
作用功能:存储类型信息\常量\静态变量\即时编译器编译后的代码缓存, 类加载器把类字节码加载到方法区,完成类的解析\验证\准备和初始化准备
服务对象:类加载器
特点:
- 不要求连续内存和固定大小(可扩展)
- 对此区域的回收主要针对类型的写在和常量池的回收
运行时常量池
作用功能:存储Class文件类加载后的各种字面量和符号引用(类的\字段的\方法的符号引用, 符号引用转直接引用),字面量和符号引用形式上都是字符串,但语意用途不一样,字面量表示常量的值,符号引用表示符号的名称,运行时解析为实际的或方法.
服务对象:JVM虚拟机
特点:
- 对比Class文件里的常量池,具有动态性. 非预置入Class文件常量池内容的东西也可以进入运行时常量池,例:String intern()方法
##常量池(Class文件里的部分)
字面量(数量值\字符串值),符号引用(类引用\方法引用\属性引用),看做一张表,虚拟机指令根据这张常量表找到要执行的类名\方法名\参数类型\字面量等
直接内存
作用功能:某些场景能大幅提升性能
服务对象:JVM虚拟机
特点:
- 非Java虚拟机运行时数据区的一部分,但是可以通过Java堆里的对象作为这块内存的引用去访问.
- 其大小不受Java堆限制,但受本机总内存约束
对象创建分布和访问
对象创建
创建过程: 类加载(判断是否已加载/运行常量池的符号引用是否被转换为直接引用)-分配内存-内存空间初始化零值(不包括对象头)-设置对象头信息(运行时数据+类型指针)-构造方法初始化设置-返回引用
涉及细节
内存分配的方式:指针碰撞\空闲列表 由是否有空间压缩整理能力的垃圾收集器决定
内存分配还需要注意一个问题,就是关于内存分配的多线程冲突问题.解决方式有两种,一种是保持操作原子性,另一种是提前给不同的线程分配不同的本地线程分配缓冲TLAB
对象内存布局
构成:对象头+实例数据+对其补充
对象头两部分:运行时数据(哈希码\锁相关信息\GC分代年龄)+类型指针+数组长度(如果是数组类型)
对象起始地址需要时8个字节的整数倍
对象的访问:
通过栈中的reference数据访问堆中的对象.
实现访问的方式有两种:句柄和直接指针,两种方式各有优劣.
句柄,在内存划分句柄池,同时句柄池内存储两个指针:对象指针和类型指针. 使用他的好处在于,由于gc存在对象可能会移动,用句柄的话reference存储的数据比较稳定,只有修改对应句柄中的对象地址. 坏处就是访问对比直接指针多一次指针定位时间开销
直接引用,对象内还包含了对象类型指针.好处在于节省了一次指针定位时间开销. 访问也是常见的操作.
2.3OOM异常
Java堆OOM
限制堆内存最大最小值:-Xms/-Xmx
排查异常过程:
找出占用内存最多的对象,看看是不是必须得对象,即检查是否内存泄漏.
如果不是内存泄漏则是内存溢出,需要看看是否需要扩大一下堆内存空间,对象是否生命周期过长,存储结构是否合理
虚拟机栈和本地方法栈SOM OOM
如果虚拟机支持栈的动态拓展,在申请栈空间时如内存不足可能导致OOM
如果虚拟机不支持栈的动态拓展(栈的大小固定),就只有在创建新线程是内存不足导致OOM;
内存溢出还有两种情况导致StackOverflowError,第一个是栈容量小,新的栈帧无法分配;第二个是栈帧太大,栈空间不足(局部变量表很大);
注意:
内存溢出异常和栈空间是否足够没有直接关系
在一个进程里如果因为线程过多(而且不能删除)情况下,内存溢出了,可以通过降低最大堆空间以及降低线程中栈的容量来换取更多线程
方法区和运行时常量池溢出OOM
常量池溢出,JDK7之后字符串常量池转移到java堆,而且没有永久代的概念,取而代之的是元空间
类溢出和类卸载
直接内存OOM