jvm学习笔记图
2022-05-05 17:16:17 1 举报
AI智能生成
作为java程序员,一定要搞懂jvm内存模型与调优
作者其他创作
大纲/内容
jvm内存模型
线程私有
程序计数器
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制
顺序执行
选择
循环
异常处理
在多线程的情况下,程序计数器用来记录当前线程执行的位置,当前线程被切回来时知道上次执行到哪里了
虚拟机栈
结构
局部变量表
操作数栈
动态链接
方法出口信息
作用
方法在栈中执行
本地方法栈
执行Native方法
线程共有
堆
结构
年轻代
Eden区
对象首先在这个区域创建
S0区
当Eden空间满了之后,会触发一个叫做Minor GC(就是一个发生在年轻代的GC)的操作,存活下来的对象移动到Survivor0区
S1区
S0区满后触发 Minor GC,就会将存活对象移动到S1区,此时还会把from和to两个指针交换,这样保证了一段时间内总有一个survivor区为空且to所指向的survivor区为空
老年代
经过多次的 Minor GC后仍然存活的对象(这里的存活判断是15次,对应到虚拟机参数为 -XX:MaxTenuringThreshold 。为什么是15,因为HotSpot会在对象头中的标记字段里记录年龄,分配到的空间仅有4位,所以最多只能记录到15)会移动到老年代
占满时就会触发我们最常听说的Full GC
老年代条件
-XX:TargetSurvivorRatio
加入某个年龄段后,总占用超过Survivor空间*TargetSurvivorRatio(默认值50%)的时候,从该年龄段开始及大于的年龄对象就要进入老年代,这时候无需等到MaxTenuringThreshold中要求的15
-XX:MaxTenuringThreshold
动态年龄的判断
Hotspot遍历所有的对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的50%时(默认值50%,可以通过-XX:TargetSurvivorRatio=percent来设置),取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阀值
参数设置
通常会将 -Xms 与 -Xmx两个参数配置成相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源
分配策略
对象优先在EDEN区分配
大对象直接进入老年代
如字符串、数组
长期存活的对象进入老年代
方法区
作用
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,也被成为永久代;类加载器将 .class 文件搬过来就是先丢到这一块上
执行
对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找
直接内存
对象创建的过程
1、类加载检查
虚拟机遇到一条new的指令时,首先去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个类是否被加载、解析和初始化过,如果没有就要先执行相应的加载过程
2、分配内存
描述
虚拟机为新生对象分配内存,在加载完成后就知道对象的大小,直接从java堆中分出一块相同的大小
策略
指针碰撞
原理
用过的内存移到一边,没用过的移到另一边,中间有个分界值指针,只需要向没用过的方向指针移动对象大小的内存即可
使用场合
堆内存规整的情况下(没有内存碎片)
GC回收器
Serial、ParNew等
空闲列表
原理
虚拟机维护一个列表,记录哪些内存块是可用的,在分配的时候,找一块足够大的内存划分给对象实例,最后更新表的记录
使用场合
堆内存不规整的情况下
GC回收器
CMS等
3、初始化零值
虚拟机将分配的内存空间初始化为零值
4、设置对象头
虚拟机要对对象进行必要的设置,例如:对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息
5、初始化
执行init方法
Init方法还没执行,所有的字段都还是为零
判断对象是否死亡
引用计数法
描述
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的
缺点
很难解决对象之间相互循环引用的问题
可达性分析算法
描述
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的
GC Roots 对象
虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)
方法区中静态变量所引用的对象
方法区中常量引用的对象
本地方法栈(即native修饰的方法)中JNI引用的对象
已启动的且未终止的Java线程
不可达对象就非死不可?
要真正宣告一个对象死亡,至少要经历两次标记过程
可达性分析法中不可达的对象第一次标记并且进行筛选,筛选的条件是此对象是否有必要执行finalize方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”
被判定为需要执行的对象将会放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收
对象间的引用分析
强引用
垃圾回收器绝不会回收它,当内存空间不足时,java虚拟机宁愿抛出 OutOfMemoryError异常终止程序
软引用
如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存
虚引用
与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收
虚引用必须和引用队列(ReferenceQueue)联合使用
弱引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
如何判断一个类是无用的类?
1、该类所有的实例已经被回收
2、加载该类的ClassLoader已经被回收
3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
jvm的垃圾回收算法
标记-清除算法
描述
首先标记出所有不需要回收的对象,在标记完以后统一回收掉所有没有被标记的对象
缺点
效率问题;空间问题(标记清除后会产生大量的碎片)
标记-复制算法
描述
将内存分为相同的两块,每次使用其中一块,当这一块使用完以后,将还存活的对象复制到另一块去,然后把使用空间一次清理掉
缺点
浪费内存空间
标记-整理算法
描述
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存
缺点
效率问题
jvm的垃圾回收器
新生代
Serial 收集器
回收算法
标记复制算法
优点
简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率
描述
使用一条垃圾收集线程去完成垃圾收集工作
目标
响应速度优先
ParNew 收集器
描述
ParNew 收集器其实就是 Serial 收集器的多线程版本
回收算法
标记复制算法
目标
响应速度优先
Parallel Scavenge 收集器
回收算法
标记复制算法
目标
吞吐量优先
老年代
CMS 收集器
回收算法
标记-清除算法
目标
响应速度优先
优点
并发收集、低停顿
缺点
对 CPU 资源敏感;无法处理浮动垃圾;它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生
收集过程
1、初始标记
暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快
2、并发标记
同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方
3、重新标记
重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
4、并发清除
开启用户线程,同时 GC 线程开始对未标记的区域做清扫
Parallel Old收集器
回收算法
标记-复制算法
目标
吞吐量优先
Serial Old 收集器
回收算法
标记-整理算法
目标
响应速度优先
新老年代
G1 收集器
回收算法
标记-整理+复制算法
目标
响应速度优先
jvm类的加载过程
1、加载
通过全类名获取定义此类的二进制字节流
将字节流所代表的静态存储结构转换为方法区的运行时数据结构
在内存中生成一个代表该类Class对象,作为方法区这些数据的访问入口
2、验证
文件格式验证:验证字节流是否符合Class文件格式的规范
元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求
字节码验证:通过数据流和控制流分析,确定程序语义是否合法的、符合逻辑的
符号引用验证:确保解析动作能正常执行
3、准备
为类变量分配内存并设置类变量初始值的阶段,不包括实例变量,实例变量在对象初始化时随着对象一块分配在java堆中。这里所设置的初始值"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等)
类变量:静态变量,被static关键字修饰的变量,只与类相关,因此被称为类变量
4、解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量
5、初始化
初始化阶段是执行初始化方法 <clinit> ()方法的过程,是类加载的最后一步,这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)
6、使用
7、卸载
卸载类即该类的 Class 对象被 GC;jdk 自带的 BootstrapClassLoader, ExtClassLoader, AppClassLoader 负责加载 jdk 提供的类,所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的
条件
该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象
该类没有在其他任何地方被引用
该类的类加载器的实例已被 GC
jvm的类加载器
jvm自带的三个类加载器
BootstrapClassLoader(启动类加载器)
最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类
ExtensionClassLoader(扩展类加载器)
主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包
AppClassLoader(应用程序类加载器)
面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类
双亲委派
描述
系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器
好处
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载
如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类
保证了 Java 的核心 API 不被篡改
如何破坏?
如果想打破双亲委派模型则自定义加载器,需要继承 ClassLoader,需要重写 loadClass() 方法
如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载
jvm的内存调优
堆内存调优
在OOM时Dump出堆:Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志路径
调整最大堆内存和最小堆内存:-Xmx –Xms
指定java堆最大值(默认值是物理内存的1/4(<1GB))和初始java堆最小值(默认值是物理内存的1/64(<1GB))
默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
通常会将 -Xms 与 -Xmx两个参数配置成相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源
调整新生代和老年代的比值:XX:NewRatio
新生代(eden+2*Survivor)和老年代(不包含永久区)的比值
例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代占整个堆的1/5。在Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置
调整Survivor区和Eden区的比值:-XX:SurvivorRatio
设置两个Survivor区和eden的比值
例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10
设置年轻代和老年代的大小
-XX:NewSize --- 设置年轻代大小
-XX:MaxNewSize --- 设置年轻代最大值
栈内存调优
调整每个线程栈空间的大小:-Xss
在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右
设置线程栈的大小:XXThreadStackSize
jvm其他参数调优
设置关闭手动GC:-XX:+DisableExplicitGC
设置垃圾最大年龄:-XX:MaxTenuringThreshold
设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代
对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。该参数只有在串行GC时才有效
加快编译速度:-XX:+AggressiveOpts
改善锁机制性能:-XX:+UseBiasedLocking
禁用垃圾回收:-Xnoclassgc
设置堆空间存活时间:-XX:SoftRefLRUPolicyMSPerMB
设置每兆堆空闲空间中SoftReference的存活时间,默认值是1s
jvm数据同步的八种操作
加载过程
lock(锁定):作用于主内存的变量,把一个变量标志为只能一个线程独占的状态
unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后可以被他线程锁定
read(读取):作用于主内存的变量,把一个变量从主内存传输到线程工作内存中,为load动作做准备
load(载入):作用于主内存的变量,将read操作从主内存中得到的变量放入工作内存的变量副本中
use(使用):作用于工作内存的变量,把工作内存的变量传递给执行引擎
assign(赋值):作用于工作内存的变量,将执行引擎收到的值赋值给工作内存的变量
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以遍随后的write的操作
write(写入):作用于主内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
规则
read与load、store与write成对出现
jvm常见线上问题
cpu占满分析
内存泄漏分析
死锁
线程频繁切换
0 条评论
下一页