深入jvm虚拟机阅读相关知识点

因为要在公司做了一次jvm的分享这里吧这几天学习的整理了一下分享出来先看一下1.8的jvm内存分布图

在这里插入图片描述

  • 名词解释

在这里插入图片描述
程序计数器

程序计数器是一块很小的内存空间,几乎可以忽略不记,却是运行速度最快的存储区域,
他是程序用来控制比如:循环、异常处理、线程恢复等等一些基础功能都需要依赖于计数器
并且他会记录下一条指令的地址,也就是即将要执行的指令代码

计数器左边的数字可以理解成下一次执行语句的地址,拿到一条指令后先交给解释器
解释器进行翻译成机器码之后交给cpu进行执行,与此同时会将当前线程寄存器里面的
指令地址会由解释器改成下一条要执行的地址
在这里插入图片描述

  • 1、使用PC寄存器存储字节码指令地址有什么作用?
    在我们看来多线程是并行的但是对机器来说他们是并发的,由于cpu切换速度过导致没办法发现,那么机器在来回切换的时候就需要用到PC寄存器这个东西来记录下次切回来要执行什么指令.
    2、PC寄存器线程线程是否私有? 答案是肯定的,因为cpu来回切换的时候为了避免出错最好的办法就是每个线程创建一个PC寄存器来记录当前线程下次要执行的指令地址,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况

虚拟机栈

在每个线程创建的时候都会创建一个虚拟机栈,每个虚拟机栈里面是一个个栈帧.

每一个栈帧就对应每一次的方法调用,帧栈里面保存的就是当前方法里面的相关数据
主要有局部变量表、操作栈、动态链接、返回地址…
在这里插入图片描述

  • 名词解释在这里插入图片描述
  • 局部变量表深入了解

局部变量表里面保存的其实都是数字,这里的数字是变量槽的下下标值我们来看个案例

    public static void main(String[] args) {
        int i = 0;
        long i2 = 2;
        String i3 = "6";
        String i4 = "6";
}

解释

  1. stack=2 当前方法的操作数栈最大深度是2
  2. local=6 局部变量表的最大长度是6
  3. args_size=1 当前方法的参数个数

LineNumberTable:

  1. line 数字 是指字节码的行号 后面的数字是代码的行号

LocalVariableTable:

  1. start: 字段开始的字节码行数
  2. length 字节的作用范围一共是多少行
  3. slot 变量槽的下标值
  4. name 变量名
  5. signature 变量的签名 I是int类型 J是long类型

在这里插入图片描述

变量槽
变量槽就是每个格子是32bit的数组,如果是long类型或者是double类型需要占用64bit的话就占用两个槽位可以通过上面的局部变量表字段名i2看出来 我们定义i2是long类型 占用槽位从2开始下面的i3就直接从4开始了可以看出来占用了两个槽位

案例一:虽然栈帧increment里面的局部变量表里面的i是变成1但是随着方法结束
栈帧increment也随之出栈没有后续操作了并没有影响到栈帧main
在这里插入图片描述
案例二:这里有所不同的是栈帧main和栈帧changeAddress的局部变量表指向的是同一个堆地址值
之后进行了修改操作栈帧main里面的变量值会受到影响
在这里插入图片描述
案例三: 虽然栈帧changeAddress给user重新指向了地址值,但是栈帧main的地址值没受到影响,最根本的原因是因为他们属于不同栈帧里面,传递过来的是实参的地址值,修改的也只是当前栈帧里面的变量指向地址值,不会影响到调用方变量指向
在这里插入图片描述

元空间

元空间跟堆内存一样也是线程共享的区域,其内部主要保存了每个类的信息 比如类名 方法信息 字段信息等等…
需要注意的是在类被编译成.class文件的时候里面就有了静态常量池 加载后会将里面的数据放入元空间的运行时常量池

  • 名词介绍

在这里插入图片描述在这里插入图片描述
堆内存

堆内存针对一个jvm来说是唯一的,但是jvm里面有很多线程 这些线程共享这一个堆,堆是在jvm启动的时候就被创建且大小固定(可通过参数调节),先来看一下堆内存的空间分布
在这里插入图片描述

  1. 标记清除算法

首先会对 需要回收的对象进行标记之后统一回收 这里的标记通过引用计数法、可达性分析法这两种方式来标记
存在问题: 从图上可以看到 标记清除后的内存空间碎片是不连续的,假如有一个大的对象被创建而内存中又没有该对象的所需的连续内存空间,这时候就需要提前进行一次GC
在这里插入图片描述

  1. 复制算法

为了解决标记清除法出现的内存空间不连续,和效率低下的问题 衍射出了另一种算法 复制算法
刚开始复制算法是将内存划分成两块一样大小的空间A,B 当A空间用完就将A空间的所有存活对象移动到B顺序分配内存即可,后面发现一半的空间太大了于是又提出了eden 和2*survivor,当eden满了之后触发gc会将存活对象转入其中一个survivorA,第二次eden满了会将eden存活对象和survivorA里面的存活对象转入另一个survivorB再清空survivorA,以此类推.
hotspot默认eden:survivor = 8:1在这里插入图片描述

  • 复制算法存在问题:
    假设出现了一个极端情况,我们再一次Minor GC的时候存活对象的总和大于其中一个survivor空间的大小,那么这时候怎么办?
    这时候引入了分配担保: 空间分配担保更像是Minor GC的一个兜底操作,如果本次Minor GC后存活的对象大于空出来的survivor空间,这时会将survivor无法容纳的对象直接进入老年代(每次一Minor GC如果有存活的对象就会给其标记年龄+1 到达默认值15岁就会进入老年代) .
    如果本次进入老年代的对象也大于老年代剩余空间大小 就会进行一次full GC 具体流程如下

每次Minor GC之前老年代都会进行一次判断,检查当前老年代最大连续空间是否大于当前年轻代对象总和,大于就直接执行Minor GC小于的话就会通过HandlerPromotionFailure进行判断没开启就执行Full GC开启了就会判断历次进入老年代的对象大小平局值是否小于当前剩余内存,是的话就执行Full GC反之执行Minor GC,如果成功就结束失败就进行空间担保,如果空间担保失败才进行Full GC
在这里插入图片描述

  • 在通过代码来看一下

1: 初始化的时候eden占用了42%的内存
2、3: 添加了a1、a2这时候eden内存来到了96%
4: 再添加a3的时候会内存不够分配失败,触发Minor gc将eden存活对象转入survivor,而因为有空间担保所以survivor无法容纳的对象进入old,a3进入eden
6、7: 添加a4 a5内存来到79%这时候eden里面有a3、a4、 a5
8: 内存不够触发经过判断最终执行Minor gc发现survivor和old空间都不够触发full gc清除了eden里面的a3添加了a6
9: 分配失败 经过判断直接执行full gc清除a4 a5 a6在eden里面添加a7
最终: eden->a7 old->a1、a2
在这里插入图片描述

  1. 标记清楚整理算法

主要用作老年代里面使用的,其算法跟标记清除法相同只不过多了一步整理的操作,这样可以保证老年代的空间不会是碎片但是也意味着耗时
在这里插入图片描述

垃圾收集器

  1. serial收集器:该收集器是单线程工作的时候需要其他进程全部停止开始收集年轻代和老年代收集结束后在执行用户的线程,虽然在其收集垃圾的时候需要暂停用户的线程但是由于serial只需要关注收集垃圾的工作所以其效率是非常高的只是暂停用户线程让人不能接受,运行示意如图在这里插入图片描述
  2. parNew收集器:parNew收集器就相当于serial的多线程版本,但其实在cpu核心数较少的情况下parNew不一定有serial收集器的效率高其运行如图在这里插入图片描述
  3. Parallel Scavenge收集器:该收集器主要是通过设置两个参数来控制垃圾收集效率的分别是 控制垃圾回收最大停顿时间的-XX:MaxGCPauseMillis和控制吞吐量的-XX:GCTimeRatio 吞吐量:吞吐量就是用户运行代码的时间/用户运行代码的时间+垃圾收集时间 这里很多同学一定决定只要把-XX:MaxGCPausesMillis设置的越小不效率不就越高吗 但事实并不是这样,虽然垃圾收集的时间变短了但是收集的不干净会导致空间不足收集的次数变多比如以前100秒收集一次 一次10秒 现在50秒收集一次一次7秒显而易见并不是越小越好. 这里说下-XX:GCTimeRatio的值设置的是用户运行代码时间 比如 -XXGCTimeRatio=19 那么gc所占比例就是 1/1+19 = 5%
    很多时候大家都不知道应该怎么设置这些参数,所以Parallel Scavenge收集器有一个开关项参数 -XX:UseAdaptivePolicy 加了该参数后收集器会自动根据当前性能自动调节各项参数

GC日志阅读

先来一段gc的日志直接分析

[GC 611.633: [DefNew: 843458K->2K(948864K), 0.0059180 secs] 
2186589K->1343132K(3057292K), 0.0059490 secs] 

GC开头的话说明本次是moniorGC
DefNew:这里大多是指gc发生的位置
843458k: 当前发发生gc位置在gc前使用的内存大小
2k: 当前位置gc后使用内存大小
(948864k): 当前位置总的内存大小
0.0059180 secs: 本次gc耗时秒为单位
2186589K: 当前堆内存gc前已使用大小
1343132K: 当前堆内存gc后已使用大小
3057292K: 当前堆内存总大小


有了上面的基础我们再看下下面的
[GC (System.gc()) [PSYoungGen: 3932K->1032K(76288K)] 
3932K->1040K(251392K), 0.0016272 secs]
// 这个可以看出是发生在年轻代的minorGC

[Full GC (System.gc()) [PSYoungGen: 1032K->0K(76288K)] 
[ParOldGen: 8K->856K(175104K)] 1040K->856K(251392K), 
[Metaspace: 3360K->3360K(1056768K)], 0.0059025 secs] 

这个是FULL开头说明是 stop the world 值得注意的是Metaspace的意思
是元空间也就是替换方法区的空间

-XX:SurvivorRatio解析

这里的-XX:SurvivorRatio的设置含义是反过来的,举个例子
-XX:SurvivorRatio=8 这里的意思是Survivor是Edan的八分之一
那么Survivor占整个新生代的多少呢?
答案是:五分之一
(1+1)/(1+1+8) = 五分之一 这里的8是edan两个1都是survivor

-XX:MaxTenuringThreshold解析

该参数很简单,就是设置年轻代里面的对象在到达多少岁后进入老年代,一般都是15岁,这里岁数就是随着年轻代的复制算法来的,只要熬过一轮赋值算法年龄就加一岁

动态对象年龄判断

其实上面的设置年龄是有问题的,想想一个场景加入当前执行完赋值算法后,我所在的survivor空间已经满了,且本次没有任何对象被回收那么下次一定会出现分配担保,于是就有了动态对象年龄判断这个东西
大致也就是一个约定:约定的内容就是如果当前survivor里面相同年龄大小的对象所占空间总和已经大于另一个survivor空间的一半那么当前年龄大于等于该年龄段的存活对象就直接进入老年代

空间分配担保

空间分配担保就是如果新生代用的是复制算法,如果当前复制算法出现了survivor内存不够下次进行赋值算法那么就会将当前应该进入surivivor的所有对象,分配担保到老年代
但是! 在每次minor GC的时候虚拟机都会计算一下当前老年代的最大连续空间是否大于新生代的所有对象总和,因为假设最极端的情况下新生代的复制算法进行的时候所有对象都没有被回收都是存活状态,那么就会出现分配担保的对象老年代没法接收出现了分配担保失败的情况,
这里还有个开关就是HandlePromotionFailure如果这个开关设置false就意味着,不同意其出现minor GC分配担保失败就会提前进行一次full GC
不过一般这个开关都是设置开启的,所以如果出现老年代的连续空间小于新生代所有对象总和,还会先通过往届分配到老年代的对象平均值作为经验值来判断是否要进行full gc来担保本次对象不过这种方式还是会有问题,就是假设某次回收对象空间骤增远超以前的平均值,这时候开关还是开着的那只好通过full gc来进行一次回收,这样兜圈子最大但是为了避免频繁的full gc开关还是开着好

怎么获取到Dump文件

有时候线上环境出现了问题,但是从表面看不出来任何问题,这时候需要分析jvm的相关数据就需要我们拿到jump的相关数据来进行分析了,在这里我们可以通过启动的时候添加参数
-XX:+HeapDumpOnOutOfMemoryError
如果jvm出现了oom的时候jvm就会自动生成jump的文件,来提供参考
这时候有同学就会问,jump文件输出到什么位置了呢,那么又引出了另一个参数
XX:HeapDumpPath=路径地址即可 如: /usr/local/test.dump
也可以通过手动直接生成dump文件
jmap -dump:format=b,file=路径 pid(就是当前运行服务的pid也就是jvm的pid)
路径就是要输出的hprof的路径如 /iflytek/test.hprof

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值