JVM
2023-09-27 21:16:43 21 举报
AI智能生成
深入理解JVM
作者其他创作
大纲/内容
Java内存区域与内存溢出异常
运行时数据区
JVM(Java虚拟机)运行时数据区是指JVM在运行Java程序时所创建的内存区域,主要用于存储程序运行时所需要的数据。
灰色线程共享数据区,白色线程隔离数据区
程序计数器
一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
字节码解释器,就是通过改变该内存空间内容,来选取下一条需要执行的字节码指令
是程序控制流的指示器,例如 分支、循环、跳转、异常处理、线程恢复
线程恢复是指,多线程条件下,线程切换后,需要知道之前执行字节码指令的位置,每个线程私有
如果执行的是Java方法,计数器存储正在执行字节码指令的地址,如果是Native方法,则为空Undified
虚拟机栈
线程私有,生命周期和线程相同
每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧
局部变量表
存放编译期可知的各种基本数据类型、对象引用、returnAdress类型(指向下一条字节码的地址)
以上类型数据在变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据都只会占用两个变量槽,其余占用1个
局部变量表内存空间在编译期间大小完全确定,运行期间不会改变局部变量表的大小
操作数栈
动态链接
方法出口
每个方法被调用到执行完毕,都对应一个栈帧在虚拟机栈中从入栈到出栈的过程
如果栈的深度>虚拟机所允许深度
StackOverFlowError
虚拟机栈允许扩容发,栈扩展无法申请到足够内存
OutOfMemoryError
本地方法栈
与虚拟机栈很相似,为本地方法Native方法服务的
HotSpot虚拟机直接将Java虚拟机栈和本地方法栈合二为一
Java堆
线程共享,存放对象实例
对象
对象内存布局中的实例数据存放成员变量
数组
垃圾回收器管理的区域范围,也被称为GC堆
Java堆的划分
分代收集理论划分
新生代Young(Coping) 1
Eden 8
用于存放新创建的Java对象,大多数对象都在这里被分配。当Eden区内存满时,触发Minor GC(新生代垃圾回收),将不再使用的对象回收掉。
survivor1 1
survivor2 1
老年代Old(MS MC) 2
用于存放长时间存活的对象,例如服务器应用程序的缓存数据、对象池等。当老年代内存满时,触发Major GC(老年代垃圾回收),一般采用标记-清除(Mark-Sweep)算法或标记-整理(Mark-Compact)算法。
TLAB
Thread Local Allocation Buffer
解决内存争用问题
共享堆划分出多个线程私有的分配缓冲区
Java堆物理上可以不连续,但是逻辑上应该视为连续
Java堆可以实现成固定大小,也可以是可扩展的,主流Java虚拟机都是按照可扩展来实现的
Java堆中没有内存完成实例分配,并且堆也无法再扩展时
OutOfMemory
方法区
线程共享区域,存放已被虚拟机加载的
类型信息(静态数据结构-->动态数据结构 也就是class对象)
对象头中的class pointer 指向
静态变量
JIT编译后的代码缓存
运行时常量池
方法区的一部分
class文件中常量池表,存放编译期间的
字面量
符号引用
永久代
直接内存
NIO,使用Native函数,直接分配堆外内存,然后通过存储在Java堆中的DirectByteBuffer对象作为这块区域的引用(虚引用)进行操作,避免了Java堆和Native堆中来回复制数据
对象的创建
执行字节码new操作时,检查指令参数,是否能在常量池中定位到一个类的符号引用,并检查这个引用代表的类是否被 加载 连接 和 初始化 过,类加载检查通过后↓
为新生对象分配内存,对象所需内存在类加载后完全确定
分配内存方式
指针碰撞
内存空间绝对规整,直接移动新生对象大小空间
是否能用,取决于GC是否采用空间压缩整理
空闲列表
内存空间不规整,虚拟机必须维护一个空闲列表法来进行内存分配
多线程下的内存分配问题
给A、B同时分配内存,可能存在内存争用问题
锁
CAS自旋锁解决
TLAB
Java堆预分配线程私有的内存,在该内存中进行分配
内存初始化
分配完内存,默认初始化,如果使用TLAB,直接在TLAB中完成,确保对象实例字段在Java代码中可以不赋值,直接使用
对象必要信息的设置(对象头)
markword
对象的哈希码
对象的GC分代年龄
class pointer
对象类型
类的元数据信息
new关键字后执行 <init>()方法,按照程序员的意愿进行对象初始化
对象的内存布局
对象头(Object Header)
Mark word
对象自身的运行时数据
HashCode
GC分代年龄
锁状态标志
偏向线程ID
线程持有的锁
偏向时间戳
未开启压缩指针,大小为32位虚拟机为4B 64位虚拟机为8B
是一个动态定义的数据结构
class pointer
指向类型元数据的指针,通过其确认对象类型
数组长度
只有对象是数组时才有,记录数组长度
实例数据(Instance Data)
实例字段内容数据,无论是父类继承,还是子类定义的
对齐填充(Padding)
Hotspot虚拟机自动内存管理,要求对象其实地址必须是8的整数倍,用于填充对齐
对象的访问定位
使用句柄
两次访存,速度慢,但是对象移动,只需要改变句柄中的示例数据指针,引用无需改变
直接指针
速度快,HotSpot使用该方式
OOM实战
堆溢出
虚拟机栈和本地方法栈溢出
方法区和运行时常量池溢出
本机直接内存溢出
垃圾收集器与内存分配策略
判断对象可被回收
引用计数法
对象中添加引用计数器,计数器为0,可被回收
存在循环引用问题
A对象引用B,B对象引用A
可达性分析
GC Roots根对象作为起始节点集,从节点开始,根据引用关系,向下搜索,搜索过程所走过的路径称为引用链
引用链显然是一张网,如果存在连通分量,且到GC Roots根不可达,则不可达部分可被回收
GC Roots
虚拟机栈中的引用对象
本地方法栈中引用对象
方法区中类静态属性引用对象
方法区常量引用对象
同步锁持有的对象
临时性的一些对象
引用
强引用
Object o = new Object();
永远不会被GC回收
软引用
系统要发生内存溢出异常OOM前,会被回收
弱引用
每次GC都会被回收
TreadLocalMap中的Entry的key
虚引用
NIO
对象生存死亡判断
对象的两次标记回收
不可达对象 + 没有重写finalize()/finalize()已被调用 回收
不可达对象 + finalize()未被执行
将未执行对象放入F-Queue队列,稍后由Finalzier线程去执行他们的finalize()方法
并不承诺一定会把finalize执行完,防止阻塞F-Queue队列
回收方法区域
虚拟机规范可以不实现垃圾回收
回收性价比不高
废弃常量
不再使用的类型
所有实例已经被回收
加载该类的类加载器已经被回收
该类对应的class对象没有在任何i地方被引用
废弃常量 + 不再使用的类型
大量使用 反射 动态代理 CGLib等字节码框架 需要JVM具备类型卸载能力,保证不会对方法区造城过大的压力
分代收集理论
假说
弱分代假说
强分代假说
跨代引用假说
java堆分区
新生代(minor/young GC)
eden
survivor
from
suivivor
to
老年代 (major/old GC)
为什么分新生代和老年代?
考虑到堆中有一些顽固对象,回收多次都回收不了,浪费性能,分开以后,把顽固的对象分到老年区,不影响新生区的垃圾回收
为什么新生区分伊甸和survivor?
垃圾回收算法
标记清除MS
先标记再回收
缺点
内存碎片化,导致频繁GC
随对象数量增长,效率变低
标记复制
半区复制
缺点
1:1划分,浪费空间
标记整理MC
先标记,然后向一个方向整理
缺点
Stop The World
垃圾回收器
CMS(老年代)
G1(新生/老年)
内存分配策略
新生对象优先分配到伊甸区
大对象直接进入老年代
长期存活进入老年代
动态对象年龄判定
类文件结构
class类文件结构
以8个字节为基础单位的二进制流
二进制流没有任何分隔符号,无论顺序还是数量都是被严格定义的
魔数与class文件的版本
常量池
访问标志
类索引,父类索引,接口索引集合
字段表集合
方法表集合
属性集合
虚拟机类加载机制
类加载过程
加载
加载的就是class文件类结构的 二进制流
完成三件事
获取二进制字节流
来源可以多种多样
压缩包中,jar、war等
网络中
运行时生成
动态代理
将类的二进制字节流文件所对应的静态结构转化为方法区 运行时动态数据结构
方法区生成代表这个类的class对象,作为这个类的各种数据的访问入口
连接
验证
验证字节码文件符合虚拟机规范
准备
为类变量分配内存,并设置类变量默认初始值
解析
将常量池中的符号引用替换成直接引用
符号转换成直接引用即指针
类或接口解析
字段解析
方法解析
接口方法解析
初始化
真正执行类中编写的java代码
根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源
执行<clinit>()方法
由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生
JVM保证在子类<clinit>()方法执行之前,父类该方法已经执行
多线程同时去初始换一个类,JVM会加锁,使其同步执行
类加载器
类与类加载器
类加载器和这个类本身共同确定在Java虚拟机中的唯一性
同一个类被不同类加载器加载也不相同
三层类加载器
Bootstrap Class Loader
启动类加载器
JAVA_HOME\lib
Extension Class Loader
拓展类加载器
JAVA_HOME\lib\ext
Application Class Loader
应用程序类加载器
加载类路径ClassPath上的类库
三方的
自己写的
双亲委派模型架构
当一个类加载器收到类加载请求时,它首先将这个请求委托给父类加载器去完成,只有在父类加载器无法完成该加载请求时,才尝试自己去加载这个类。这样的好处在于可以保证 Java 类库的安全性,避免类的重复加载,同时也保证了 Java 类库的稳定性和一致性。
优点
双亲委派模型(Parents Delegation Model)
带有优先级的层次关系
建ClassLoader类中的loadClass方法
破坏双亲委派模型
虚拟机字节码执行引擎
运行时栈帧结构
Java内存模型与线程
并发处理广泛应用的根本原因
Amdahl定律替代摩尔定律
Amdahl定律
系统中并行化和串行化的比重,描述多处理器系统获得的运算加速能力
摩尔定律
处理器晶体管数量与运行效率只见那的发展关系
机器硬件的效率与一致性问题
机器内存模型
效率问题
如何解决CPU运算速度与主存IO不匹配的问题?
引入多级高速缓存处理cache,解决速度不匹配的问题
一致性问题
引入缓存后导致了缓存cahe和主存数据不一致的问题?
制定缓存一致性协议MESI,从硬件层次解决一致性问题
cache写策略
处理器对指令顺序重排序,优化程序,增强程序执行效率
只保证了最终结果的一致性
Java内存模型
Java 内存模型(Java Memory Model,JMM)是一种规范,定义了在多线程下 Java 虚拟机如何协调访问共享内存的机制。Java 内存模型定义了 Java 程序在执行时,对内存的读写操作所要遵循的一些规范,以保证多线程之间的通信正确性。
JMM
也会存在一致性问题
解决方式
volatile
定义的意义
关注 虚拟机把变量值存储到内存和从内存中取出变量值的底层细节
这里的变量指的是成员或者静态变量线程共享的
所有的变量都存储到主内存中
对应关系
主内存
Java堆中的对象实例数据
直接对于物理硬件的内存
工作内存
虚拟机栈的部分区域
可能会让工作内存优先存储到cache或者高速缓存找中
内存交互操作
8种原子操作
Volitile
对应的字节码是 lock add
相当于一个内存屏障,防止指令后面指令排在内存屏障之前
并发的三大特性
A原子性
基本数据类型 读写都具备原子性
V可见性
synchronized
volatile
O有序性
volitile
synchronized
线程的实现方式
通过虚拟机参数可以设置线程模型
线程状态转换
图片
0 条评论
下一页