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