JVM虚拟机
一、JVM组成部分:
1.程序计数器
- 作用,是记住下一条JVM指令的内存地址;1.多线程情况下,程序计数器用于记录当前线程执行的位置,从而线程切换回来的时候能够知道线程上次运行到哪儿了。2.字节码解释器通过改变程序计数器来依次读取指令,实现代码的流程控制。
- 特点
- 是线程私有的,
- 不会存在内存溢出的问题
2.虚拟机栈
-
栈 - 线程运行需要的内存空间;一个栈中具有多个栈帧
-
栈帧 - 每个方法运行时需要的内存;栈帧中又有(参数、局部变量、返回地址)。
-
每个线程只能有一个活动栈帧,对应着当前正在执行的方法
方法调用的数据需要通过栈进行传递;每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出.
生命周期:随着线程的创建而创建,随着线程的死亡而死亡
局部变量表:存放了编译时期可知的各种数据类型(boolean\byte\char\short\int\float\long\double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
操作数栈:主要作为方法调用中转站进行使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。
动态链接 主要服务一个方法需要调用其他方法的场景。在 Java 源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用(Symbilic Reference)保存在 Class 文件的常量池里。当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用。
问题辨析
- 垃圾回收是否设计栈内存?
栈就是方法间的调用;垃圾回收不需要涉及
- 栈内存分配越大越好吗?
栈默认大小为1M,线程运行需要的是栈空间,而物理机的内存大小是固定的,栈内存大,对应的栈数量就越少;
- 方法内的局部变量是不是安全的;
主要看方法中的局部变量是否是被共享的,每次调用方法都是创建一个新的栈,局部变量是线程私有的。
如果方法局部变量没有逃离方法的作用访问,它是线程安全的
如果如下图中在参数传递,和作为返回值有可能被其他线程拿到对应的引用地址,也就是如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全
2.1 栈内存溢出
- 方法自己调用自己(出现stack overFlow)
设置虚拟机内存属性
当内存过小,也会出现栈内存溢出;
- 出现对象循环引用的时候,也会栈溢出
StackOverFlowError
: 若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError
错误。
OutOfMemoryError
: 如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError
异常。
2.2 线程运行诊断
案列1: CPU运行过高
定位:
1.用top定位是哪个进程对cpu的占用过高
2.查看线程使用率:ps H -eo pid,tid,%cpu | grep 32655
3.jstack id进程
1. 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号
案例2:程序运行很长时间都没有返回结果
排查死锁问题
- jstack pid
2.3 基础故障虚拟机诊断工具
2.3.1 JPS虚拟机进程状况工具
- 简称JVM Process state Tool,可以列出正在运行的虚拟机进程,并显示虚拟机正在执行的主类名称以及这些进程的本地虚拟机唯一ID
2.3.2jmap:JAVA内存映象工具(JHSDB取代)
jmap命令用于生成堆转储快照,通常dump下来进行查看
2.3.3 jstack:Java堆栈跟踪工具
用于生成这一时刻的线程快照,**线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,**生成线程快照的 目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂 起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过jstack来查看各个线程的调用堆栈, 就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。
2.3.4jvisualvm(Java VisualVM)
一种图形化界面的工具,很强大,在IDEA中也有对应的插件,提供内存和CPU分析,堆转储分析,内存泄漏检测和垃圾收集等。
2.4 可视化故障处理工具
JHSDB提供了非常强大且灵活的命令和功能,本节的例子只是其中一个很小的应用,读者在实际 开发、学习时,可以用它来调试虚拟机进程或者dump出来的内存转储快照,以积累更多的实际经验。
3.本地方法栈
- 与虚拟机栈发挥的作用极为相似,区别是:虚拟机栈为虚拟机执行java方法(即字节码服务),而本地方法栈则为虚拟机使用到的Native方法服务。
4.堆
定义:通过new关键字,创建对象都会使用存放在堆内存中
Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法**,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间**。进一步划分的目的是更好地回收内存,或者更快地分配内存
在JDK7及JDK7版本之前,堆通常被分为以下三部分:
- 新生代内存(Young Generation)
- 老生代(Old Generation)
- 永久代 (Permanent Generation)
下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3FJwo6E3-1676015188420)(E:\img\image-20221227203532711.png)]
JDK8版本后PermGen(永久)已被Metaspace(元空间)取代,元空间使用的是直接内存
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。
4.1 堆内存的溢出
- 通过设置Xmx8m后
4.2 堆内存诊断工具
1.jps工具
查看当前系统中有哪些java进程
2.Jmap工具
查看堆内存占用情况
3.jconsole工具
图形界面,多功能检测工具,可以连续检测
调用jmap - heap 12113查看内存
- 使用一个更好的可视化jvisualvm ,来定位到底是哪个导致的堆溢出
5.方法区
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ADxk7jOE-1676015188429)(…/…/…/img/image-20221228093731148.png)]
5.1内存溢出
1.6JDK永久代溢出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MhXBYVS-1676015188430)(…/…/…/img/image-20221228095052118.png)]
1.8JDK元空间溢出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rhHiK6jS-1676015188431)(…/…/…/img/image-20221228094504519.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bscvsrph-1676015188432)(…/…/…/img/image-20221228094523402.png)]
5.2场景
- SPring
- Mybatis
两者之间都有一个Cglib动态代理,在运行期间生成的类还是容易出现这个内存溢出的
5.3方法区,常量池
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期间生成的各种字面量(Literal)和符号引用和常量池表(Constant Pool Table)
- 反编译
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m2uvjkZm-1676015188433)(…/…/…/img/image-20221228105332065.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0nlKWyQ3-1676015188434)(…/…/…/img/image-20221228103621987.png)]
5.4 运行时常量池
-
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的方法
-
运行时常量池:就是放在内存中的地址(常量池在类加载后存放到方法区的运行常量池中)
-
运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出
OutOfMemoryError
错误
5.5 StringTable
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Yjm1mvc-1676015188435)(…/…/…/img/image-20221228110954375.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1nEn9XMw-1676015188436)(…/…/…/img/image-20221228111509103.png)]
在常量池中寻找
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWodGh17-1676015188437)(…/…/…/img/image-20221228112046242.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRL4jTAo-1676015188438)(…/…/…/img/image-20221228114337453.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pINMKPgT-1676015188439)(…/…/…/img/image-20221228115524060.png)]
对于1.6 如果串池没有,会将对象拷贝一份放入串池,而1.8不会拷贝,如果没有就直接放入串池
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CXulWaaJ-1676015188440)(…/…/…/img/image-20221228153331519.png)]
x2发现串池中没有,拷贝一份到常量池,而x2就是堆中的newString(ab);x1此时引用的串池中拷贝的"ab"
6.StringTable的位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vmxn7R7w-1676015188441)(…/…/…/img/image-20221228153700240.png)]
- 为什么转到堆中?
- 主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存
- 模拟:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAhC7mFe-1676015188442)(…/…/…/img/image-20221228162312166.png)]
JDK1.8后,StringTable移动到heap space空间了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wznymOJA-1676015188443)(…/…/…/img/image-20221228162901973.png)]
5.7StringTable垃圾回收
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PWI5prRb-1676015188444)(…/…/…/img/image-20221228164205879.png)]
5.8 StringTable性能调优
- 1.设置虚拟机的堆内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-14qr4SZ9-1676015188445)(…/…/…/img/image-20221228164606775.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7rqGr3VU-1676015188446)(…/…/…/img/image-20221228164949862.png)]
如果字符串常量个数较多,可以把这个StringTableSize的值=桶个数调的比较大
- 为什么要用StringTable?
验证:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oEYYhPNX-1676015188447)(…/…/…/img/image-20221228170503110.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFvlqAkM-1676015188448)(…/…/…/img/image-20221228170429802.png)]
修改后:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-veRqLeLF-1676015188449)(…/…/…/img/image-20221228170528492.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ykgsk5Od-1676015188453)(…/…/…/img/image-20221228170643815.png)]
**结论:**如果字符串多且重复 ,可以考虑入池,减少性能的消耗,节约堆内存的使用
7.直接内存
**定义:**直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
JDK1.4 中新加入的 NIO(New Input/Output) 类,引入了一种基于*通道(Channel)与缓存区(Buffer)的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
- 常见于NIO操作,用于数据缓冲区
- 分配回收成本高,但读写性能高
- 不受JVM内存回收管理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t3HIG74x-1676015188453)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20221229094243817.png)]
7.1直接内存释放内存原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d46wV7qV-1676015188454)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20221229094746101.png)]
-
java的垃圾回收是自动的,但是直接内存的回收是需要我们手动释放,如上图的Unsafe.freeMemory();
分配和回收原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ni27NEhP-1676015188455)(…/…/…/img/image-20221229095617005.png)]
7.2 虚引用对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3tb5QVrI-1676015188455)(…/…/…/img/image-20221229095749798.png)]
如果JVM中设置这个参数后,会禁用掉该虚拟机显示的垃圾回收,因为system.gc()是Full gc影响性能,但是禁用后上述代码会影响直接内存的回收
测试后发现直接内存没有回收,byteBuffer要等到真正的垃圾回收之后才会被清理,直接内存才会被释放
怎么解决呢?
直接使用Unsafe对象的freeMemory
二、 垃圾回收机制
1.如何判断垃圾可以回收
1.1 引用计数法:当一个对象的引用数为0的时候,就可以被回收了,下图互相都有引用此时不能被回收。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cB3vijwe-1676015188456)(…/…/…/img/image-20221229101618095.png)]
1.2 可达性分析算法
如果一个对象么有被根对象直接或者间接的引用,那么就可以被回收
演示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7e0KVwde-1676015188456)(…/…/…/img/image-20221229110038590.png)]
抓取到heap文件后用MIT打开
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5CEdOc2y-1676015188457)(…/…/…/img/image-20221229110223018.png)]
当我们list==null的时候,就代表list这个对象引用已经被回收了
2.四种引用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PDmkT00H-1676015188458)(…/…/…/img/image-20221229110959063.png)]
4个绿色框框是指4个实实在在的对象实例
软引用:当没有强引用A2对象,同时A2软引用时发生GC回收并且内存不够的时候就可以回收
弱引用:当没有强引用对象的时候,同时A3弱引用发生垃圾回收的时候就可以被回收。
- 这两本身也就是一个对象;因此在软引用的对象回收后,他们就进入引用对列,通过遍历队列进行回收
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K24Artny-1676015188459)(…/…/…/img/image-20221229112014847.png)]
虚引用、终结器引用:必须配合引用队列进行使用
- 虚引用:直接内存不在jvm中,没有办法被垃圾回收
,因此虚引用关联byteBuffer,当ByteBuffer被回收时,虚引用入队列,此时队列有个RefrenceHandle线程来查看队列是否有虚引用了,有的话调用虚引用中的clean方法找到直接内存地址在调用Unsafe的freeMemory对直接内存进行回收。
- 终结器引用
执行A4对象的回收时,会将终结器引用放入引用队列中,然后调用一个优先级很低的finalizeHanlde线程来遍历队列,发现终结器引用后,根据它找到我们的A4这个fianlize垃圾回收对象,调用fianlize()方法进行回收。下一次进行垃圾回收时就可以真正的进行回收掉。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q2orgKdT-1676015188460)(…/…/…/img/image-20221229113538363.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9LVTYmH-1676015188460)(…/…/…/img/image-20221229142607568.png)]
- 软引用的演示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iDcX9Wpt-1676015188461)(…/…/…/img/image-20221229143049632.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zSzQx9UH-1676015188461)(…/…/…/img/image-20221229143429274.png)]
此时发现list中还有多个null,怎么将软引用给回收呢?使用RefrenceQueue
/**
* @Author: sakura
* @Date: 2022/12/29 14:41
* @Description: 测试我们的软引用
* @Version 1.0
*/
public class Jvm_demo6 {
private static final int _4MB = 1024*1024*4;
public static void main(String[] args) {
List<SoftReference<byte[]>>list = new ArrayList<>();
//引用队列
ReferenceQueue<byte[]>queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
//软引用将引用队列关联起来
SoftReference<byte[]>ref = new SoftReference<>(new byte[_4MB],queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
//回收软引用
Reference<? extends byte[]> poll = queue.poll();
while(poll!=null){
list.remove(poll);
poll = queue.poll();
}
System.out.println("=============");
for (SoftReference<byte[]> softReference : list) {
System.out.println(softReference.get());
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uo4PEHHR-1676015188462)(…/…/…/img/image-20221229150721113.png)]
- 弱引用的演示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OzgnyLMZ-1676015188463)(…/…/…/img/image-20221229151717135.png)]
3.垃圾回收算法
标记清除
该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPTYUt1W-1676015188463)(…/…/…/img/image-20221229152008786.png)]
- 垃圾回收速度快,缺点时容易产生内存碎片(由于内存不连续,当一个大的对象加进来时,不能充分利用**);同时效率也不高**
标记整理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HzGpN1HF-1676015188464)(…/…/…/img/image-20221229152314088.png)]
优点就是速度快、缺点就是干的活多
根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
标记-复制
优点是没有内存碎片,缺点是需要占用双倍内存空间
为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clDjgFLf-1676015188465)(…/…/…/img/image-20221229154029172.png)]
分代回收
老年代存放的是我们常用的对象,而那些用完就可以丢弃的存放在新年代,根据对象生命周期的不同特点,进行不同的垃圾回收策略。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1iRGWODC-1676015188465)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20221230101112586.png)]
新生代这边称为Minor GC 也就是说,伊甸园这边经过垃圾回收了后,没有被回收的会加入到幸村区To中,然后生命值+1,然后From和To交换位置。
幸存区有一个阈值,当计数达到15之后就可以晋升到老年代;
**当新生代老年代都挺多的时候会怎样?**当老年代空间不足,先触发minor GC,如果之后空间还是不足,会触发一次FullGC,STW时间更长,进行从新生代到老年代都会回收掉。
3.1相关VM参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZy2mrLG-1676015188466)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20221230103849355.png)]
当新生代不够时,会直接晋升到老年代;如果都不够了,抛出内存溢出
- 注意
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SUIdk28P-1676015188467)(…/…/…/img/image-20221230113316502.png)]
一个线程内的outofmemory不会导致JAVA线程结束
4.垃圾回收器(需要借助pdf)
1.串行
- 单线程
- 堆内存较小,适合个人电脑
2.吞吐量优先
- 多线程
- 堆内存大,多核CPU
- 让单位时间内,STW的时间最短
3.响应时间优先
- 多线程
- 堆内存较大,多核CPU
- 尽可能让STW的单次时间最短
4.1串行垃圾回收器
-XX:+UseSerialGC = Serial + SerialOld
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJPVNLG6-1676015188468)(…/…/…/img/image-20221230114814290.png)]
**工作流程:**由于串行是单线程,因此触发垃圾回收线程后,其他线程都要阻塞,等待完成后在开始运行,serial新生代和serialOld一样。
新生代采用标记-复制算法,老年代采用标记整理算法
4.2(吞吐量优先,暂停每片的时间短)
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择
新生代采用标记-复制参数,老年代采用标记-整理算法。
JDK1.8默认使用的是Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC参数,则默认指定了-XX :-UserParallelOldGC
4.3响应时间优先
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6zBhY3r0-1676015188469)(…/…/…/img/image-20221231154641217.png)]
-
当重新标记这块可能出现的是,新生代引用了老年代的对象,对性能影响大,新生态本身是需要清除的,再去查找老年代下次还是要被删除,所以CMSScan…是这个开关
-
如果并发失败了(因为其新生代老年代碎片过多,预留的空间就不足够了),那么CMS就退化到serialOld串行执行老年代的垃圾回收期
CMS收集器
CMS(concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
CMS收集器也是一种实现了并发的收集器,让垃圾回收线程与用户线程同时工作,从Mark_Sweep可以看出是一种:标记-清除算法的实现,整体过程分为4个步骤:
- 初始标记:暂停所有其他线程,并记录直接与跟对象相连的对象
- 并发标记:同时开启GC和用户线程,用一个闭包结构记录可达性对象,当这个阶段结束时不能保证可达性对象都被记录了**,因为并发过程中用户对象有可能不断的更新引用域,所以这个算法会跟踪这些发生引用更新的地方**
- 重新标记:重新标记就是为了修正那些在并发标记阶段由于用户程序继续运行而导致标记会产生变动的那一部分对象的标记记录,这个阶段的停顿时间可能要比初始标记的时间就,但比并发标记阶段时间段。
- 并发清除:开启用户线程,同时GC开始对未标记的区域做清扫
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生
G1(面试常问)
G1是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及超大堆内存的,同时注重吞吐量和低延迟性能特征。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XC5GxYTL-1676015188469)(…/…/…/img/image-20221231155800615.png)]
是JDK1.7中HotSpot虚拟机的一个重要进化特征,具备以下特点:
- 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行GC操作,G1收集器任然可以通过并发的方式让java程序继续执行。
- 分代收集:???
- 空间整合:从整体上看是标记-整理算法,从局部上看是标记-复制算法。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
G1收集器的运作大致分为以下几个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)
著作权归所有 原文链接:https://javaguide.cn/java/jvm/jvm-garbage-collection.html
- 垃圾收集阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03d317r4-1676015188471)(…/…/…/img/image-20221231161014883.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KvWBH1tB-1676015188472)(…/…/…/img/image-20221231161815030.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czwgo2BM-1676015188473)(…/…/…/img/image-20221231162827753.png)]
有先收集那些垃圾比较多的区,达到暂停时间短的目标;对于老年代来说,不会拷贝所有区域
- G1回收垃圾速度跟不上产生垃圾速度就开始FULL GC;
- CMS只有并发失败的时候,才会full gc
Young Collection跨代引用
新生代回收的跨代引用:如果老年代引用了新生代,则将该卡置为脏卡;新生代有一个Remember Set记录有哪些从外部哪个脏卡对其的引用,然后在新生代垃圾回收时,通过这个rememberset找到对应的脏卡,减少遍历GC root的时间。remeberset找到脏卡-脏卡区找到对应的gc root
- Remark
JDK8字符串去重
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jeUHxvb-1676015188473)(…/…/…/img/image-20221231214323903.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8lNYwYE-1676015188474)(…/…/…/img/image-20221231214520378.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iGt9cmRJ-1676015188475)(…/…/…/img/image-20221231214704923.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3T4j7GJH-1676015188477)(…/…/…/img/image-20221231214716307.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1gpdwGX-1676015188478)(…/…/…/img/image-20221231215127449.png)]
怎样减少FullGC?
提前开始垃圾回收,让并发标记,混合收集提前开始,在JDK9之前设置这个参数:-XX:InitiatingHeapOccupancyPercent
垃圾回收调优
5.1调优领域
- 内存
- 锁竞争
- cpu占用
- io
5.2确定目标
根据低延迟还是高吞吐量,选择合适的回收器,CMS,G1,ZGC,ParallelGC
5.3 最快的GC是不发生GC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcshbqxx-1676015188480)(…/…/…/img/image-20230101115222601.png)]
5.4 新生代调优
在排查完自己的一些代码问题后,在开始内存调优,先从新生代开始
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sR5KNlNq-1676015188481)(…/…/…/img/image-20230101115759858.png)]
- 内存方面的调优
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VkeiUDNU-1676015188483)(…/…/…/img/image-20230101170232521.png)]
官方建议新生代的内存为百分之25-50%之间是最好的;
新生代内存挑的太大会导致后面垃圾回收后时间过长、因此导致最后吞吐量下降
内存划分:并发量*请求响应内存
幸存区大到能保留(当前活跃对象+需要晋升对象)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VT1Z79j8-1676015188484)(…/…/…/img/image-20230101173629234.png)]
- 如果长时间存活在新生代的对象应该早些晋升到老年代,因为新生代是在复制,从幸存区FROM到幸存区TO,所以如果有大量长时间存活的没有及时晋升那么就会复制来复制去,对性能是一个负担
5.5 老年代调优
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JuUxDknS-1676015188486)(…/…/…/img/image-20230101174614825.png)]
5.6内存调优案例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nIBwCeWA-1676015188488)(…/…/…/img/image-20230101175205312.png)]
如果幸存区内存紧张就会触发晋升阈值的下降
案例1解决方法:先试着增大新生代内存,增加了幸存区的空间以及晋升的阈值,让对象更多留在新生代,老年代就不会这么频繁。
案例2:首先可以减少新生代的内存大小,在对新生代的垃圾进行一次清理:-xx:+CMSScavengeBeforeRemark
案例3:由于部署的版本jdk是1.7,1.7采用的是永久代,1.8是元空间,因此当永久代内存不足的时候也会导致fullGC,元空间采用的操作系统,如果永久代内存不足,会导致fullGC
jvm调优实战
6.1 大内存硬件上的程序部署策略
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xWQlWtJg-1676015188490)(…/…/…/img/image-20230116200741977.png)]
- 案列问题:主要问题是过大的堆内存进行回收时带来的长时间停顿
- 分析
1.目前单体应用在较大内存的硬件主要部署有两种:通过单独的java虚拟机实例来管理大量的Java堆内存,同时使用若干个Java虚拟机,建立逻辑集群来利用硬件资源。
大量使用本地缓存(如大量使用HashMap作为K/V缓存)的应用,在逻辑集群中会造成较大的内 存浪费,因为每个逻辑节点上都有一份缓存,这时候可以考虑把本地缓存改为集中式缓存。 介绍完这两种部署方式,重新回到这个案例之中,最后的部署方案并没有选择升级JDK版本,而 是调整为建立5个32位JDK的逻辑集群,每个进程按2GB内存计算(其中堆固定为1.5GB),占用了 10GB内存。另外建立一个Apache服务作为前端均衡代理作为访问门户。考虑到用户对响应速度比较关 心,并且文档服务的主要压力集中在磁盘和内存访问,处理器资源敏感度较低,因此改为CMS收集器 进行垃圾回收。部署方式调整后,服务再没有出现长时间停顿,速度比起硬件升级前有较大提升。
6.2集群间同步导致的内存溢出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PMjQ1Q4O-1676015188491)(…/…/…/img/image-20230116203635972.png)]
堆外内存导致的溢出错误
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4EagDtRA-1676015188492)(…/…/…/img/image-20230116204203117.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0X4baIX-1676015188494)(…/…/…/img/image-20230116204437810.png)]
调优日志的打印
以通过以下几个参数要求虚拟机生成GC日志**:-XX:+PrintGCTimeStamps(打印GC停顿时 间)、-XX:+PrintGCDetails(打印GC详细信息)、-verbose:gc(打印GC信息,输出内容已被前一 个参数包括,可以不写)、-Xloggc:gc.log。**
字节码与Class文件
一、魔数与Class文件的版本
魔数:每个Class文件的头4个字节被称为魔数,它的唯一作用就是确定这是否是一个能被虚拟机接受的Class文件。
第四个字节存储的是Class文件版本号:第5和第6个字节的是次版本号,第7和第8个字节是主版本号,JDK支持向下兼容,不支持低版本执行超过其版本的class文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QwoVrPq-1676015188495)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230117142705114.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-si08aIHQ-1676015188496)(…/…/…/img/image-20230104204959798.png)]
0-3个字节代表的是文件结构,4-7个字节代表的是类的版本00 34(52) JAVA8。
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
二、常量池
- 常量池
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BiaKzs8C-1676015188498)(…/…/…/img/image-20230104210843168.png)]
次版本后就是常量池,常量池相当于Class文件里的资源仓库,由于常量池中的常量(字面量和符号引用)的数量是不固定的,需要放置一项U2类型的数据,代表常量池容量计数值。
在Class文件格式规范制定之时,设计者将第0项常量 空出来是有特殊考虑的,这样做的目的在于,如果后面某些指向常量池的索引值的数据在特定情况下 需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。Class文件结构中只有 常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的 容量计数都与一般习惯相同,是从0开始。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ppVRLs0b-1676015188501)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230117143341365.png)]
- 等于对于**常量池是在类版本2个字节后,**看看是指向哪个常量池的常量,比如说这里是CONSTANT_Utf8_info,指向后面的02也就是第二个常量,01是代表其长度
三、访问标识与继承信息
- 常量池结束后,紧接着2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1oWLDo2e-1676015188502)(…/…/…/img/image-20230104212514448.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RgWy3PT-1676015188503)(…/…/…/img/image-20230117203915571.png)]
四、类索引、父类索引与接口索引集合
类索引、夫类索引和接口索引都顺序排列在访问标志之后,类索引和父类索引用两个u2类型的索引值表示,它们各自指向一个类型为Constant_Class_info类描述符常量 ,通过CONSTANT_Class_info类型的常量中的索引值可有找到定义在CONSTANT_utf8_info类型的常量中的全限定名字符串。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z2OM4dGy-1676015188504)(…/…/…/img/image-20230117204601738.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QiRXnJMF-1676015188505)(…/…/…/img/image-20230117204740902.png)]
五、字段表(field_info)集合
字段表用于描述接口和类中声明的变量。JAVA语言中的字段(filed)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段可以包括修饰符有字段的作用域(public、private、protected修饰符)、是实例变量还是静态类变量(static)、是否是可变类型(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、是否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rDxmc4cU-1676015188507)(…/…/…/img/image-20230117205705928.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1dUxCFb-1676015188508)(…/…/…/img/image-20230117211005173.png)]
在descrip_tor_index之后跟随着一个属性表集合,用于存储一些额外的信息
六、方法表集合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SoAtIUqJ-1676015188508)(…/…/…/img/image-20230117211835156.png)]
Class文件存储格式对方法的描述与对字段的描述采用了几乎完全一致的方式,因此方法表如同字段表一样,但是方法不能被volitale以及transient修饰,但是可以被synchronized.abstract修饰。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MwAY6IK4-1676015188509)(…/…/…/img/image-20230117212028188.png)]
七、属性表集合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7DOmO6O6-1676015188509)(…/…/…/img/image-20230117213205356.png)]
- Code属性
code是一个U4类型的长度值,理论可以达到2的32次方,但是Java 虚拟机规范明确限制了一个方法不允许超过65535条字节码指令,其次Code属性是Class文件中至关重要的一个属性,code是用来存储Java开源编译后生成的字节码文件,也可以说是存储字节码指令的一系列字节流。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awDRTCRy-1676015188510)(…/…/…/img/image-20230117213140967.png)]
- **max_stack代表操作数栈深度最大值,max_locals代表了局部变量所需的存储空间,**这里注意max_locals的单位是slot,变量槽是虚拟机为局部变量分配空间最小的单位,对于byte,char,cloat等占一个槽位,但是long,double这64为的需要两个槽位来进行存放。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xyRHhZhd-1676015188511)(…/…/…/img/image-20230104212844154.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QyAzG4Hz-1676015188512)(…/…/…/img/image-20230104213748525.png)]
LocalVariableTable属性
- 属性用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,
ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静待变量赋值,只有被static关键字修饰的变量才可以使用这项属性。对于非static类型的变量的赋值是在实例构造器()方法中进行的;而对于类变量,则有两种方式可以选择,在类构造器()方法中或者使用ConstantValue属性,如果同时是使用final和static来修饰一个变量,且该变量的类型是String或者基本类型就会生成ConstantValue属性来进行初始化,如果没有被final修饰,会选择在clint()方法中进行初始化。
StackMapTable属性
概念:类型检查验证器,一个方法的Code属性最多只能有一个StackMapTable属性,否则将抛出ClassFormatError异常。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8LuIjEte-1676015188512)(…/…/…/img/image-20230104214236147.png)]
八、字节码指令
Java虚拟机的指令由一个字节长度的、代表某种特定操作含义的数字构成,Java虚拟机面向的是操作数栈而不是面向寄存器结构,只有一个操作码,指令参数都存放在操作数栈中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmNhXJXo-1676015188513)(…/…/…/img/image-20230104220356479.png)]
加载和存储指令
- 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈。这类指令包括:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LXWiIDPx-1676015188513)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118101740759.png)]
- 其中iload_n这几组指令代表的是,操作数为n时的iload指令。
运算指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-od0a7Bvq-1676015188514)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118102227362.png)]
- 《JAVA虚拟机规范》虚拟机在处理浮点数的时候,不会抛出任何运行时异常;在处理整形数据的时候,只有除法指令中当出现除数为零时导致虚拟机抛出ArithmeticException异常。
类型转换指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jaeFaFww-1676015188515)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118102716281.png)]
对象创建与访问指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3BLA9oj-1676015188515)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118135350956.png)]
操作数栈管理指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UnD4wGzq-1676015188516)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118135944896.png)]
控制转移指令
控制转移指令可以让Java虚拟机由条件或无条件地从指定位置指令的下条指令继续执行程序,从概念上理解,可以认为控制指令就是从有条件或无条件的修改PC客户端的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W8vgSWxc-1676015188516)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118140557979.png)]
方法调用和返回指令
这里列举几条指令用于方法的调用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RxcevFm0-1676015188517)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118140758685.png)]
分析Class文件的字节码的工具
javap -verbose HelloWorld.class
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTmUUudr-1676015188517)(…/…/…/img/image-20230104221312536.png)]
- 面试题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HYkJaocL-1676015188518)(…/…/…/img/image-20230105145300497.png)]
赋值操作是操作数栈执行的,因此iload_x先将0入栈,然后局部变量表上进行自增后,赋值的时候操作数栈的0赋值到x上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UNoXTBDR-1676015188518)(…/…/…/img/image-20230105145704239.png)]
取出I的值是?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xVdSc7pM-1676015188519)(…/…/…/img/image-20230105145743784.png)]
结果是30;
PS F:\Javastudy\concurrent\case_java8\src\main\java\cn\itcast\tests> javac Jvm_demo7.java
PS F:\Javastudy\concurrent\case_java8\src\main\java\cn\itcast\tests> javap -c Jvm_demo7.class
Compiled from "Jvm_demo7.java"
public class cn.itcast.tests.Jvm_demo7 {
static int a;
public cn.itcast.tests.Jvm_demo7();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #13 // Field a:I
6: invokevirtual #19 // Method java/io/PrintStream.println:(I)V
9: return
static {};
Code:
0: bipush 10
2: putstatic #13 // Field a:I
5: bipush 20
7: putstatic #13 // Field a:I
10: bipush 30
12: putstatic #13 // Field a:I
15: return
}
- 判断初始方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7GQntMP6-1676015188519)(…/…/…/img/image-20230105152313525.png)]
- 多态执行原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hmnlUzA8-1676015188520)(…/…/…/img/image-20230105154208223.png)]
- 运行HSDB工具
java -cp sa-jdi.jar sun.jvm.hotspot.HSDB
进入到jdk的安装目录执行上述代码D
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UeuIUTdB-1676015188520)(…/…/…/img/image-20230105154507068.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2OyOTz3J-1676015188521)(…/…/…/img/image-20230105163051151.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yc4tcBC-1676015188521)(…/…/…/img/image-20230105163602208.png)]
class 对象和这个vtable相差的数是1B8
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-glpv9seE-1676015188522)(…/…/…/img/image-20230105164338507.png)]
异常处理
- try-catch
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWO8RJh3-1676015188524)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230106100621921.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YR20FUuJ-1676015188529)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230106100906658.png)]
对于值的复制和加载,我们要知道我们**返回的是操作数栈中的值,而iload_x是将值保存到本地变量表的变量槽中去。**后序的iload 是将局部变量表的加载到操作数栈
- 多个single_catch快的情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PY3C3kor-1676015188530)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230106101134409.png)]
首先都会检测2-5中的代码块,异常旧跳到各自的行号,slot2存在槽位复用,因为异常出现时,只能进入Exception table中一个分支,所以局部变量表slot2位置被共用。
- mutil_catch
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jH8rZFFA-1676015188531)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230106101605192.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXOWYNwS-1676015188531)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230106101724143.png)]
- finally
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w2fbMIfc-1676015188532)(…/…/…/img/image-20230106101904876.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJIFenfZ-1676015188533)(…/…/…/img/image-20230106101922436.png)]
finally会放在try和catch中,保证执行任意一个都会执行到finally;还有一种情况,由于不能捕获所有异常,所以异常表中有剩余的异常类型,字节码中就有一个aload3加slot3中的引用存入到槽位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRbTYvrL-1676015188534)(…/…/…/img/image-20230106102231714.png)]
可以看到finally会放到try,catch,以及剩余的异常分支中;
面试题-finally
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V4xlFQxW-1676015188535)(…/…/…/img/image-20230106102640454.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gJMQElfA-1676015188536)(…/…/…/img/image-20230106102737763.png)]
首先会将10push到操作数栈,然后将10从栈顶移除存到局部变量表的0号槽位
-
根之前finally相比,没有athrow后,这告诉我们,如果在finally中出现了return,会吞掉异常
-
面试题2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RB8mxAhf-1676015188537)(…/…/…/img/image-20230106104028719.png)]
将10放到操作数栈的栈顶,然后赋给i,然后将slot0加载到操作数栈里,然后在这里没有直接返回,又将10又暂存到1号槽位,
- Synchronized
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b81YpKjb-1676015188537)(…/…/…/img/image-20230106104809385.png)]
synchronized加在方法上不会在字节码上体现;
3.编译期处理(语法糖)
3.1默认构造器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0PAFu0L9-1676015188538)(…/…/…/img/image-20230106111115969.png)]
3.2 自动拆装箱
基本类型和包装类型的转变
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaOHqCAt-1676015188539)(…/…/…/img/image-20230106111157765.png)]
3.3 泛型集合取值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fToW8IO3-1676015188539)(…/…/…/img/image-20230106111448865.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXl0KtKW-1676015188540)(…/…/…/img/image-20230106111755150.png)]
3.4 可变参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SNSV0vXl-1676015188540)(…/…/…/img/image-20230106112608659.png)]
3.5foreach
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X5qY2zdw-1676015188541)(…/…/…/img/image-20230106113118731.png)]
3.6 switch字符串
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UAlvWtl-1676015188541)(…/…/…/img/image-20230106113408160.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vjkSYsAq-1676015188542)(…/…/…/img/image-20230106113524136.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jxVTfaRN-1676015188542)(…/…/…/img/image-20230106115053531.png)]
3.7 Switch枚举
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-731LeVTT-1676015188543)(…/…/…/img/image-20230106115343994.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sti8vnaf-1676015188543)(…/…/…/img/image-20230106115407589.png)]
3.8 枚举类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2zytQLE-1676015188544)(…/…/…/img/image-20230106115658515.png)]
3.9 try_with_resources
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPR2u8Sh-1676015188545)(…/…/…/img/image-20230106120147940.png)]
资源对象必须实现AutoCloseable接口,才可以不用写fianlly
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XaCn2JKy-1676015188545)(…/…/…/img/image-20230106120900459.png)]
3.10 方法重写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5IJoHO4q-1676015188546)(…/…/…/img/image-20230106121135478.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJcN9scx-1676015188546)(…/…/…/img/image-20230106121401725.png)]
3.11 匿名内部类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6MM1LkuM-1676015188547)(…/…/…/img/image-20230106122059458.png)]
这同时解释了为什么匿名内部类引用局部变量时,局部变量表必须是final的:因为在创建Candy11$1对象时,将x值赋给了val&x属性,所以x不应该再发生变化了,如果变化,那么valx属性是没法跟着变化的;
类加载-I
概述:描述类的数据如何从Class文件加载到内存,并对数据进行校验、准备解析和初始化最终形成可以被虚拟机直接使用的JAVA类型,这个过程称为虚拟机的类加载机制。
类加载时机
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期将如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mDLnHCAH-1676015188547)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118144127315.png)]
-
其中图中加载-验证-准备-初始化-卸载这5个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班的开始,而解析阶段则不一定,它在某些情况下是可以初始化。
-
有且只有6种情况下必须立即对类进行初始化操作
(1). 遇到new、getstatic、putstatic 、invokestatic这四条字节码指令时,如果类型没有进行初始化,则需要先触发其初始化阶段,具体的场景有:
- 使用new关键字实例化对象的时候
- 读取或设置一个类型的静态字段
- 调用一个类型的静态方法的时候
(2)使用lava.lang.reflect包的方法对类型进行反射调用的时候,如果;类型没有进行过初始化,则需要先触发其初始化。
(3).当初始化类的时候,父类还没有初始化,则需要先触发其父类的初始化
(4).当虚拟机启动时,用户需要指定一个执行的主类,虚拟机会先初始化这个主类
(5) 当使用JDK7新加入的动态语言支持,如果一个java.lang.invoke.MethoHandle实例后的解析结果为REF_getstatic四种类型的方法,这个方法的类没有进行初始化,需要进行初始化。
(6)当一个接口种定义了JDK8新加入的默认方法时,如果有这个接口的实现类发生了初始化,那么该接口要在之前进行初始化
-
这6种场景种的行为称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化。
接口的加载过程与类加载过程稍有不同,接口中是不能使用static{}
语句块的,但是编译器任然会为接口生成《clinit》类构造器,用于初始化接口中的成员变量,还有一个重要的区别就是接口不需要父类接口完成初始化
类加载的过程
主要是:加载、验证、准备、解析和初始化这5个阶段所执行的具体动作。
加载
加载阶段是整个类加载过程中的一个阶段,在加载阶段,Java虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。
没有指定二进制字节流必须从某个Class文件中获取也就根本没有指明要从哪里获取,如何获取。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vi0LJT4E-1676015188548)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118154025390.png)]
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《JAVA虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全
为什么需要验证阶段?
但前面也曾说过, **Class文件并不一定只能由Java源码编译而来,它可以使用包括靠键盘0和1直接在二进制编辑器中敲出 Class文件在内的任何途径产生。**上述Java代码无法做到的事情在字节码层面上都是可以实现的,至少 语义上是可以表达出来的。Java虚拟机如果不检查输入的字节流,对其完全信任的话,**很可能会因为 载入了有错误或有恶意企图的字节码流而导致整个系统受攻击甚至崩溃,**所以验证字节码是Java虚拟 机保护自身的一项必要措施
验证阶段大致会完成下面四个阶段的检验动作:文件格式验证,元数据验证,字节码验证,符号引用验证。、
准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段;这些变量所使用的内存都应当在方法区中进行分配,在JDK7之前,使用永久代来实现方法区,JDK8及之后,类变量会随着对象一起存放在Java堆中
关于准备阶段,还有两个重要点:首先此时进行内存分配的仅仅包括类变量,而不包括实例变量,实例变量将会在对象实例化随着对象一起分配到java堆中,其次初始值“通常情况来说是数据类型的零值”
public static int value = 123;
变量value在准备阶段后的初始值是0而不是123,因为这时尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法中的。
但如果是由ConstantValue在准备阶段变量值就会被初始化为ConstantValue属性所指定的初始值,假设上面类变量value的定义修改为:
public static final int value = 123;
解析
解析阶段是JAVA虚拟机将常量池内的符号引用替换为直接引用的过程,在Class文件中它以CONTANT_CLASS_info等类型的常量出现;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bVJ7GrBf-1676015188548)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118163350921.png)]
-
方法解析:首先需要解析出方法表的class_index中索引的方法所属的类或者接口的符号引用,解析成功后,再按下列步骤进行检索。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZTVZ4Eez-1676015188549)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118165210090.png)]
-
接口方法解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F7NCYfYU-1676015188549)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118165755488.png)]
初始化
直到初始化阶段,JAVA虚拟机才真正开始执行类中编写的Java程序代码,==初始化阶段就是执行类构造器()方法的过程。==其中《clint》是Javac编译器的自动生成物,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCqCbEnx-1676015188550)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230118170826288.png)]
static class Parent{
public static int A = 1;
static{
A = 2;
}
}
static class Sub extends Parent{
public static int B = A;
}
public static void main(String[]args){
System.out.println(Sub.B);//2
}
Java虚拟机会保证在子类方法执行之前,父类的方法先执行完毕,因此java虚拟机中第一个被执行的()方法的类型肯定是java.lang.Object。所以意味着父类中定义的静态语句块要优先于子类的变量赋值操作,因此字段B的值是2;
类加载器-II
**概念:**每一个类加载器,都拥有一个独立的类名称空间,**只有在这两个类是由同一个类加载器加载的前提下才有意义,**否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,这两个类就必定不相等
启动类加载器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZxjSV1A4-1676015188550)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230110155000848.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q7oSku30-1676015188551)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230110155544435.png)]
-
对于拓展类我们使用当下命令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TW7cdXiC-1676015188551)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230110155958911.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ANL660FC-1676015188552)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230110160116422.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8KB0D89-1676015188552)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230110160203428.png)]
双亲委派机制
站在虚拟机角度,只有两种不同的类加载器,一种是启动类加载器(Bootstrap ClassLoader),这个有C++语言实现,是虚拟机的一部分;另一种就是其他所有的类加载器,这些类加载器有Java语言实现,独立于虚拟机外部,并且全部继承自抽象类javalang.ClassLoader.
启动类加载器:**赋值加载存放在<JAVA_HOME>\lib目录,**或者被-XbootclassPath参数所指定的路径中存放的,而且是Java虚拟机能够识别的类库加载到虚拟机内存中。其中启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器处理,直接使用Null代替即可
拓展类加载器(Extension Class Loader): 这个类加载器,负责加载<JAVA_HOME>\lib\ext目录中的,或者被java…ext.dirs系统变量所指定的路径中的所有类库。JDK9之后,被模块化所取代了,由于拓展类加载器是由Java代码实现的,开发者可以直接在程序中使用拓展类加载器来加载Class文件。
应用程序类加载器(Application Class Loader):负责加载用户类路径(ClassPath)上所有的类库,开发者也可以直接使用,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vrrv1X8-1676015188553)(…/…/…/img/image-20230119105101210.png)]
图示展示的各类加载器之间的层次关系被称为类加载器的“双亲委派模型”,**除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。**而类加载器父子关系之间是使用组合的关系来复用加载器代码;
工作流程 :**如果一个类加载器收到了类加载,首先不会自己尝试取加载这个类,而是把类委派给父类加载器,**直至传到最顶层的启动类加载器,只有当父加载器自己无法完成这个请求的时候,子类加载器才会尝试自己完成加载。类似于从上在往下的递归,父没有后在找子类是否有
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OJQcBhPF-1676015188553)(…/…/…/img/image-20230119110310316.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dpb0g0zn-1676015188553)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230110160344711.png)]
- 这 段代码是说,先检查请求加载的类是否已经被加载了,如果没有则调用父加载器的loadClass方法。若父加载器为空,则默认使用启动类加载器作为父加载器,如果父类加载器失败,抛出ClassNotFoundException异常,在调用自己的findClass()方法进行尝试加载。
线程上下文
相当于是父类加载器去请求子类加载器完成类加载的行为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pBT05pJ-1676015188554)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230115091805335.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5dYFCm2u-1676015188554)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230115092201836.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qq4eahgB-1676015188555)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230115092535683.png)]
- 启动类程序调用DriverManage->在利用其应用类加载器调用上下文类加载器调用对应的Mysql驱动
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPXMRxoj-1676015188555)(…/…/…/img/image-20230119111420177.png)]
模块化下的类加载器
模块下的类加载器任然发现了一些应该被注意到的变动,主要包括以下几个方面:
拓展类加载器被平台类PlatForm Class Loader取代,整个JDK都基于模块化进行了构建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PjwTDgC8-1676015188556)(…/…/…/img/image-20230119114336705.png)]
自定义类加载器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KkGCPNiC-1676015188556)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230115093200789.png)]
javap MapMall.class
反编译
虚拟机字节码执行引擎
所有输入的字节码二进制流,处理过程是字节码解析执行的等效过程,输出的是执行过程
运行时栈帧结构:
- JAVA虚拟机以方法作为基本的执行单元,“栈帧”则是用于支持虚拟机进行方法调用和方法执行背后的数据结构。
- 、栈帧存储了局部变量表、操作数栈、动态链接和方法返回地址等信息。
局部变量表
- 局部变量表是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。在JAVA编译成class文件的时候,在方法Code属性的数据项中确定了该方法所需分配的局部变量表的最大容量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lr57QOGg-1676015188557)(…/…/…/img/image-20230128102446652.png)]
操作数栈
- 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。
- 操作数栈中的元素的数据类型必须与字节码指令的序列严格匹配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tlWcMNXK-1676015188557)(…/…/…/img/image-20230128102750008.png)]
动态链接
什么是动态链接?
Class文件中存在的符号引用,字节码中的方法进行调用指令的时候,以常量池里指向方法的符号引用作为参数,在每一次运行期间都被转化为直接引用,这部分称为动态链接
方法调用
唯一任务:确定是调用哪个方法,Class文件的编译过程中不包含传统程序语言编译的链接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局的入口地址。
解析
-
这个方法的调用版本在运行期间是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译那一刻就已经确定下来。这类方法称为解析
-
对于"编译器可知,运行期间不可变"这个要求的方法,主要有静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们不可能通过继承或别的方式重写出其他版本。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CKFGJcs0-1676015188558)(…/…/…/img/image-20230128105022165.png)]
1.分派
分派调用是揭示多态性特征的一些基本的体现,比如重载和重写
静态分派 :最典型的应用代表就是方法重载,**静态分派发生在编译阶段,**因此静态分派实际上不是由虚拟机来执行的
动态分派:调用中的invokevirtual指令并不是把常量池中方法的符号引用解析到直接引用解析到直接引用上就结束,还会根据方法接收者的实际类型来选择方法版本;我们把这种在运行期根据实际的类型确定方法执行版本的分派过程称为动态分派。
2.虚方法表
-
虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那子类的虚方法地址和父类相同方法的地址入口是一致的,都指向父类的实现入口。如果子类重写了这个方法,子类虚方法表中的地址会被替换为指向子类实现版本的入口地址。
-
动态语言的核心特征就是:变量obj本身没有类型,变量obj的值才具有类型。
3.java.lang.invoke包
- Reflection API的设计目标是只为Java语言服务的,而MethodHandle 则设计为可服务于所有Java虚拟机之上的语言,其中也包括了Java语言而已,而且Java在这里并不是主 角。
4.invokedynamic指令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5J7oS4FX-1676015188558)(C:\Users\裴承林\AppData\Roaming\Typora\typora-user-images\image-20230129081654367.png)]
类加载器和字节码的案列
1.Tomcat如何规划类库结构和类加载器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7UDBYh7-1676015188559)(…/…/…/img/image-20230129091208412.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOHXrRyE-1676015188559)(…/…/…/img/image-20230129091800433.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZNqMG8aO-1676015188560)(…/…/…/img/image-20230129092549864.png)]
执行临时代码的功能:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QWKcOwOB-1676015188560)(…/…/…/img/image-20230129103153562.png)]
编译期的优化
即时编译被触发的条件?
- 基于采样的热点探测。
采用这种方法的虚拟机会周期性的检查各个线程的调用栈顶,如果经常出现在栈顶,这个方法就是热点方法
- 基于计数器的热点探测
统计执行次数超过一定的阈值就认为是“热点方法”。
运行期优化
方法内联
概念:就是把目标方法的代码原封不动地“复制”到发起调用的方法之中,避免发生真实的方法调用而已。
逃逸分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bpYSEG0W-1676015188561)(…/…/…/img/image-20230129141936187.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUQXv86w-1676015188561)(…/…/…/img/image-20230115100422065.png)]
逃逸分析,针对外部用不到的对象对其进行优化
- 字段优化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oVH6CEtN-1676015188562)(…/…/…/img/image-20230115101752681.png)]
方法二的效率最高。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dbWhmeJh-1676015188562)(…/…/…/img/image-20230115102339394.png)]
反射优化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJuEzSzJ-1676015188563)(…/…/…/img/image-20230115102728311.png)]
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;
NativeMethodAccessorImpl(Method method) {
this.method = method;
}
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
// We can't inflate methods belonging to vm-anonymous classes because
// that kind of class can't be referred to by name, hence can't be
// found from the generated bytecode.
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}
return invoke0(method, obj, args);
}
void setParent(DelegatingMethodAccessorImpl parent) {
this.parent = parent;
}
private static native Object invoke0(Method m, Object obj, Object[] args);
}
JMM:
可见性:修改时对其他线程是能够知道的,同时适合一个写线程多个读线程。
线程安全
-
ABA问题:当一个变量V初次读取的时候是A值,并且准备赋值的时候检查到还是A,那么也不能说明没有被改变,如果在这期间值被改成B后来又改成A了,那么CAS会认为它从来没有被改变过
-
解决:JUC包提供了一个标记用的原子引用类AtomicStampedReference,控制变量值的版本来保证CAS的正确性
-
对于线程安全来说,可重入性是更为基础的特性,它可以保证代码线程安全,即所有可重入的代码都是线程安全的
我们可以通过一个比较简单的原则来判断 代码是否具备可重入性:如果一个方法的返回结果是可以预测的,只要输入了相同的数据,就都能返 回相同的结果,那它就满足可重入性的要求,当然也就是线程安全的。
自旋锁和自适应自旋锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ms9mFkSl-1676015188563)(…/…/…/img/image-20230129210553234.png)]
锁消除
对被检测到不可能存在共享竞争的锁进行消除。锁消除主要判定依据来源于逃逸分析的数据支持
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rh3iPZJ7-1676015188564)(…/…/…/img/image-20230129211742705.png)]
锁粗化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8NXLffGx-1676015188564)(…/…/…/img/image-20230129212119069.png)]
偏向锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BN0Guk4Q-1676015188565)(…/…/…/img/image-20230129213055495.png)]
actory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}
return invoke0(method, obj, args);
}
void setParent(DelegatingMethodAccessorImpl parent) {
this.parent = parent;
}
private static native Object invoke0(Method m, Object obj, Object[] args);
}
JMM:
可见性:修改时对其他线程是能够知道的,同时适合一个写线程多个读线程。
## 线程安全
- ABA问题:当一个变量V初次读取的时候是A值,并且准备赋值的时候检查到还是A,那么也不能说明没有被改变,如果在这期间值被改成B后来又改成A了,那么**CAS会认为它从来没有被改变过**
- 解决:JUC包提供了一个标记用的原子引用类AtomicStampedReference,控制变量值的版本来保证CAS的正确性
- 对于线程安全来说,可重入性是更为基础的特性,它可以保证代码线程安全,即所有可重入的代码都是线程安全的
我们可以通过一个比较简单的原则来判断 代码是否具备可重入性:如果一个方法的返回结果是可以预测的,只要输入了相同的数据,就都能返 回相同的结果,那它就满足可重入性的要求,当然也就是线程安全的。
### 自旋锁和自适应自旋锁
[外链图片转存中...(img-Ms9mFkSl-1676015188563)]
### 锁消除
对被检测到不可能存在共享竞争的锁进行消除。锁消除主要判定依据来源于逃逸分析的数据支持
[外链图片转存中...(img-Rh3iPZJ7-1676015188564)]
### 锁粗化
[外链图片转存中...(img-8NXLffGx-1676015188564)]
#### 偏向锁
[外链图片转存中...(img-BN0Guk4Q-1676015188565)]