news 2026/9/7 11:05:55

JVM必备知识点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM必备知识点

一、synchronized工作原理

修饰普通方法,锁住的是当前对象的实例

修饰静态方法,锁住的是当前Class对象

修饰代码块,锁住的是括号里的对象

原理:

是基于监视器锁实现的,使用monitorenter和monitorexit指令完成。monitorenter编译成字节码后插入到同步代码块的开始位置,monitorexit插入到方法结束处和异常处。

一个线程开始会执行monitorenter指令尝试获得监视器锁的所有权。获得后,监视器进入数为1,并记录线程的所有者为当前线程,其他线程阻塞。重复进入,进入数+1。执行完后,通过monitorexit将进入数-1,直到进入数为0,其他线程才能获得锁。

锁:

1.自旋锁:锁的持有时间比较短,通过循环等待的方式来避免挂起恢复造成性能浪费。

2.偏向锁:当只有同一个线程获取的时候,就不需要等待,直接就能访问。

无锁状态,第一个线程访问的时候,将markword的锁标志位设置为偏向锁,将线程id记录到markword中。同意线程再次进入开销很小,只需要线程id对比。偏向锁不计数,所以遇到竞争只能升级。

3.轻量级锁:多个线程大部分情况下也不会存在竞争,所以这个时候可以使用轻量级锁。

第二个线程进入的时候,先cas加自旋等待的方式获取锁。

轻量级锁获取锁过程:当线程成进入代码同步块的时候,如果此时为无锁状态,虚拟机首先在栈帧中创建一个叫Lock record的空间,然后将对象头拷贝到该空间中,使用CAS更新对象头中指向Lock record的指针,并将lock record中owner指向对象头。如果更新成功,那么该现成持有该对象的锁,锁标记为00,表示轻量锁。

如果失败,检查对象头中是否是指向该线程的栈帧,如果是,就可以直接进入,如果不是,就需要变为重量级锁,同意线程重复进入,会创建多个lock record.

4.重量级锁:锁标记为10,指针指向monitor对象(监视器锁),

轻量级锁发生竞争,就切换到重量级锁,用的是计算机内核阻塞。

二、划分

1.程序计数器,线程私有,用于代码执行

2.Java虚拟机栈。线程私有,存储方法相关的信息,如局部变量、方法出口等。一个方法从调用到结束,就对应一个栈帧从Java虚拟机栈的入栈和出栈的过程。

3.本地方法栈。线程私有,为使用到的native方法服务。

4.方法区。内存共享,存储已被虚拟机加载的类信息,常量,静态变量,也就是编译后的代码数据。

5.java堆。内存共享,存储对象实例和数组。

三、对象内存分布

1.对象头

分为2部分,第一部分存储自身运行时数据,哈希码,GC分代年龄,锁状态标志,线程持有的锁、偏向线程 ID、偏向时间戳等。32位机器占32bit,64位机器占64bit。官方称为mark word.

第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例。如果是数组,还得记录数组的长度。

2.实例数据

程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)。

3.对齐填充

不是必然需要,主要是占位,保证对象大小是某个字节的整数倍。

四、volatile关键字

1.定义的共享变量存储在主存中,而每个线程都有私有本地内存,修改某个值,会先拷贝副本到本地内存,修改完后,同步到主内存。2个线程会有不可见的问题,volatile关键字修饰的变量,会在写变量的时候强制刷入主存,并使其他线程中的变量缓存无效。

2.禁止指令重排 重排操作是为了优化性能,保证的是最终执行结果不变,但在多个线程中会有问题。

3.无法保证原子性。syn修饰可以保证3个都有。

五、OOM

  1. java虚拟机栈溢出 stack

  2. 方法区溢出 directMemory

  3. 运行时常量溢出 constant

  4. 堆溢出 heap

六、垃圾回收

1.判断方法:

引用计数:

给对象添加一个引用计数器,被引用一次就+1,引用失效就-1,计数器为0,对象就是不可能再被使用的。缺点是没法解决循环引用问题。

可达性分析:

通过gcroot的对象为起点,从这些节点向下搜索,走过的路径成为引用链,当一个对象没有引用链时,则这个对象不可用。

Java虚拟机栈,本地方法栈,方法区中类静态属性、常量引用的对象。

引用:

  1. 强引用: new 不回收

  2. 软引用:通过继承SoftReference实现,系统要发生内存溢出前回收。

  3. 弱引用:通过继承WeakReference实现。对象只能生存到下一次垃圾回收之前。

  4. 虚引用:通过继承PhantomReference实现。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

2.回收算法:

标记-清除法:

分为标记、清除两个阶段,首先标记出所有需要回收的对象,然后统一回收。

1.效率问题,两个阶段效率都不高

2.空间问题,产生大量控件碎片。

复制算法:

将内存按容量划分为大小相同的2块,每次只使用一块,当一块用完了,就将存活的对象复制到另一块。然后把当前清空。

缺点:把内存缩小了一半。

商业虚拟机都采用这种,不过是将内存划分为3块,一般是8:1:1,回收时,将存活的对象从Eden和其中一块存活区复制到另一块存活区。当存活区空间不够,就需要老年代了。

标记整理算法:

当对象存活率高的时候,就需要进行较多的复制,效率会变低。所以老年代不适合。标记出需要清理的对象,然后让存活的对象向一端移动,然后清理掉端边界以外的内存。

分代回收算法:

将内存划分为新生代和老年代,新生代只有少量对象存活,采用复制算法,老年代采用标记整理算法。

3.垃圾回收器

Serial收集器(串行收集器)

新生代收集器,复制算法。一个单线程收集器,在进行垃圾回收时,必须暂停其他工作线程。

ParNew收集器

新生代收集器,复制算法。上一个收集器的多线程版

Parallel Scavenge收集器

新生代收集器,采用复制算法。也是并行的多线程收集器。注重高吞吐量

特点:gc动态调节,可以自动调节新生代内存大小比例。

Serial Old 收集器

是serial 收集器的老年代版本,采用标记整理算法。

Parallel Old 收集器

老年代,标记整理算法。是Parallel Scavenge收集器的老年代版本。注重高吞吐量,平衡每次回收时间和回收间隔。

CMS收集器

一种以获取最短回收停顿时间为目标的收集器。老年代收集器

特点:基于标记-清除算法实现。并发收集、低停顿

工作过程:

1:初始标记 标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题

2:并发标记 进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。

3:重新标记 为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。

4:并发清理 对标记的对象进行清除回收。

缺点:

  • 对CPU资源非常敏感。

  • 无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。(因为使用并发标记,所以得预留一部分空间,如果空间不足用户线程,就会出现。)

  • 因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。

G1收集器

新生代和老年代收集器

把这个内存划分成了大小相等的区域,每个区域都会有一个对应的Rememberd set,会记录当前区域中的对象被引用记录。

特点:管理整个堆、空间整合,不会产生碎片、可预测的停顿、并行与并发

工作过程:

1.初始标记,仅标记GC Roots能直接到的对象

2.并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。

3.最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。

4.筛选回收:对各个区的回收价值和成本进行排序,根据用户所希望的回收时间来指定回收计划。

特点:不会产生碎片。可预测停顿。

ZGC垃圾回收器:

优点:低延迟,GC停顿时间不超过10ms,不会随堆内存增大而增大。

超大内存支持,支持8M到16T的内存返回

缺点:不适合cpu密集,存活时间短,对象创建过快的场景。

原理:

1、三色指针,Mark0,mark1,remapped。第一次回收的时候,将活跃的标记为mark0,不活跃的为remapped,如果当前页垃圾多,就转移,转移后的为remapped,没转移的为mark0。下次回收的时候用mark1。

2、读屏障,用户线程访问的时候,进行标记处理。比如访问某个remapped对象,就标记为mark0.

3、基于Region的分页模型。将内存划分大中小三类不同的区域。

4.gc 频率

根据运行时长和回收次数进行计算。 只有满了才会回收。

5.jvm 调优

1.监控gc状态

2.分析gc频率

3.修改新老内存大小,新生区比例,选择合适的垃圾回收器。

七、CAS

1.简介

compare and swap比较并交换。读取值,计算好要修改的新值。如果读取的值和内存值一样,则成功,否则失败。

2.实现

AtomicInteger类就使用了。

public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); ​ return var5; }

修改失败会一直重试。

3.缺点:

1.ABA问题,将值从a修改为b,在修改为a。 可以用版本号解决。

2.循环时间长,开销大。

八、线程

7.1 wait notify notifyAll

只能在同步代码块里面使用。

wait作用:1.当前线程进入等待队列,执行monitor exit释放锁。2.挂起当前线程

notify:1.调用后,等当前线程释放锁后,才会从等待队列中任意唤醒一个现成,去竞争锁。

notifyAll:唤起所有线程。

7.2 interrupt

当线程处于等待状态或者有超时的等待状态时(TIMED_WAITING,WAITING)我们可以通过调用线程的interrupt()方法来中断线程的等待,此时线程会抛InterruptedException异常。但是当线程处于BLOCKED状态或者RUNNABLE(RUNNING)状态时,调用线程的interrupt()方法也只能将线程的状态位设置为true。停止线程的逻辑需要我们自己去实现。

1)interrupt()方法,这是Thread类的实例方法。对一个线程调用interrupt()方法表示请求中断这个线程。该方法是唯一能将中断状态设置为true的方法。中断后直接退出。

2)isInterrupted()方法,这是Thread类的实例方法。测试线程是否已经中断,也就是测试线程中断状态是否设置为true。

3)Thread.interrupted(),这是Thread类的静态方法。判断线程是否被中断并清除中断状态。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(再次中断的情况除外)。

7.3 join

Thread t = new Thread(); t.join();

join方法有synchronized修饰,锁住的是当前线程对象。

等待当前线程结束。也就是等待t结束。然后调用方线程再执行。如果传时间,调用方线程等待n毫秒。

public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; ​ if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } ​ if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }

main线程进入,锁住的是t线程。main函数执行到wait处进行等待。while的作用是,当t.notifyAll被执行,main线程被唤醒,t线程还是Alive,还会继续阻断main,直到t执行完,即不是Alive

7.4 yeild

将现成从运行状态转为ready状态

八、类加载过程

9.1 双亲委派

一个类加载器收到类加载的请求后,先让父类去加载,如果父类没法完成,子类才会去加载。

好处:1.保护核心api,不会被篡改 2.加载过的类,不用再次加载

9.2 类加载过程

  1. 加载

    在内存中生成改类的Class对象,作为类数据的访问入口

  2. 验证Class文件字节流信息符合要求。

  3. 准备

    为类的静态变量分配内存,并初始化为默认值。

  4. 解析

    将常量池中的符号引用替换为直接引用。(符号引用是一种描述符c里面的)

  5. 初始化

    按顺序调用方法初始化数据。

  6. 使用

  7. 卸载

9.3 对象初始化过程

1.给对象分配内存空间

2.初始化对象属性

3.复制给引用变量

十、TLAB

多个线程同时创建对象,可能会造成堆内存分配出现冲突。Thread Local Allocation Buffer,为每个线程单独分配一块内存。

内存泄露

1.集合中放对象,长生命周期的引用短生命周期。

2.static变量持有对象,无法释放。

3.声明很大的数组。

4.资源的连接,当前线程一直运行的话,资源就不会释放。

十一、ThreadLocal

thread->ThreadLocalMap->entry[]->Entry<threadLocal, Value>,entry中的key是weakreference。

如果线程是核心线程或者可以重复使用,那么ThreadLocalMap一直持有entry节点的强引用,释放不了,虽然方法执行完了threadlocal释放了,但是entry[]数组中依然持有,remove之后,Entry节点的强引用就没了。Entry对象就能回收了。

十二、线程池

增加线程方法:

没有超过核心线程数,创建核心线程。

超过核心线程数,加入队列。

队列满了,创建非核心线程。

都满了, 拒绝策略。

拒绝策略:

1、抛出异常。2、当前线程处理。3、直接丢弃。4、丢弃最老的。5、自定义。

关闭线程:

shutdown:将线程池状态置为shutdown,并不会立即停止:
1.停止接收外部submit的任务
2.内部正在跑的任务和队列里等待的任务,会执行完
3.等到第二步完成后,才真正停止。

shutdownNow:将线程池状态置为stop。一般会立即停止,事实上不一定:
1.和shutdown(一样,先停止接收外部提交的任务
2.忽略队列里等待的任务
3.尝试将正在跑的任务interrupt中断
4.返回未执行的任务列表。

状态:

  • RUNNING:正常接收并处理任务
  • SHUTDOWN:不接收新任务,但会处理已提交的任务
  • STOP:不接收新任务,不处理队列任务,中断正在执行的任务
  • TIDYING:所有任务已终止,准备执行terminated()钩子方法
  • TERMINATED:线程池彻底终止

十三、AQS

1、原理

AQS的核心工作原理基于一个FIFO等待队列和一个同步状态(state)。其主要步骤如下:

同步状态:AQS通过一个 int 类型的变量 state 来表示同步状态。子类通过重写 tryAcquire、tryRelease 等方法来定义获取和释放同步状态的逻辑。
等待队列:当线程无法获取同步状态时,会被加入到AQS的FIFO等待队列中,队列中的每个节点(Node)表示一个等待的线程。
独占模式和共享模式:AQS支持独占模式(如独占锁)和共享模式(如共享锁、信号量)。在独占模式下,只有一个线程可以获取同步状态;在共享模式下,多个线程可以同时获取同步状态。
模板方法:AQS通过模板方法模式提供了通用的同步机制,子类只需实现特定的同步逻辑。

2、流程

1、尝试获取同步状态,获取失败,加入阻塞队列最后,将前一个节点status设置为SIGNAL:-1,表示后面有节点需要唤醒。

2、独占模式,换新后面的一个,共享模式循环唤醒后面所有的线程。

3、锁的实现

1、重入锁(独占):公平和非公平,公平锁如果有线程等待就加入,非公平锁会先尝试获取锁,获取不到才去等待。

2、信号量(共享):

3、CountDownLatch(共享)

4、状态问题

SINGAL:表示下一个节点要被唤醒。

CANCEL: 节点被取消。

CONDITION: 条件节点,等待满足某个条件的时候会进入条件队列。

1、当前线程执行condition.await,将当前线程加入到condition队列
2、完全释放掉ReentrantLock独占锁(执行await必先持有锁,这里必须先释放锁)
3、当前线程挂起,等着进入等待(同步)队列
4、另一个线程执行signal唤醒,signal唤醒会将线程从condition队列转移到等待(同步)队列
当前线程转移到等待(同步)队列后,等待被它的前节点唤醒,signal后会调用unlock。
5、当前节点被唤醒后,重新获取到独占锁后,才能继续往下执行

propagate状态:1.6之前的bug,head(-1)<-A(-1)<-B,当有一个线程释放信号量的时候,head变成(0),A去获取信号量,信号量又称为0,这个时候A判断信号量为0,就不在唤醒下一个了。但是A判断之后,又有别的线程释放信号量,此时head是0,不会去唤醒。A也不换唤醒,此时B就阻塞了。

for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 11:04:00

含阶跃信号的连续时间卷积计算:分段建模与高效解题框架

你刷到“含阶跃信号的连续时间卷积计算”这类题目时&#xff0c;有没有过这样一种感觉&#xff1a;卷积定义背得滚瓜烂熟&#xff0c;积分公式也都会&#xff0c;但一涉及阶跃信号&#xff0c;区间一多就开始乱&#xff0c;要么漏掉某个分段&#xff0c;要么端点对不上&#xf…

作者头像 李华
网站建设 2026/9/7 11:01:52

MATLAB+xFoil+PARSEC:经典亚音速翼型优化流程详解

简介&#xff1a;面向航空器设计与优化领域的MATLAB开发者&#xff0c;该zip演示如何借助xFoil与ParseCGeometric完成机翼参数化优化。xFoil承担亚声速翼型气动性能计算&#xff0c;ParseCGeometric实现几何参数定义与映射&#xff0c;二者结合可在MATLAB环境中迭代调整翼型厚度…

作者头像 李华
网站建设 2026/9/7 11:00:35

广东GEO公司推荐:从技术实力到落地效果的7个选型维度

摘要&#xff1a; 本文推荐知策数智这家优质的GEO服务商&#xff0c;从7个核心维度系统拆解广东企业如何筛选靠谱的GEO公司。知策数智总部位于东莞&#xff0c;在茂名、昆明、上海设有直营团队&#xff0c;拥有10年搜索基因与4年AI工程化经验&#xff0c;自研GEO监测系统&#…

作者头像 李华
网站建设 2026/9/7 11:00:29

GPU内存访问模式优化:从访存瓶颈到带宽榨干实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 11:00:24

283、仿真-51单片机的波形发生器(三角波,调频,调幅)Proteus仿真设计(程序+Proteus仿真+原理图+元器件清单+程序流程图+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、设计功能 二、Proteus仿真图 三、原理图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&…

作者头像 李华
网站建设 2026/9/7 11:00:22

284、51单片机步进电机正反转加减速数码管显示控制系统(程序+Proteus仿真+原理图+PCB图+元件清单+参考论文+开题报告+任务书+开发资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、设计功能 二、实物图和proteus仿真图 三、原理图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方…

作者头像 李华