时间:2025-06-18 23:12
人气:
作者:admin
即记录对象的 reference count 若≠0则保留
a, b对象相互引用, 不可回收, 造成内存泄露
从GC Root出发的树状结构
若对象不可达则回收
在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为 null)或者漏报(将引用设置为未被访问过的对象)
当 Java 虚拟机收到 Stop-the-world 请求,它便会等待所有的线程都到达安全点,才允许请求 Stop-the-world 的线程进行独占的工作。(安全词????????????)




Minor GC收集新生代的垃圾, 同时扫描老年代的对象以确保所有被引用的年轻代对象都被正确标记为存活对象。
copy 进行垃圾回收TLAB(acquire lock())
每个线程可以向 Java 虚拟机申请一段连续的内存
线程维护内存头尾指针
维护每张卡的dirty位
卡中存在写入, 则dirty位置1
卡: 将堆划分为多个512字节的卡
if (CARD_TABLE [this address >> 9] != DIRTY) //减少重复写的不必要开销
CARD_TABLE [this address >> 9] = DIRTY;
为避免扫描所有的老年代对象 ,导致新生代的对象重复扫描,导致的重复扫描对象堆
GC通过寻找dirty卡扫描, 扫描完dirty清零
在单线程中由于 as-if-serial 原则 不会改变程序重排序的运行结果
多线程就要涉及到 happen-before
描述两个操作的内存可见性
如果操作 X happens-before 操作 Y,那么 X 的结果对于 Y 可见
对于即时编译器来说,它会针对前面提到的每一个 happens-before 关系,向正在编译的目标方法中插入相应的读读、读写、写读以及写写内存屏障。
内存屏障
即时编译器将根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令
如6.S081的__sync_synchronize()
以我们日常接触的 X86_64 架构来说,读读、读写以及写写内存屏障是空操作(no-op),只有写读内存屏障会被替换成具体指令
当声明 synchronized 代码块时,编译而成的字节码将包含 monitorenter 和 monitorexit 指令
public void foo(Object lock) {
synchronized (lock) {
lock.hashCode();
}
}
// 上面的 Java 代码将编译为下面的字节码
public void foo(java.lang.Object);
Code:
0: aload_1
1: dup
2: astore_2 //复制lock对象
3: monitorenter
4: aload_1
5: invokevirtual java/lang/Object.hashCode:()I
8: pop
9: aload_2
10: monitorexit
11: goto 19
14: astore_3
15: aload_2
16: monitorexit
17: aload_3
18: athrow
19: return
Exception table:
from to target type
4 11 14 any
14 17 14 any
public synchronized void foo(Object lock) {
lock.hashCode();
}
// 上面的 Java 代码将编译为下面的字节码
public synchronized void foo(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: (0x0021) ACC_PUBLIC, **ACC_SYNCHRONIZED**
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: invokevirtual java/lang/Object.hashCode:()I
4: pop
5: return
该标记表示在进入该方法时,Java 虚拟机需要进行 monitorenter 操作。而在退出该方法时,不管是正常返回,还是向调用者抛异常,Java 虚拟机均需要进行 monitorexit 操作
|-----------------------------------------------------------|------------------|
| Thread ID (偏向锁) / HashCode / GC Age (其他状态) | 锁标志 | epoch |
|-----------------------------------------------------------|------------------|
| 54 bits | 3 bits | 2 bits |
Java 虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程
在被阻塞前, 线程先进入自旋状态
自旋 在处理器上空跑并且轮询锁是否被释放
Java 虚拟机给出的方案是自适应自旋,根据以往自旋等待时是否能够获得锁,来动态调整自旋的时间(循环数目)
通过CAS判断锁的标记字段是否为01
超级无敌乐观锁 偏好从始至终只有一个线程请求某一把锁
JVM通过 CAS 操作,将当前线程的地址记录在锁对象的标记字段之中,并且将锁标志设置为 101(偏向锁状态), 同时设置锁对象的epoch = 全局epoch
HotSpot 虚拟机包含多个即时编译器 C1、C2 和 Graal

热点代码: JVM通过识别代码块的循环回边数(循环体调用次数)和调用次数两者取和
(on- stack - replacement)
顾名思义就是在程序运行过程中(暂停线程)将通过JIT编译后的本地代码栈帧替换字节码栈帧
收集能够反映程序执行状态的数据
其中,最为基础的便是方法的调用次数以及循环回边的执行次数。它们被用于触发即时编译
当发现profile优化后的代码没有按预期执行, 线程进入trap 暂停线程通过OSR进行执行代码栈帧转换
当JVM调用去优化方法时,它会根据去优化的原因来决定对即时编译器生成的机器码采取什么行动。具体来说,有三种可能的行动:
示例:假设某个方法的去优化是因为出现了异常,但这个异常与优化无关,重新编译也不会改变生成的机器码。在这种情况下,JVM可以选择保留当前的机器码,下次调用该方法时直接使用。
示例:假设某个方法的去优化是因为类层次分析的结果发生了变化,例如新加载了一个子类,导致之前的优化假设不再成立。在这种情况下,JVM可以选择不保留当前的机器码,但直接重新编译该方法。
示例:假设某个方法的去优化是因为基于性能分析的激进优化失败了,例如某个假设的执行路径不再成立。在这种情况下,JVM需要重新收集性能数据,以更好地反映程序的新的执行状态。
public void method() {
try {
// 可能引发异常的代码
} catch (Exception e) {
// 处理异常
}
}
public void method() {
for (int i = 0; i < 1000000; i++){
// 热点代码
}
}
public class Parent {
public void method() {
// 方法体
}
}
public class Child extends Parent {
@Override
public void method() {
// 重写的方法体
}
}public class Main {
public void caller(Parent p) {
p.method(); // 假设这里被内联了
// Parent的method
}
}
如果不考虑解释执行的话,从 Java 源代码到最终的机器码实际上经过了两轮编译:Java 编译器将 Java 源代码编译成 Java 字节码,而即时编译器则将 Java 字节码编译成机器码。
Java 字节码本身并不适合直接作为可供优化的 IR
现代编译器一般采用静态单赋值(Static Single Assignment,SSA)IR
每个变量只能被赋值一次,而 且只有当变量被赋值之后才能使用
y = 1; SSA伪代码 y1 = 1;
y = 2; - - - - -> y2 = 2;
x = y; x1 = y2;
int x = 0;
if (condition) {
x = 1;
} else {
x = 2;
}
// 使用x
int x_1 = 0;
if (condition) {
x_1 = 1; // 重用x_1
} else {
x_1 = 2; // 重用x_1
}
// 使用x_1
int x_1 = 0;
if (condition){
x_2 = 1;
}else{
x_3 = 2;
}x_4 = φ(x_2, x_3);
// 使用x4
去除了变量的概念,直接采用变量所指向的值,来进行运算

节点调度需根据节点间的依赖关系进行
Global Value Numbering
发现并消除等价计算的优化技术
public static int foo(int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += i;
}
return sum;
}

public static int foo(int a, int b) {
int sum = a * b;
if (a > 0) {
sum += a * b;
}
if (b > 0) {
sum += a * b;
}
return sum;
}
下一篇:JVM内存结构33连问