jvm的知识体系
2022-03-09 08:40:58 0 举报
AI智能生成
JVM
作者其他创作
大纲/内容
图解
1、通过一个类的全限定名获取定义此类的二进制字节流
2、将字节流所代表的的静态存储结构转化为方法区的运行时数据结构
3、在java堆中生成了一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
装载
文件格式验证/元数据验证/字节码验证/符号引用验证
1、验证:保证被加载类的正确性
为类的静态变量分配内存,并将其初始化为默认值
2、准备
把类中的符号引用转换为直接引用
3、解析
链接
对类的静态变量,静态代码块执行初始化操作
初始化
如果一个类加载器在接到加载类的请求时,它首先不会自己尝试加载这个类,而是把这个请求任务委托给父类加载器去完成,一次递归,如果父类加载器可以完成加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
定义
java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。
例如:java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此object类在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类
优势
双亲委派原则
类加载器
类加载机制
缺点:循环引用无法辨别是垃圾,一旦相互持有引用,就导致对象永远无法被回收
引用计数法
如何判断一个对象是垃圾
GC Root:类加载器、Thread、本地变量表、static成员、常用引用、本地方法栈中的变量
由GCroot出发,开始寻找,看看某个对象是否可达
可达性分析
子主题
标记清除
复制
标记整理
垃圾回收算法
young区:复制算法
old区:标记清除或标记整理
分代收集算法
单线程的收集、适用于新生代、暂停用户代码
1、Serial
2、Serial Old
多线程收集、复制算法、 适用于新生代
3、ParNew
相对于ParNew,更加关注于吞吐量
4、Parallel
标记整理的算法
5、Parallel Old
初始标记非常快所以用单线程
6、CMS
7、G1
jdk11 增加一个新的 :ZGC
垃圾收集器
Serial
Serial Old
只能有一个垃圾回收线程执行,用户线程暂停。
适用于内存比较小的嵌入式设备
1、串行收集器
Parallel Scanvenge
Parallel Old
多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
适用于科学计算、后台处理等交互场景
2、并行收集器
CMS
G1
用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集器在执行的时候不会停顿用户线程的运行。
适用于相对时间有要求的场景,比如web
3、并发收集器
垃圾收集器分类
停顿时间越短越适合需要和用户交互的程序,良好的响应速度能提升用户体验
停顿时间->垃圾收集器进行垃圾回收终端应用执行响应的时间
高吞吐量则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务
吞吐量->运行用户代码时间/(运行用户代码时间+垃圾收集时间)
两个指标也是评价垃圾回收器好处的标准,其实调优也就是在观察者两个变量。
吞吐量和停顿时间
优先调整堆的大小让服务器自己来选择
如果内存小于100M,使用串行收集器
如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
如果允许时间停顿超过1秒,选择并行或JVM自己选
如果响应时间最重要,并且不能超过1秒,使用并发收集器
如何选择合适的垃圾收集器
垃圾回收
内存溢出
用mat工具处理
OOM
打印GC日志文件
gceasy.io
分析工具
吞吐量
停顿时间
追求高吞吐量低停顿
关注点
GC频繁
死锁
线程池不够用
CPU负载过高
发现问题
打印出GC日志,查看minor gc/major gc,结合工具gc viewer/gceasy.io
jstack查看线程堆栈信息
dump出堆文件,使用MAT或者其他工具分析
合理使用jdk自带的jconsole,jvisualvm,阿里的arthas等实时查看JVM状态
灵活应用jps,jinfo,jstat,jmap等常用命令
排查问题
适当增加堆内存大小/选择合适的垃圾收集器
使用zk,redis实现分布式锁
设置本地,Nginx等缓存减少对后端服务器的访问
后端代码优化及时释放资源/合理设置线程池中的参数
集群部署从而减少单节点的压力
利用一些消息中间件比如MQ,Kafka实现异步消息
解决方案
性能优化 指南
GC优化
性能优化
所有的对象都在堆里面进行分配
堆是java虚拟机所管理内存中最大的一块
起始:在虚拟机启动时创建,被所有线程共享
java对象实例以及数组都在堆上分配
当堆无法满足内存分配需求时,将抛出OutOfMemoryError
堆
方法区是各个线程共享的内存区域,在虚拟机启动时创建
方法区记录的是方法以外的一些数据
存储内容:被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的是和java堆区分开来
异常产生:方法区无法满足内存分配需求时,抛出OutOfMemoryError异常
jdk8:Metaspace【元空间】
jdk6或7中就是Perm Space【永久代】
jdk的版本区分
Run-Time Constant Pool 在方法区分配
方法区
jvm
虚拟机栈是一个线程执行的区域,保存着线程中方法的调用状态。
虚拟机栈肯定是线程私有,独有的,随着线程的创建而创建
一个java线程的运行状态,由一个虚拟机栈来保存
是什么?
每个线程执行的方法,为该栈的栈帧,即每个方法的执行对应一个栈帧
每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间
局部变量表
操作数栈
程序运行当中会动态调用某些类的方法
动态链接
上一个方法执行完,返回到指定行数
方法返回地址
附加信息
每个栈帧中包含
细节
栈帧
调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出
java虚拟机栈
当前线程所执行方法的位置,在虚拟机栈中 The PC register
一个jvm进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据cpu调度来的。假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到了线程B了,然后当xianchengA再获得Cpu执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到的位置
1、程序计数器占用的内存空间很小,由于java虚拟机的多线程是通过线程轮换切换,并分配处理器执行时间来实现的,任意时刻,一个处理器只会执行一个独立的程序计数器(线程私有)
2、如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址
3、如果正在执行的是Navicat方法,则这个计数器为空
程序计数器的说明
线程私有
程序计数器
本地方法栈
运行时数据区
因为Eden是负责存放新生对象的区域,大部分的对象都是朝生夕死的因此必然会占用大量的Eden区域,而如果变小的话会造成Eden不够使用的情况。Survivor区的存在是为了在GC过程中活下来的作为存留,也是为了避免产生碎片化的空间
为何如此分配?
young区
如果没有Eden和survivor区会导致GC后出现空间碎片化,导致空间不连续会造成大的对象可能会过早的分配不下导致过早的GC
正常对象创建所在区域,大多数对象“朝生夕死”
Eden
Survivor区分为S0和S1,也可以叫From和To
同一时间点上,S0和S1只能有一个区有数据,另外一个是空的,也是为了处理空间碎片化的问题
Survivor
一般old区都是年龄比较大的对象,或者相对超过了某个阈值的对象
Survivor区如果分配不下了会向old区借空间
old
命令 jvisualvm
工具
jvm内存模型
标准参数
-x参数
-XX:[+/-]
-XX:+UseG1GC
-XX:<name>=<value>
-XX:InitialHeapSize = 100M
-XX参数
-Xms100M ==》 -XX:InitialHeapSize=100M
-Xmx100M
-Xss100
其他参数
JVM参数
可以查看当前的java进程
jps
可以查看某个java进程目前参数设置的情况
示例2
示例1
示例
jinfo
查看java进程的统计信息
查看当前进程类加载的统计情况 10秒 打印十次
查看当前进程的GC情况
jstat
查看当前java进程的堆栈信息
可以用于查看死锁的情况排查
jstack
jmap -heap PID
获取堆内存信息
idea
排查内存泄露
打印出堆转存储快照
jmap
JVM命令
jconsole
监听某个java进程
jvisualvm
阿里的一个工具
arthas
内存相关信息
mat/perfma
gceasy.io/gcviewer
常用工具
JVM工具
收藏
收藏
0 条评论
回复 删除
下一页