JVM知识全景图
2023-01-03 12:26:59 0 举报
JVM知识全景图结合尚硅谷,图灵学院以及网上笔记做的全网最全笔记 制作不易,无保留分享 还请付费克隆支持下 也有全套课程视频 需要联系分享
作者其他创作
大纲/内容
当前栈帧
多次部分回收老年代,为了低延迟
2.对象内存分配
缺点:回收成本较高(不受JVM内存回收管理,监控不到)
Shenandoah(并发)局部复制+全局整理
复制算法
常量池
重度竞争、耗时过长、自旋过多等
低使用内存和并行开销
单CPU,C模式
G1回收过程(优先回收价值最大的Region,可能浮动垃圾下次GC处理)
(无论何种语言,满足JVM规范即可运行)
验证文件格式/元数据/字节码/符号引用等(确保加载类的正确性)
GC标志
3.初始化默认值
启动类加载器Bootstrap ClassLoader(只加载java/x或sun包,jre/lib下核心类库,含子类扩展和应用)
执行<init>,对属性赋值 (显示/代码块/构造器赋值)
垃圾回收算法
子类构造方法
当出现大量线程(CPU核数一半+)同时竞争轻量级锁或一个线程CAS10次(默认是十次,可以通过-XX:PreBlockSpin调整)以上时轻量级锁发生膨胀,升级到重量级锁
分配担保:当发生GC时,一个S区空间无法储存eden区和另外一个S区存活对象时,这些对象直接被转移到老年代,这个过程就是空间分配担保。JDK8以后,在进行Minor GC前,如果老年代的连续空间大于新生代对象大小总和或历次晋升的平均大小,则进行Minor GC,否则进行Full GC。
是指没有空闲的内存,并且垃圾收集器也无法提供更多的内存原因:1.堆空间设置过小;2.创建了大量大对象且存在引用,长时间无法被收集器收集
JVM内存模型
一、调优指标:① 降低回收STW延迟 ②提升最大吞吐量 ③减少内存占用量二、调优原则: ① MinorGC回收原则: 每次MinorGC都要尽可能回收更多垃圾对象,以减少应用程序发生Full GC的频率。 ② GC内存最大化原则:处理吞吐量和低延迟问题时候,垃圾处理器能使用的内存越大,垃圾收集的效果越好,应用程序也会越来越流畅。 ③ GC调优3选2原则: 在性能属性里面,吞吐量、延迟度、内存占用率,我们只能选择其中两个进行调优,不可三者兼得。三、JVM调优的常用参数:(-X是jvm运行参数,:后+为使用,-为关闭) -Xms/-Xmx:堆最小/大空间(开发建议一样) -XX:+PrintGC:在控制台输出GC信息 (默认为物理内存大小/64和/4) -XX:+PrintTLAB:可以查看TLAB的使用情况 -Xss:虚拟机栈大小 -XX:+UseSpining:开启自旋锁 -XX:NewRatio: Old/New的比例 -XX:MaxGCPauseMillis:设置每次年轻代垃圾回收最长时间 -Xmn:年轻代大小,调整会影响老年代大小(一般不设置) -XX:EliminateAllocations:开启标量替换 -XX:SurvivorRatio:调整Survivor区和Eden区的大小比例 -XX:+DoEscapeAnalysis:开启逃逸分析 -XX:MetaspaceSize:元空间初始化大小,JVM-64bit默认20.75M +XX:+EliminateLocks:开启同步消除 -XX:MaxMetaspaceSize:元空间最大大小,逻辑限制为物理内存上限 -XX:+PrintEliminateAllocations:查看标量替换情况 -XX:PretenureSizeThreshold:大对象直接进入老年代的阈值(字节为单位) -server:代表以server模式运行虚拟机 -XX:MaxTenuringThreshold:进入老年代的分代年龄阈值 -client:代表以client模式运行虚拟机 -XX:-XX:TargetSurvivorRatio:动态年龄判断比例设置 -XX:ThreadStackSize:设置线程栈默认大小 -verbose:gc:输出JVM的gc情况 -XX:-UseTLAB:开启TLAB线程本地空间 -XX:+PrintGCDetails:输出GC详细信息 -Xnoclassgc:关闭垃圾回收机制 -XX:+PrintGCTimeStamps:输出GC的时间戳(以基准时间的形式) -XX:TLABWasteTargetPercent:TLAB与Eden区的占比 -XX:+PrintTenuringDistribution:输出对象GC年龄信息 -XX:+CollectGen0First:FullGC是否先Young GC -XX:+PrintHeapAtGC:在进行GC的前后打印出堆的信息 XX:+UseAdaptiveSizePolicy:自适应Survivor和Eden大小 -Xloggc:路径:日志文件的输出路径 -XX:GCTimeRatio:设置GC时间占程序运行时间的百分比 -XX:+UseG1GC:用G1垃圾收集器(其他垃圾收集器也是一样,更换名字即可) -XX:NewSize:设置年轻代的初始大小 -XX:+PrintCommandLineFlags -version:输出默认的垃圾回收器 -XX:PermSize:设置年老代的初始大小
Survivor1 1/10(谁空谁To)
断开
热点探测
基本数据类型变量a
xx.Class数据结构/字段/方法信息)
Major GC速度一般会比Minor GC慢10倍以上,STW时间更长如果Major GC后,内存还不足,就报OOM
虚拟机栈(Stack)
是指一个对象到GCroots没有引用链
堆内存太小
类加载子系统
偏向锁位
卡表维护卡页的地址
YGC后存活+1
效率最低
空闲列表:与指针碰撞一样,空闲列表同样是Java在为新对象分配堆内存时的一种内存分配方式,一般适用于CMS等一些会产生内存碎片、堆内存不完整的垃圾收集器。分配过程:堆中的已用内存和空闲内存相互交错,JVM通过维护一张内存列表记录可用的空闲内存块信息,当创建新对象需要分配内存时,从列表中找到一个足够大的内存块分配给对象实例,并同步更新列表上的记录,当GC收集器发生GC时,也会将已回收的内存更新到内存列表。
引用多回收难
2bit
4bit
Parellel Old(并行)整理
在堆中划出一块和对象大小的对应内存出来,具体的分配方式根据堆内存是否规整决定,而堆内存是否规整则由当前程序采用的GC机制决定。分配方式:①指针碰撞(堆规整) ②空闲列表(堆不规整) 分配过程可能出现并发线程不安全问题,解决方法:①采用CAS自旋锁 ②为每个线程分配一个TLAB
存活对象
线程私有(指令)区
运行时常量池×1
(更低延迟jdk11)
用于存放基本数据类型(8种),reference和returnAddress类型的变量(包括this,方法参数和局部变量)基本数据类型存储数值本身,引用数据类型存储指向堆内存的引用指针随着方法出栈后销毁 而销毁()最基本的单位是slot(long和double为64bit,占2槽,其余都是1槽)变量槽可重复利用(局部变量离开大括号销毁)
为类变量(静态变量)在方法区分配内存,并设置默认初始值(static final+基本类型/字符串常量显示赋值---通过字面量赋值,而非调用方法或构造器赋值)
31bit
锁标志位
符号引用:类和结构的完全全限定名属性名称和描述符方法名称和描述符
main()栈帧1
一个栈帧对应一个(普通/静态)方法,只有出栈与入栈先进后出(进分配/出销毁内存)
后备方案→
(并发)三色标记/颜色指针
会引发STW(stop the world)暂停其它用户的线程,等垃圾回收结束才恢复运行
1
A
否
对于 JVM 来说,程序就是存储在方法区的字节码指令,而 returnAddress 类型的值就是指向特定指令内存地址的指针。JVM支持多线程,每个线程有自己的程序计数器(pc register),而 pc 中的值就是当前指令所在的内存地址,即 returnAddress 类型的数据,当线程执行 native 方法时,pc 中的值为 undefined。
不管能不能分配下都是在Eden区进行分配,因为TLAB指的是Hostpot对于new对象的优化,因为会出现同时多个线程在创建(new)对象,那么假设如果两个线程都看上了同一块内存,就会在这里浪费很多争抢的时间,所以Hostpot为每一个线程都在eden区中分配了一块专享的空间供线程使用。
验证
(N)对象不会被其他线程引用2.进行标量替换(对象→变量)栈上分配
哈希值(有调用才显示)、GC分代年龄、锁状态/标志位.
每个对象都自带一个计数器,有对象引用自己时+1,取消引用时-1。当计数器为0时该对象标记为垃圾可以被回收font color=\"212121\
FGC
后台线程执行编译
卡表
TLAB:Thread Local Allocation Buffer,线程本地分配缓存在Eden区为每个线程开辟的一个很小的缓冲空间,能够避免多个线程同时创建对象时由于竞争同一块堆内存时产生线程安全问题,同时也能提高系统的吞吐量(若同时操作堆空间,则需加同步加锁,导致系统吞吐量下降)一旦对象在TLAB空间分配内存失败,JVM会尝试使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存
1 1
Java虚拟机栈管理Java方法的调用;而本地方法栈管理本地方法的调用
重量级锁
字面量:基本类型值字符串值final常量值
是
....(一些附加部分)
CMS(并发)三色+写屏障清除
1bit
局部变量表
本地内存
标记整理算法
TLAB1
有内存碎片
1.隔离加载器:在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境(类仲裁→类冲突)2.扩展加载源:比如从数据库、网络中加载3.防止源码泄漏:Java代码容易被编译和篡改,可以进行编译加密。那么类加载也需要自定义类加载器,还原加密的字节码
HashCode
系统类加载器**AppClassLoader(加载java.Class.Path下类包,即你自己写的类)
32bit/位虚拟机对象头信息
安全点:无论是在GC中还是线程安全中都会出现安全点(SafePoint)这个概念,当我们需要阻塞一个线程时都需要在安全点停止,简单说安全点就是指当线程运行到这类位置时,堆对象状态是确定一致的,当前线程停止后,JVM可以安全地进行操作,如GC、偏向锁撤销等。安全点的定义:①循环结束的末尾段 ②方法调用之后 ③抛出异常的位置 ④方法返回之前当JVM需要发生GC、偏向锁撤销等操作时如果让所有线程到达安全点阻塞?①主动式中断(JVM采用的中断方式):不中断线程,而是设置一个标志然后让每个线程执行时主动轮询这个标志,当一个线程到达安全点后,发现中断标志为true时就自己中断挂起 ②抢断式中断:先中断所有线程,如果发现线程未执行到安全点则恢复线程让其运行到安全点位置。安全区域(SafeRegion):当一个线程处于中断或者休眠状态时就不能响应JVM的中断请求走到安全点区域挂起了,所以出现了安全区域的概念。安全区域是指一个线程执行到一段代码时,该区域的代码不会改变堆内对象的引用,在这区域内JVM可以安全地进行操作。当线程进入到该区域时需要先标识自己进入了,这样GC线程则不会管这些已标识的线程,当线程要离开这个区域时需要先判断GC Roots是否完成,如果完成了则往下执行,如果没有则需要原地等待到GC线程发出安全离开信息为止。
大对像15(长期存活)超S一半
判断老年代最大可用连续空间是否大于新生代所有对象的总和
标记-清除算法
新生代分配
returnAddress类型变量
S
优化参数-XX:MaxGCPauseMillis:GC最大停顿时间(尽量,默认200ms)-XX:ConcGCThreads:设置并发标记的线程数-XX:OnOutOfMemoryError=/xxx/restart.sh(自动化运维)
1.强引用:GC不会回收(显示赋值null则回收)2.软引用:堆内存不足GC回收(可实现缓存)3.弱引用:GC则回收4.虚引用
GCRoot
1.该类所有实例被回收2.该类的类加载器被回收3.该类对应的Class对象没有被如何地方引用(例如栈上变量引用)
锁信息
低延迟9废14除
几乎所有对象优先分配Eden区
多CPU,S模式大内存
匿名偏向锁未开启
尝试栈上分配
逃逸分析:判断变量作用域是否存在于其他栈帧或者线程中。当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸,判断逃逸的方法被称为逃逸分析。逃逸的作用域:①栈帧逃逸:当前方法内定义了一个局部变量逃出了当前方法/栈帧。 ②线程逃逸:当前方法内定义了一个局部变量逃出了当前线程能够被其他线程访问。全局变量赋值逃逸:当前对象被赋值给类属性、静态属性参数赋值逃逸:当前对象被当作参数传递给另一个方法方法返回值逃逸:当前对象被当做返回值return
同步消除:通过逃逸分析对一个对象进行逃逸判断之后,如果该对象为线程级作用域不可逃逸时,则代表当前这个对象只会有一个线程可访问。如果当前这个对象是被作为同步锁对象的,那么JVM会在编译时消除加锁和解锁的代码。决定能否同步消除(满足一个即可):①当前对象被分配在栈上。②当前对象的无法逃出线程作用域。
卡页
偏向锁
字节码效验器
偏向锁已启动
STW
用于存储计算过程中的临时数据最基本的单位是栈深度(long和double为64bit,占2个栈深度)
静>父>子①处于并列优先级的按照Java程序编写时代码的先后顺序加载。②前面两步为静态过程,程序运行过程中只会执行一次,之后再次创建该类或该子类的对象时并不会再次执行。
标记-压缩(整理)算法:首先,通过根可达算法找出所有存活对象,并标记然后,将所有存活对象移动到内存的一端,按序存放,最后,对于所有未标记对象进行统一回收。优点:空间连续,无碎片;无需复制算法两倍的内存空间缺点:①效率最低,需标记,且移动对象的同时还需要修改引用的地址
1.是否逃逸分析
G1(并行+并发)三色+写屏障局部复制+全局整理
JDK7及后(全局唯一)
1. 通过一个类的全限定名获取此类的二进制字节流2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构3. 然后在堆中生成一个代表此类的Class 对象,作为数据访问入口
利用双亲委派机制选择合适类加载器进行加载
0 1
语义分析
是否满足old代分配条件
所谓写屏障,其实就是在赋值操作前后,加入一些处理的逻辑(类似 AOP 的方式)
三、对属性设置默认值,保证对象实例字段在不赋值时可以直接使用(不包括对象头,如果使用了TLAB,这一步会提前到内存分配阶段进行)。
类的主动使用(其余为被动使用)1.(new/反射/序列化)创建类的实例2.访问类或接口的静态变量或对其赋值3.调用类的静态方法4.初始化一个类的子类(会先初始化父类)5.被标明为启动类的类(含main方法)
子类变量/代码块
举例
状态0/1
父类变量/代码块
使用
磁盘载入内存
指针碰撞:指针碰撞是Java在为对象分配堆内存时的一种内存分配方式,一般适用于Serial和ParNew等不会产生内存碎片、堆内存完整的的垃圾收集器。分配过程:堆中已用分配内存和为分配的空闲内存分别会处于不同的一侧,通过一个指针指向分界点区分,当JVM要为一个新的对象分配内存时,只需把指针往空闲的一端移动与对象大小相等的距离即可。
new
方法返回地址
Serial(串行)根可达+复制
方法区 | 具体实现:永久代(Perm)和元空间(Meta,jdk8及以后在本地内存分配)
无内存碎片
ZGC(并发)颜色+读屏障局部复制+全局整理
1.自定义加载器重写了ClassLoader的loadClass()方法,将双亲委派机制覆盖了(建议重写findClass(),调用还是loadClass()=双亲+findClass)2.线程上下文加载器(中介)3.由于用户对程序动态性的追求导致,如代码热替换(模块热部署)等
(jkd9变化)URL→Secure→ClassLoader抽象类(自定义直接继承,系统和扩展间接继承)
标记完成后
-Xmn
用复制算法来回交换同一时间永远有一个为空
高吞吐量
对象年龄
old
对象真正存储的有效信息(如成员变量,包含其父类)规则:1.父类变量在子类之前;2.相同宽度字段分配一起
主要设置①对象运行时元数据,如hashcode、GC分代年龄、锁状态/标志位等;②类型指针,指向它对应的类元数据,JVM用这个确定其属于哪个类的实例。
复制算法:首先,通过根可达算法找出所有存活对象,然后,将所有的存活对象复制移动到另一块未使用的内存区域,最后,对于前面的这块使用内存区域中的对象进行统一回收。(将原有的内存区域一分为二,在同一时间内只会使用其中一块内存区域用来分配对象。)优点:无需标记速度快,空间连续无碎片缺点:空间开销大,需要两倍的内存空间不合适存活对象多的情况(应用场景:存活对象少,如s区)
判断老年代最大可用连续空间是否大于历次晋后进入老年代的对象的平均大小
JVM调优
D
运行时元数据(Mark Word)
线程共享,有线程安全问题;随JVM创建而创建; 有GC,OOM异常
jdk8默认
静态变量(实例引用)
-XX:MaxMetaspaceSize
轻量级锁
1.与Java环境外交互 (主要原因)2.与操作系统交互 (部分由C实现)3.Sun's Java (Sun解释器由C实现)目前使用少,除非与硬件打交道
混合回收((Mixed GC)
直接内存
方法/回边计数器+1/+n
无锁态
Unused
ParNew(并行)复制
注解抽象语法树
本地方法栈(Native Meathod Stack)
执行编译后的机器码
(元空间用的就是本地内存,即物理内存)
垃圾对象
程序计数器(PC寄存器)(Program Counter Register)
young
(低延迟jdk9及后默认)
空闲空间
1.老年代空间不足 (1)大对象直接进入老年代,而老年代的可用空间不足 (2)空间分配担保(MinorGC后进入老年代的数据的平均大小>大于老年代的可用内存)2.方法区空间不足3.显示调用System.gc()
链接
GC算法
javac 前端编译器
Parellel(并行)复制
JIT即时编译器字节码→JIT即时编译(代码体积增大)→执行→结果
方法执行
初始化
卸载
javap -v xxx.class或View/jclasslib插件进行解析
两个计数器相加是否超过阈值
线程共享(数据)区
MIGC时
垃圾
对象成员分配内存后初始化顺序
wait
栈上分配:一般而言Java对象创建出来都是会在堆上进行内存分配,但其实并不是所有的对象都会在堆中分配,有时候也能够在栈上进行分配。不过想要将对象在栈上进行分配,需要先开启标量替换以及逃逸分析。决定一个对象能否在栈上分配的因素:①对象能够通过标量替换分解成一个个标量。②对象在栈帧级作用域不可逃逸。如果一个对象能够满足如上两点则代表该对象可以在栈上进行分配。
当第二个线程尝试获取锁,但是发现第一个线程还在执行,那么会将对象回归无锁态并将markword中除开锁标志位的信息全部copy到自己的栈内存中锁信息LockRecord中,然后再尝试利用CAS将对象头内markword中的指针指向自己栈内锁信息,能够替换成功代表获取到锁,替换失败继续自旋(小细节:如果此时之前持有偏向锁的线程来获取锁,优先CAS)
对象存活判断算法
生成dump文件-XX:+HeapDump(OnOutOfMemoryError / BeforeFullGC)-XX:HeapDumpPath=d:\\xxx.hprof生成gc日志-Xloggc:d:/xxx.log
标记-清除算法:首先,通过根可达算法找出所有存活对象,并标记然后,对于所有未标记对象进行统一回收。优点:简单缺点:①大量内存碎片(需维护一个空闲列表)
(线程各独一份,和线程同生命周期)无需GC,栈有OOM和SOF异常
持有偏向锁线程的ID
引用类型
多CPU,S模式
抽象语法树
YGC
(字节码)执行引擎(解释器+JIT及时编译器+垃圾回收器)字节码指令→对应平台的机器指令(OS识别,如CPU识别计算)
class文件
栈帧2
词法分析
MiGC
三色标记算法:(用于并发标记)垃圾收集器从GCRoot开始,向下搜索每经过一个对象,标记为灰色(灰色表示还存在一些引用对象未被扫描)若灰色对象没有其他引用则标记为黑色(黑色表示访问过,且它的所有引用对象也被访问过,绝对存活)其余没经过的对象就是白色,可以当成垃圾回收掉
存放方法正常结束时的返回地址,即PC寄存器的值
不管你当前对象是处于何种锁状态,只要你调用了Object.wait()方法,那么该锁会直接膨胀为重量级锁,因为wait方法实则还是依赖于monitor实现,所以不管什么时候调用wait都会使得对象头中markword内出现指向monitor的指针,而markword的改变对于用户线程是不可逆的,一旦出现monitor指针,就无法回到之前的锁状态
拓展类加载器ExtClassLoader(加载jre/lib/ext类包)
在新生代达到一定条件的对象会进入年老代:1. s0与s1区域交换一次年龄+1 当对象年龄达到阈值15岁进入2. 大对象直接进入(可设置大小判断标准)3.s区相同年龄的对象大小总和超过s区空间一半,大于等于该年龄的所有对象直接进入
误标:用户线程并发标记下,会有误标问题,导致存活的对象被当成垃圾回收掉原因:扫描过程中插入了一条或多条从黑色对象到白色对象的新引用(不可达变可达-很糟糕),并且同时去掉了灰色对象到该白色对象的直接引用或间接引用(可达变不可达-浮动垃圾下次清理)解决:破坏两个条件中的一个条件即可CAS--增量更新-写后屏障:破坏第一个条件,也就是说,当黑色对象新插入了指向白色对象的引用时,则把这个新增引用关系记录下来(新增引用A→C),等到并发标记结束后,再根据记录重新扫描标记一遍(也即重新标记的过程)(CMS标记三个阶段:初始标记+并发标记+重新标记)G1--原始快照(STAB)-写前屏障:破坏第二个条件,也就是说,当我们要去删除一个引用关系时,则把这个删除引用记录下来(删除引用:B→C),等到并发标记结束后,再根据记录重新扫描标记一遍,并将重新扫描到的白色对象直接标记为黑色,让其存活,若最终不存活则为浮动垃圾,等下次GC回收
0 0
高级语言→字节码指令
非静态会先放this
对象头
打印内存或堆情况-XX:PrintGCDateSamps(时间戳不可单独使用+↓)-XX:+PrintGCDetails(+结束时内存情况)-XX:+PrintHeapAtGC(每次GC前后堆情况)
轻度竞争
当第一个线程试图获取锁资源时会先判断匿名偏向是否已开启,如果开启会先将markword内除开锁标志位的信息全部copy到自己的栈内存中锁信息LockRecord中并尝试利用自旋将自己的线程ID设置到markword中,以后每次该线程执行用改对象作为锁资源的代码无需加锁和放锁操作,只是在LR中加一个空的markword
O
父类静态变量/静态代码块
失败
堆空间(Heap)
\"x\"
指向监视器对象(Monitor)的指针
解释器解释
已用内存
Old/Full GC
在程序执行过程中能够随着程序的需要生成并执行新的代码及称为即时编译。在目前的商用虚拟机里面都实现了JIT即时编译这种技术。虽然Java虚拟机规范中没有明确指出必须要实现它,但是JIT编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。 在HotSpot中Java程序通常情况下都是通过解释器(Interpreter)一边解释一边执行的模式运行代码的,当虚拟机检测到某个代码块或者方法体执行非常频繁时,会将这些检测到的代码判定为热点代码。而如果这些热点代码都通过解释器的模式执行,那么每次执行都需要用到解释器解释,为了提升热点代码执行效率,在运行时虚拟机会将这些代码编译成与当前本地平台+硬件相关的机器码并进行各层面的优化,同时也会将方法的调用地址指向编译后的机器码并且也会将编译后的机器码保存到磁盘,而这个编译热点代码的编译器被成为JIT(即时编译)器。 PS:当热点代码被检测到时,不管是代码块还是方法体,虚拟机都是以方法为最小单位进行编译。 HotSpot中实现的两种JIT器:ClientCompiler(C1):局部优化,追求编译速度/ServerCompiler(C2):充分优化,追求编译质量。会根据JVM运行模式来决定选择谁。
空(null)
JDK早期,sync叫做重量级锁,因为锁资源必须通过kernel申请,系统调用而偏向锁和自旋锁都是用户空间完成
why自定义加载器
优点:用户T和收集器T可并发执行缺点:1.碎片,2.并发占用一部分线程导致程序慢,总吞吐量降低3.无法清除浮动垃圾(可达→不可达,需等下一环节回收)
字节码生成器
0
getClassLoader()
数组长度(数组独有)
new的实例xx对象/数组
reference对象引用(objClass/obj/Loadder引用变量)
对齐填充 (8整起占位符作用)
加载
B
编译器以方法为最小单元对于热点代码进行编译之后,原本的方法调用地址被替换为编译后的方法地址。
简单
栈上替换
24bit
成功
类型指针(指向类元信息)
被final修饰的是全局常量,编译期就已分配好
尝试TLAB分配
根可达算法
当前线程执行解释器代码
执行栈上替换
25bit
基于计数器实现的热点探测JVM给每个方法绑定了两个计数器:1.方法调用计数器:方法被调用执行一次时计数器+1。2.回边计数器:统计一个方法体内循环体循环的次数。
512B
为什么要调整StringTable?jdk7将StringTable放到了堆中,主要是为了避免永久代内存溢出,因为永久代空间默认比较小,并且回收效率很低,只有在FGC时才会触发。而开发中字符串是引用类型会创建对象,并且会被大量创建,回收效率低则会导致永久代内存不足而溢出,故放在堆里能及时回收
Eden 8/10
JVM启动后前四秒 new 出来的对象并未启动匿名偏向锁
C
年龄计数器
dirty?
引用关系
(Eden满时触发)
23bit
执行类构造器<clinit>()--主动使用类对类变量显示赋值并执行静态代码块赋值(若有父类,优先初始化父类)
字节码文件(.class )
new
一次编译,处处运行安装不同平台虚拟机即可运行
-XX:+UserxxxGC 使用xxxGC收集器
提交OSR编译请求
每个1%
类加载器
问题:为什么使用PC寄存器记录当前线程的执行地址?答:因为CPU需要不停的切换线程,如果切换回来,需要知道从哪开始继续执行,所以需要寄存器存储指向下一条指令的地址
空闲列表分配
普通对象
语法分析
空闲内存
-XX:+PrintCommandLineFlags:查看命令行参数(包括使用的垃圾收集器,设置一个默认激活另一个)
通过GCRoots对象(**不在堆里且指向堆对象/跨代引用-引它不他引)(栈中变量/方法区中常量/静态变量/被同步锁sync持有的对象/JVM内部引用)作为起始点通过BFS或DFS向下搜索,搜索走过的路径被称做引用链,当一个对象到GCRoots没有引用链时则判断为垃圾可以被回收存在少见的跨代引用问题(而遍历老年代查看是否引用效率低)收集器通过记忆集(记录非收收集区到收集区的指针结合)的实现-卡表判断
Eden区将满
子类静态变量/静态代码块
操作数栈
MyClassLoader对象
TLAB2
当第二个线程试图获取锁资源时发现偏向锁已开启,那么会进行上一步操作,尝试将自己的线程ID设置到markword中,但是之前第一个线程已经赋值,对象头内存储的是第一个线程的ID,那么此时会判断是否是安全点(第一个线程是否已执行完毕),如果第一个线程已经执行完毕,那么会发生偏向锁撤销(将锁对象清除锁记录并回归无锁态),然后第二个线程重新cas自旋将自己的线程ID设置到markword中,重新偏向
Synchronized锁膨胀/升级过程
5.赋值
栈帧3
如果第一个线程试图获取锁时判断匿名偏向发现偏向锁未开启时会直接膨胀为轻量级锁,先将markword中除开锁标志位的信息全部copy到自己的栈内存中锁信息LockRecord中,然后再尝试利用CAS将对象头内markword中的指针指向自己栈内锁信息,能够替换成功代表获取到锁,替换失败继续自旋
Java对象创建过程
指针压缩:在64bit的虚拟机中为了提升内存的利用率,所以出现了指针压缩这一技术,指针压缩的技术会将Java程序中的所有引用指针(类型指针、堆引用指针、栈帧内变量引用指针等)都会压缩一半,而在Java中一个指针的大小是占一个字宽单位的,在64bit的虚拟机中一个字宽的大小为64bit,所以也就意味着在64位的虚拟机中,指针会从原本的64bit压缩为32bit的大小,而指针压缩这一技术在JDK1.7之后是默认开启的。指针压缩失效:指针压缩带来的好处是无可厚非,几乎能够为Java程序节省很大的内存空间,一般而言,如果不开启压缩的情况下对象内存需要14GB,在开启指针压缩之后几乎能够在10GB内存内分配下这些对象。但是压缩技术带来好处的同时,也存在非常大的弊端,因为指针通过压缩技术后被压缩到32bit,而32bit的指针最大寻址为32GB,也就代表着如果你的堆内存为32G时出现了OOM问题,你此时将内存扩充到48GB时仍有可能会出现OOM,因为内存超出32GB后,32bit的指针无法寻址,所有压缩的指针将会失效,发生指针膨胀。
E
指向栈中锁记录(Lock Record)的指针
........
常量池作用1.可以找到要执行的字面量、类、字段、方法等信息,2.将一些常量进行复用,避免频繁的创建和销毁对象,实现数据共享(程序加载会有很多重复常量,所以只需在常量池保存一份,当需要时将符号引用替换为直接引用)
Survivor0 1/10(From)
类加载过程/类的生命周期(静态变量)
老年代(Old) 2/3
JVM启动后后四秒 new 出来的对象默认启动匿名偏向锁
也称指向常量池中方法的引用编译期确定为静态-早期绑定-非虚方法;运行时确定为动态-晚期绑定-虚方法(由于OOP思想中存在多态的概念使得编译器在编译源代码时无法确定对象类型,只有在运行时才能确定对象)
父类构造方法
解释器字节码→解释器执行→结果
轻量级锁(CAS自旋锁)
(Stop-The-World),在GC发生时,所有用户线程都需要挂起停止,对于用户而言就是整个程序卡住不能动了,直到等到GC完成之后用户线程才能重新恢复执行。原因:确保数据一致性并行会STW,并发不会(GC发生时,GC线程和用户线程操作的是同一块内存区域,如果用户线程不停止,GC线程前脚刚标记完一块区域,用户线程就在这块区域创建了一个对象,当回收的时候,可能这个对象还是存活的,那么就会导致该对象被清除了,对于业务来说会出现很大影响,存在线程安全问题。)
Epoch
准备
(Y)堆中分配
(动态性:运行时还可新增常量)
短H
产生浮动垃圾,并发失败则启动serial Old,停顿时间更长
并发可能会数据不一致1.可达<-->不可达2.标记整理,移动对象,故不用
版本1:只回收老年代版本2:回收整堆+方法区(也可称Full GC)
是否编译?
代表xx类的class对象
垃圾回收器
是否配置担保参数
内存溢出
解释执行
空间开销大
.java 源文件(类或接口)
-XX:+DoEscaoeAnalysis
匿名偏向
方法返回
类型(元)信息×n
内存分配策略(对象提升规则)
若使用ParallelGC-XX:-UseAdaptiveSizePolicy 关闭自适应大小-XX:SurvivorRotio=8显示开启比例则8:1:1
类编译期间生成的字面量+符号引用类加载期间符号引用替换后的直接引用(静态链接)程序运行期间s.intern()生成的常量(jdk7之前)程序运行期间由于动态链接,将符号引用替换后的直接引用
指针碰撞分配
类加载器的3个特征:1.双亲委派机制(推荐而非必须):当需要加载某个Class类时,虚拟机会使用双亲委派机制。原理:如果一个类加载器收到了子类加载的请求,它不会自己去加载,而是把这个请求向上委托给父类的加载器去加载(直至最终请求到达顶层的启动类加载器);如果父类加载器可以完成类加载任务,就成功返回,若父类加载器加载失败,才会交由子类加载器去加载,这就是双亲委派模式。好处:1.避免类的重复加载,确保一个类的全局唯一性;2.保护程序安全,防止java核心源代码被随意篡改(外部想要篡改JDK的系统类时,因为父类加载器已经加载过JDK的系统类,子类加载器不会再加载,从而能够在一定程度上防止了危险代码的植入,这样就保证对java核心源代码的保护)缺点:顶层的类加载器无法访问底层的类加载器所加载的类2.可见性:子类加载器可以访问分类加载器加载的类型,但是反过来不允许3.单一性:由于父类加载器的类型对于子类加载器是可见的,所以父类加载器中加载过的类型,就不会在子类加载器中重复加载(但是注意,同一类型加载器仍然可以被加载多次,因为互相并不可见)
-XX:xxxThread= xxx线程数
除病虫标记-并发清理
本地方法接口←方法库由C或+实现,Native标识
cms_free
作用:存储指向下一条指令的地址(由执行引擎读取下一条指令)
字符串常量池(不变)字符串+对象引用
新生代(New) 1/3
破坏
1.静态集合类,如HashMap、LinkedList,它们生命周期与程序一致,则容器中引用的对象在程序结束之前不能被释放2.单例模式,同上3.各种连接,例如数据库连接,网络连接或IO连接,必须手动Close()
频繁收集年轻代较少收集老年代基本 不动元空间
一个进程 (1pid多个线程)→ 一个JVM实例 → 一个运行时数据区 → 一个方法区/堆空间
效率高
-Xss
分代模型
魔数,Class文件版本,常量池(字面量+符号引用),访问标志,类/分类/接口索引集合,字段/方法/属性表集合
1 0
可能在初始化后进行
4.设置对象头
实例数据(父类→子类数据)
JIT动态编译
个人深入并发编程系列文章:>>>戳我访问<<<
store存局部变量表push/load/add都是操作数栈return 栈帧结束
Full GC
是指指程序中某些堆内存由于某种原因忘记回收或无法回收,造成系统内存不断增加,直至OOM(java拥有垃圾回收机制,你尽管造,剩下就交给它来回收)
运行时元数据 mark word
句柄访问:如果是句柄引用reference则存储的是堆中句柄池内的句柄,直接指针:如果是直接引用reference则存储的是堆中对象的内存地址。
静态/私有/final/父类方法,实例构造器都是非虚方法(其他为虚方法,不确定是谁的方法)
将常量池(类/接口/字段/方法的)符号引用(#11)替换为直接引用(真实地址)
,C++编写,无父加载器
1.类加载检测
写屏障维护卡表状态
GC器 连线代表可搭配使用
动态链接
解析
直接引用:①直接指向内存地址的指针。②相对的偏移量(方法指针/指向实例变量) ③句柄池中间接指向内存地址的句柄符号引用:符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。在类加载时的解析阶段,会有一步操作就是将class文件中的符号引用替换为直接引用,简单来说就是:在编译的时候一个每个Java类都会被编译成一个class文件,但在编译的时候因为类还没有被装载进内存,所以虚拟机并不知道所引用类的内存地址,所以就用符号引用来代替,而这个解析阶段就是为了把这个符号引用转化成为真正的地址。
JVM特点1.一次编译,到处运行(字节码文件)2.自动内存管理与垃圾回收(相对C手动释放,减少内存泄露和溢出的风险)
堆内存到达阈值
obj@7fe2f3
Serial Old(串行)整理
-Xms初始-Xmx最大
JIT热点代码缓存(hotCodeCache)
自定义加载器(加载用户自定义路径下类包)
分区模型
新增
引用计数法
内存泄露
26bit
标量替换:使用标量替换聚合量(对象),对于一个对象进行分解,将其拆解成一个个小的标量的过程(需要先进行逃逸分析,不可逃逸的对象才能进行标量替换)。标量:不可分割的量,指基本数据类型和reference类型好处:①能够节省堆内存,因为进行标量替换之后的对象可以在栈上进行内存分配。②相对运行而言省去了去堆中查找对象引用的过程,速度会更快一些。③因为是分配在栈上,所以会随着方法结束和线程栈的弹出自动销毁,不需要GC的介入。
JIT(Just In Time Compiler)即时编译(字节码指令→机器指令)(函数执行时只执行编译后的机器码即可)
方法入口
问题:为什么Java中的long与double不是线程安全的?答:Java虚拟机规范定义的许多规则中的一条:所有对基本类型的操作,除了某些对long类型和double类型的操作之外,都是原子级的。 目前的JVM都是将32位作为原子操作,并非64位(两个slot)。当线程把主存中的 long/double类型的值读到线程内存中时,可能是两次32位值的写操作,显而易见,如果几个线程同时操作,那么就可能会出现高低2个32位值出错的情况发生。要在线程间共享long与double字段是,必须在synchronized中操作,或是声明为volatile。
类型:类/接口/枚举/注解
当遇到new指令时,JVM首先会进行类加载检测:①检测new指令的参数是否能在常量池中定位类的符号引用(#11)。②检测这个符号引用是否进行过加载,链接和初始化(即有无直接引用),没有则先对该类进行类加载。
64bit/位虚拟机对象头信息
运行时数据区
年老代分配
TLAB分配
getClass()
引用级别:强引用 → 软引用 → 弱引用 → 虚引用强引用:通常通过new指令创建的对象都属于强引用类型,堆中的对象与栈中变量存在直接引用,对于这类存在强引用的对象。周所周知,当堆内存不足时,GC机制会被强制触发,但是如果GC器发现堆内对象都存在强引用时,GC器不会强制回收,而是触发OOM。所以一般情况下在编码的时候,如果确定一个对象不再使用之后可以显式的将对象引用清空:obj=null,这样能够方便GC在查找垃圾时直接发现它。软引用:软引用是指使用java.lang.ref.SoftReference<Object>(obj)类型修饰的对象,当一个对象只存在软引用时,在堆内存不足时,该引用级别的对象将被GC机制回收。当然在发生GC时如果堆内存还充足,那么是不会回收该引用级别的对象(可以用来实现缓存)。弱引用:弱引用是指使用java.lang.ref.WeakReference<Object>(obj)类型修饰的对象,与软引用的区别在于:弱引用类型对象的生命周期更短,因为弱引用类型的对象只要被GC发现,不管当前的堆内存资源是否紧张,都会被GC机制回收。不过因为GC线程的优先级比用户线程更低,所以一般不会立马发现弱引用类型对象,因此一般弱引用类型的对象也会有一段不短的存活周期。虚引用:虚引用是指使用java.lang.ref.PhantomReference<Object>(obj)类型修饰的对象,不过在使用虚引用的时候是需要配合ReferenceQueue引用队列才能联合使用。与其他的几种引用类型不同的是虚引用不会决定GC机制对一个对象的回收权,如果一个对象仅仅存在虚引用,那么GC机制将会把他当成一个没有任何引用类型的对象,随时随刻可以回收它。不过它还有个额外的用途:跟踪垃圾回收过程,也正是由于虚引用可以跟踪对象的回收时间,所以也可以将一些资源释放操作放置在虚引用中执行和记录。
偏向锁未启动
Epsilon:无操作GC器,当你的程序不需要GC时可以选择使用该GC器
老年代并发标记过程(含YGC)
0 条评论
回复 删除
下一页