JVM体系基础知识
2021-05-04 20:10:43 29 举报
AI智能生成
jvm
作者其他创作
大纲/内容
JVM
作用:可实现跨平台编译,是java虚拟机
组成部分
堆
用来存储对象
结构
新生代(年轻代)
组成
Eden(伊甸园)
较大空间
Survivor
Survivor0
Survivor1
空间分配比例8:1:1
记忆集
概念:当年轻代里有被老年代引用的对象的时候,会在年轻代里开辟空间存储老年代的引用地址,以免年轻代里的对象无法回收。解决跨代引用问题
用来存储年轻代对象在老年代里引用的地址
卡表:记忆集的具体实现
实现原理:写屏障
老年代
空间比例新生代:老年代 = 1:2
方法区(永久代)
存储一些静态常量,类信息等。一般不回收
对象的生命周期
创建对象
存储到Eden伊甸园区
存满之后字节码执行引擎开启垃圾回收线程minor gc
将找到的非垃圾对象转入到survivor的s0区域内
将Eden,s0,s1内的所有垃圾对象进行回收
当s0区域内的对象再次筛选时仍被标记为非垃圾对象
对象被复制到s1区
Eden和s0,s1区域内所有垃圾对象进行回收
将对象再次复制到s0区域
分代年龄+1
分代年龄存储在对象头内存区域Header
当分带年龄达到15就会被复制到老年代
当老年代满了之后,执行引擎开启full gc
如full gc无法再回收垃圾对象,则内存溢出(oom)
gc 算法
算法原理
标记清除算法(Mark-Sweep)
缺点:容易产生碎片,内存不连续
复制算法(Copying)
缺点:不会产生碎片,比较浪费空间
标记整理压缩算法(Mark-Compact)
缺点:没有碎片,效率比较低
算法分类
引用计数器算法(很少使用)
给对象添加一个引用计数器,每当有一个地方引用它,计数器值就加一;相反的,当引用失效的时候,计数器值就减一;任何时刻计数器为0的对象就是不可能再被使用的
缺点:当存在堆内对象相互引用的情况无法进行计数器清0,但实际上已经没有引用,导致无法回收
如:Node a=new Node();
Node b=new Node();
a.next=b;
b.next=a;a=null;
b=null;
Node b=new Node();
a.next=b;
b.next=a;a=null;
b=null;
a,b变量已经变为null,失去了栈与堆对象之间的联系,所以堆对象应该做清除,但是此时还是有a和b之间的引用,计数器不是0,即无法做垃圾回收。
可达性分析算法(jvm使用)
GC roots跟节点:包含线程栈的本地变量,静态变量,本地方法栈变量,方法区的变量等。
原理:将GC roots跟节点作为起点,从节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余均为垃圾对象
过程:将jvm内的所有跟节点及其引用对象找出来
标记为垃圾和非垃圾
垃圾回收处理器分类
串行收集器(jdk1.0默认使用)
serial
年轻代
复制算法
单线程
效率较慢
serial old
老年代
整理算法
单线程
效率较慢
并行收集器(jdk8默认使用的收集器)
parallel scavenge(ps)
年轻代
复制算法
多线程
parallel old(po)
老年代
整理算法
多线程
并发收集器(jdk4版本时发明,但未被使用默认,其它收集器会有stw,效率较低。cms的stw时间很短,效率高)
parNew(pararllel改良版)
年轻代
复制算法
多线程
CMS
老年代
标记清除算法
多线程
CMS原理
三色标记算法
三种颜色
白色
还未被标记算法检索到
灰色
自身被标记,下一级还没有被标记
黑色
自身被标记,下一级也被标记
多线程同时配合标记
浮动垃圾
当对象引用被赋值为null置位垃圾对象的时候,但此时gcroots检索已经走完,导致此对象未来得急被标记为垃圾对象,叫浮动垃圾
漏标
当对象引用被赋值为null垃圾对象的时候,又被其父父对象引用,但此时,gcroots已经检索到当前对象的父节点,其父父对象已经被标记过了,导致无法检索到当前对象,此时该对象会被当做垃圾对象。但实际上已经被父父对象引用而非垃圾对象,叫错标。
解决方案:写屏障
通过写屏障(类似aop切面)操作,监控引用对象在引用的时候如果是黑色的且当前对象为白色,就标记父级为灰色
解决方案:增量更新
只要有黑色对象新加了指向白色对象的引用关系,把这个新插入的引用记录下来,等并发扫描结束后,在将这个黑色对象为根重新扫描。
CMS过程
初始标记(stw)
并发标记
重新标记(stw)
并发清除
CMS缺点
有碎片垃圾
有错标,浮标问题
cms优点
可以与用户线程同时进行
G1回收处理器(jdk9中默认使用)
分区方法:G1默认是将堆划分为2048个大小相等的独立区域(Region),保留了概念隔离,不再是物理隔离,每个格子的角色不固定。格子分配比例年轻代8:1:1,年轻代默认占比5%。
区别:不再是分代处理器,是分区处理器
Humongous:大对象
g1不像是之前的垃圾处理器,大对象直接放入老年代,g1开辟一个新空间存储大对象
G1过程
初始标记(stw)
并发标记
最终标记(stw)
筛选回收(stw)
此处cwt是与cms的主要区别
g1可以设置总ctw时间(-XX:MaxGCPauseMillis默认200ms),在gc之前会先预估回收时间,只做部分回收。
jvm优化核心调整的参数
预估时间的算法:
内部会按照回收效益比排序,优先回收高的
整体使用标记整理算法
使用复制算法实现:将存活对象复制到空白区域,然后将其它清除掉
Mixed Gc
当老年代占有率达到参数(默认45%)设置的比例时触发
回收所有的年轻代和部分老年代(根据回收效益比计算)
young Gc
再做youngGc之前会先计算本次清理时间是否到达设置的回收时间,如果没到,继续把年轻代增加空间
full Gc
单线程收集
stw
缺点
适合于大内存服务器
算法复杂,如果服务器内存小,效率不如cms
漏标
解决方案:
SATB(原始快照)
与CMS区别
SATB效率更高
ZGC
垃圾处理器选择
4g以下的建议使用parallel
4-8g建议使用prarNew + cms
8g建议使用g1
百g建议使用ZGC
CMS和G1的区别
堆空间上分配的不同:
1 CMS收集器:将堆空间分成Eden、Servivor、old,并且他们是固定大小,JVM启动的时候设定且不能改变。
2 G1收集器:将堆空间分成多个大小相同的Region区域,逻辑上分Eden/Servivor、old,且大小是可变的,每次会根据GC的信息做出调整。
gc策略的不同:
CMS:存在minor GC、full GC,且CMS的年轻代使用(ParNew)去完成,老年代使用CMS去完成。
G1:区分三种策略,minor Gc、mixed GC 、 full GC, 而mixedGC会回收整个yong区,回收部分old区
1 CMS收集器:将堆空间分成Eden、Servivor、old,并且他们是固定大小,JVM启动的时候设定且不能改变。
2 G1收集器:将堆空间分成多个大小相同的Region区域,逻辑上分Eden/Servivor、old,且大小是可变的,每次会根据GC的信息做出调整。
gc策略的不同:
CMS:存在minor GC、full GC,且CMS的年轻代使用(ParNew)去完成,老年代使用CMS去完成。
G1:区分三种策略,minor Gc、mixed GC 、 full GC, 而mixedGC会回收整个yong区,回收部分old区
minor gc(young gc)(年轻代gc)
用途
用来回收年轻代里的垃圾对象
算法
复制算法
full gc/Major gc(老年代gc)
用途
用来回收年轻代和老年代的垃圾对象
算法
cms
标记清除
大多数使用
标记整理
老年代对象分配机制
分代年龄达到15(默认,CMS收集器默认是6)
大对象直接进入老年代(Eden区放不下即为大对象)
对象年龄动态判断机制
当survivor内一批对象的大小总和超过区域的50%,就会直接进入老年代
老年代空间分配担保机制
栈(线程)
每一个线程运行的时候,都会分配一块专属空间。
线程里的每一个方法运行都会在这块空间再分配一块栈帧
栈帧
栈帧主要包含
局部变量表
int a;int b;
如果是对象类型,存储的是堆的内存地址,指针引用的关系
操作数栈
定义:当程序给变量赋值的时候,会把数值1压入到操作数栈里(进栈),然后把值赋值给局部变量a(出栈)。
a = 1;
动态链接
把符号引用转变成直接引用。常量池是存在方法区里,动态链接会把方法区的内存地址存储下来,程序运行到该方法的时候,会根据这个动态链接到方法区查找运行时常量。
方法出口
当调用方法时,就把返回行数信息存储在方法出口区域,当方法执行完毕后会根据存储的信息继续向下执行代码。
当方法执行结束,栈帧即销毁
查看栈帧的执行过程
通过反编译字节码文件
javap -c GroupUserController.class > a.text
可查看底层执行过程(可查看JVM指令手册)
栈(本地方法栈)
native修饰的方法叫本地方法
用来存储本地方法
本地方法可以看做是一个接口,底层是调用其它语言的入口,一般是c语言。
方法区(元空间,永久代)
主要存储
常量
public static final int a = 1;
静态变量
public static User u = new User();
类信息
存储类的字节码信息
如果存储的是对象信息,那么存储的也是堆内存地址。
程序计数器
每一个线程运行的时候,都会分配一块专属空间,用来存储当前线程运行的内存地址,这块区域叫程序计数器
作用:当多个线程执行的时候,当前线程被其它优先级更高的线程中断,需要记录下当前线程的执行位置。以免继续执行的时候会重复执行
字节码执行引擎做修改
结构图
字节码执行引擎
用途
修改程序计数器
创建gc垃圾回收线程
jvm调优
工具
jvisualvm
命令行打开
arthas(常用)
阿里开源调优工具
文档网址
https://arthas.aliyun.com/doc/quick-start.html
调优原因
jvm底层在做垃圾回收的时候,会暂停用户线程(STW,stop the world),所以要减少垃圾回收
oom
核心目标
减少minorGC和full GC(主要)
调优案例
如图中空间分配:年轻代1G 老年代2G空间 s0区域100m s1区域100m
产生原因:当用户线程产生每秒60M的数据到eden区域,执行minnor gc时将60m数据复制到s0区域,由于老年代的动态年龄判断机制,会直接将数据复制到老年代区域而不是进入s0区域,所以会产生老年代内存溢出问题。
解决方法:如上图所示,将年轻代大小增加1g,老年代大小减少1g。这样就不会触发动态年龄判断机制。
参数配置
Xmn:年轻代空间大小
Xss:每个线程栈大小
Xms:每个线程的初始堆大小,默认物理内存的1/64
Xmx:最小堆大小
调优实例
线上环境不能给图形化界面开放端口
压测环境使用图形化界面连接服务器
图形化界面远程链接连高可用集群中的一个
负载摘除
流量回放
cpu突然飙高怎么办:
1,定位-先查看是什么程序占用cpu:arthas-thread命令
2,看线程是业务线程还是系统线程
业务线程
查看调用栈,看看是哪个方法在占用cpu
系统线程
查看gc问题,查看是哪里出现无法gc
jvm类加载机制
加载过程
加载
写入jvm虚拟机的方法区,同时创建堆class对象
连接
验证
校验字节码文件的正确性
准备
为类的静态变量分配内存并初始化默认值(如static int a= 5,此时只是分配默认值0,5会在初始化的时候赋值),预分配内存
解析
把类中的符号引用转换为直接引用
初始化
对类的静态变量初始化为指定的值,执行静态代码块
类加载器
1,引导类加载器(Bootstrap Classloader)
负责加载jre/lib下的核心类库
底层由c++实现
2,扩展类加载器(Extension Classloader)
负责加载jre/lib/ext下的1包
3,应用类加载器(Application Classloader)
负责加载classpath下的class字节码文件
4,自定义类加载器
负责加载用户自定义目录下的class文件
双亲委派机制
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
优点
避免类的重复加载,当父加载器已经加载过该类,没有必要子加载器再加载一遍,保证了唯一性
沙箱安全机制,自己写的String类不会被加载,可以防止核心api库被随意篡改
0 条评论
下一页