jvm 相关知识点整理

二. JVM

1. 类加载过程

加载、链接(验证、准备、解析)、初始化、使用、卸载
加载 加载指的是把class字节码文件从各个来源通过类加载器装载入内存中
验证 主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。(文件格式的验证,private,重载)
准备 主要是为类变量(注意,不是实例变量)分配内存,并且jvm为每种类型的数据赋予初始值。
解析 在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
符号引用:类名; 直接引用:类地址;
初始化 这个阶段主要是对类变量初始化,是执行类构造器的过程。静态变量、静态代码块调用、类中各种方法引用顺序,父子类加载

2. 类加载器分类

  1. 启动类加载器:bootstrapclassLoader 负责JAVA_HOME\lib目录中的; 通过-Xbootclassath参数指定路径中的 被虚拟机认可的类(rt.jar) 由C++实现,不是classLoader
  2. 扩展类加载器(ExtensionclassLoader) 负责加载JAVA_HOME\lib\ext目录中的 或者通过java.ext.dirs系统变量指定的类库
  3. 应用类加载器(applicationclassloader) 负责加载用户路径(classpath)上的类库
  4. 自定义加载器 若要实现自定义类加载器,只需要继承java.lang.ClassLoader类,并且重写findClass()方法即可。java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成对应的字节码,然后从这些字节码中定义出一个Java类,即java.lang.Class类的一个实例。
    (1) 一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。
    (2) 另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

3. 双亲委派

  1. 自底向上检查(loadClass())、自顶向下尝试加载 如果一个类加载器收到了类请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每一层都是如此,因此所有类加载的请求都会传到启动类加载器,只有当父加载器无法完成该请求时,子加载器才去自己加载。
  2. 双亲委派作用: 沙箱操作:防止黑客写的String类,覆盖原有的类 防止重复加载:防止一个类被加载两次。
  3. 打破双亲委派
    Tomcat启动多个应用程序,不能加载多个版本类名相同的包(User)

4. JVM内存结构

在这里插入图片描述

  1. JVM内存参数设置
    在这里插入图片描述

元空间默认是21M,达到该值就会触发full gc进行类型卸载,同时收集器会对该值进行自动扩容调整(-XX:MaxMetaspaceSize)
JVM默认有这个参数-XX:+UseAdaptiveSizePolicy(默认开启),会导致这个8:1:1比例自动变化。如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\jvm.dump
java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar microservice‐eurek a‐server.jar
3. Hotspot中对象的存储结构
在这里插入图片描述
4. 逃逸分析
当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针(或对象)的逃逸。
不逃逸的对象,其创建时,可能在java栈中直接创建,而不是堆。
具体而言,分为方法逃逸和线程逃逸两种。
方法逃逸:在一个方法体内,定义一个局部变量,而它可能被外部方法引用,比如作为调用参数传递给方法,或作为对象直接返回。或者,可以理解成对象跳出了方法。
线程逃逸:这个对象被其他线程访问到,比如赋值给了实例变量,并被其他线程访问到了。对象逃出了当前线程
逃逸分析和标量替换在JDK1.7以后,默认开启
5. 内存分配的方法
指针碰撞:java堆中的内存绝对规整
空闲列表:java堆中的内存存储随机
解决并发分配内存问题:1.CAS失败重试。2.每个线程在java堆中预先分配一块小内存(-XX:TLABSize);
在这里插入图片描述
TLAB是堆内存的一部分,他在读取上确实是线程共享的,但是在内存分配上,是线程独享的。TLAB默认是eden区的1%

5. JVM垃圾回收

1. 垃圾回收算法

引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
可达性分析算法
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等。
将“GC Roots”对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象

2. 垃圾回收器

在这里插入图片描述

Serial(-XX:+UseSerialGC -XX:+UseSerialOldGC)

单线程执行,停下所有任务去回收;新生代-复制算法。老年代采用标记-整理算法。

Parallel Scavenge收集器(-XX:+UseParallelGC,-XX:+UseParallelOldGC)

(JDK8默认的新生代和老年代收集器)Serial的多线程版本。关注点是吞吐量(高效率的利用CPU)。新生代采用复制算法,老年代采用标记-整理算法。

ParNew收集器(-XX:+UseParNewGC)

新生代采用复制算法。主要用于和CMS配套使用,现在主流用ParNew+CMS。

CMS收集器:(-XX:MaxTenuringThreshold)

回收年代=6,一般收集器为15,四位标识最大15,该收集器是⼀种以获取最短回收停顿时间为⽬标的收集器。注重⽤户体验。
缺点:
(1)浮动垃圾。
(2)有大量空间碎片,可通过参数XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理.
(3)如果出现上一次垃圾回收还没执行完又被触发(尤其是并发标记和并发清理阶段),会产生就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器。
标记-清除算法实现
初始标记:(STW),并记录下gcroots直接能引用的对象,速度快。
并发标记:并发标记阶段就是从GCRoots的直接关联对象,这个过程用户线程可以与垃圾收集线程一起并发运行。
重新标记:(STW)重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。主要用到三色标记里的增量更新算法。
并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。
并发重置:重置本次GC过程中的标记数据。
CMS相关配置参数
在这里插入图片描述

G1收集器(-XX:+UseG1GC)(JDK9默认)

是⼀款⾯向服务器的垃圾收集器,面向的范围是整个堆内存,把整个堆内存划分为2048大小相等的独立区域(Region)。维护了一个列表用于记录每个Region回收的价值大小Remembered Set.
垃圾回收:
初始标记(initial mark,STW):暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快;
并发标记(Concurrent Marking):同CMS的并发标记
最终标记(Remark,STW):同CMS的重新标记
筛选回收(Cleanup,STW):筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间(可以用JVM参数-XX:MaxGCPauseMillis指定)来制定回收计划。回收算法主要从整体来看是标记整理,局部来看用的是复制算法
G1垃圾收集分类
YoungGC Eden区放满了,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数-XX:MaxGCPauseMills设定的值,那么增加年轻代的region,继续给新对象存放,直到G1计算回收时间接近参数-XX:MaxGCPauseMills设定的值,那么就会触发Young GC
MixedGC不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,复制算法执行中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC;
Full GC 停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。
在这里插入图片描述

什么场景适合使用G1
1.50%以上的堆被存活对象占用
2.对象分配和晋升的速度变化非常大
3.垃圾回收时间特别长,超过1秒
4.8GB以上的堆内存(建议值)
5.停顿时间是500ms以内

3. 如何判断一个类是无用的类

类需要同时满足下面3个条件才能算是“无用的类”:

  1. 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

4. 对象何时进入老年代

  1. 大对象直接进入老年代
  2. 长期存活的对象进入老年代
  3. 对象动态年龄判断
    Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年龄判断机制一般是在minorgc之后触发的。
  4. 老年代空间分配担保机制
    在这里插入图片描述

5. 三色标记

黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。
白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
在这里插入图片描述

漏标-读写屏障
漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案:增量更新(Incremental Update)和原始快照(Snapshot At The Beginning,SATB)
增量更新(CMS默认)就是当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。(黑色对象变回灰色对象)。
原始快照STAB(G1默认)
当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。
二者通过写屏障实现

6. JVM调优

1. JVM调优基本命令

jps
查看运行的程序及其pid
jstack pid
查看运行状态,是否含有死锁
jmap
此命令可以用来查看内存信息,实例个数以及占用内存大小
jmap -histo pid {>} ./log.txt 将内存信息输出到文件里
jmap -heap pid 查看堆信息
jmap -dump:format=b,file=cqjy.hprof pid 到处某时刻堆的快照信息
top -p pid
显示Java进程的内存情况,按H获取每个线程的内存情况获取到占用CPU最高的线程tid,将tid转为十六进制(小写)
jstack pid|grep -A 10 tid 得到线程堆栈信息中tid这个线程所在行的后面10行
jinfo
查看正在运行的Java应用程序的扩展参数
jinfo -flags pid 查看jvm的参数及启动时设置的参数是否生效
jinfo -sysprops pid 查看系统参数
jstat
查看堆内存的使用量
jstat -gc pid 垃圾回收统计
jstat -gc pid m n 秒钟执行一次输出命令,执行m次

2. 系统频繁卡顿执行full gc

调优主要考虑的是减少full gc的次数
机器2核4G,JVM:2G,系统一天内发生70多次full gc,每次400毫秒
JVM参数设置
-Xms1536M -Xmx1536M -Xmn512M -Xss256K -XX:SurvivorRatio=6 -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
CMSInitiatingOccupancyFraction 老年代达到75出发full gc
UseCMSInitiatingOccupancyOnly 使上面参数生效,如果不配置,则上一个参数只生效一次

  1. 考虑对象进入老年代的方式
  2. Jviualvm 查看内存情况
  3. top 命令查看cpu情况

3. Arthas

Arthas是Alibaba在2018年9月开源的Java诊断工具。支持JDK6+,采用命令行交互模式,可以方便的定位和诊断线上程序运行问题。
Arthas 官方文档
https://alibaba.github.io/arthas
Arthas下载

  1. github下载arthas
    wget https://alibaba.github.io/arthas/arthas‐boot.jar
  2. Gitee 下载
    wget https://arthas.gitee.io/arthas‐boot.jar
    Arthas命令
    dashboard
    在这里插入图片描述

thread 查看线程详细情况
thread id 查看线程堆栈
thread -b 查看死锁
jad + 类的全名 反编译

4. GC日志

  1. 导出GC日志
    -Xloggc:D:\gc-%t.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCCause -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M
  2. 垃圾分析器
    GCeasy (收费)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值