没有合适的资源?快使用搜索试试~ 我知道了~
JVM 基础 JAVA 并发 JVM 性能调优 LeetCode 算法 .......
需积分: 0 1 下载量 45 浏览量
2022-12-14
18:59:20
上传
评论
收藏 2.07MB PDF 举报
温馨提示
试读
232页
JVM 基础 JAVA 并发 JVM 性能调优 LeetCode 算法 .......
资源推荐
资源详情
资源评论
JVM 基础.xls
JVM 参数有哪些?
JVM 参数⼤致可以分为三类: 1. 标注指令: -开头,这些是所有的 HotSpot 都⽀持的参
数。可以⽤java -help 打印出来。 2. ⾮标准指令: -X 开头,这些指令通常是跟特定的
HotSpot 版本对应的。可以⽤java -X 打印出来。 3. 不稳定参数: -XX 开头,这⼀类参数是跟
特定 HotSpot 版本对应的,并且变化⾮常⼤。详细的⽂档 资料⾮常少。在 JDK1.8 版本下,
有⼏个常⽤的不稳定指令: java -XX:+PrintCommandLineFlags : 查看当前命令的不稳定指
令。 java -XX:+PrintFlagsInitial : 查看所有不稳定指令的默认值。 java -XX:+PrintFlagsFinal:
查看所有不稳定指令最终⽣效的实际值。
什么是三⾊标记?
三⾊标记:是⼀种逻辑上的抽象。将每个内存对象分成三种颜⾊: 1. ⿊⾊:表示⾃⼰和成
员变量都已经标记完毕。 2. 灰⾊:⾃⼰标记完了,但是成员变量还没有完全标记完。 3. ⽩
⾊:⾃⼰未标记完。
垃圾回收分为哪些阶段
GC 分为四个阶段: 第⼀:初始标记 标记出 GCRoot 直接引⽤的对象。STW 第⼆:标记
Region,通过 RSet 标记出上⼀个阶段标记的 Region 引⽤到的 Old 区 Region。 第三:并发
标记阶段:跟 CMS 的步骤是差不多的。只是遍历的范围不再是整个 Old 区,⽽只需要遍 历
第⼆步标记出来的 Region。 第四:重新标记: 跟 CMS 中的重新标记过程是差不多的。 第
五:垃圾清理:与 CMS 不同的是,G1 可以采⽤拷⻉算法,直接将整个 Region 中的对象拷
⻉到另 ⼀个 Region。⽽这个阶段,G1 只选择垃圾较多的 Region 来清理,并不是完全清
理。
JVM 有哪些垃圾回收器?
新⽣代收集器: Serial ParNew Parallel Scavenge ⽼年代收集器: CMS Serial Old Parallel Old
整堆收集器: G1
什么是 STW?
STW: Stop-The-World,是在垃圾回收算法执⾏过程当中,需要将 JVM 内存冻结的⼀种状
态。在 STW 状态下,JAVA 的所有线程都是停⽌执⾏的-GC 线程除外,native⽅法可以执
⾏,但是,不能与 JVM 交 互。GC 各种算法优化的重点,就是减少 STW,同时这也是 JVM
调优的重点。
JVM 有哪些垃圾回收算法?
1. MarkSweep 标记清除算法:这个算法分为两个阶段,标记阶段:把垃圾内存标记出来,
清除阶 段:直接将垃圾内存回收。这种算法是⽐较简单的,但是有个很严重的问题,就是
会产⽣⼤量的内 存碎⽚。 2. Copying 拷⻉算法:为了解决标记清除算法的内存碎⽚问题,
就产⽣了拷⻉算法。拷⻉算法将内存 分为⼤⼩相等的两半,每次只使⽤其中⼀半。垃圾回
收时,将当前这⼀块的存活对象全部拷⻉到另 ⼀半,然后当前这⼀半内存就可以直接清
除。这种算法没有内存碎⽚,但是他的问题就在于浪费空 间。⽽且,他的效率跟存货对象
的个数有关。 3. MarkCompack 标记压缩算法:为了解决拷⻉算法的缺陷,就提出了标记压
缩算法。这种算法在标 记阶段跟标记清除算法是⼀样的,但是在完成标记之后,不是直接
清理垃圾内存,⽽是将存活对象 往⼀端移动,然后将端边界以外的所有内存直接清除。 这
三种算法各有利弊,各⾃有各⾃的适合场景。
怎么确定⼀个对象到底是不是垃圾?
1. 引⽤计数: 这种⽅式是给堆内存当中的每个对象记录⼀个引⽤个数。引⽤个数为 0 的就
认为是垃 圾。这是早期 JDK 中使⽤的⽅式。引⽤计数⽆法解决循环引⽤的问题。 2. 根可达
算法: 这种⽅式是在内存中,从引⽤根对象向下⼀直找引⽤,找不到的对象就是垃圾。
⼀个对象从加载到 JVM,再到被 GC 清除,都经历了什么过程?
1. ⽤户创建⼀个对象,JVM⾸先需要到⽅法区去找对象的类型信息。然后再创建对象。 2.
JVM 要实例化⼀个对象,⾸先要在堆当中先创建⼀个对象。-> 半初始化状态 3. 对象⾸先会
分配在堆内存中新⽣代的 Eden。然后经过⼀次 Minor GC,对象如果存活,就会进⼊S 区。
在后续的每次 GC 中,如果对象⼀直存活,就会在 S 区来回拷⻉,每移动⼀次,年龄加
1。-> 多 ⼤年龄才会移⼊⽼年代? 年龄最⼤15, 超过⼀定年龄后,对象转⼊⽼年代。 4. 当
⽅法执⾏结束后,栈中的指针会先移除掉。 5. 堆中的对象,经过 Full GC,就会被标记为垃
圾,然后被 GC 线程清理掉。
你们项⽬如何排查 JVM 问题
对于还在正常运⾏的系统: 1. 可以使⽤jmap 来查看 JVM 中各个区域的使⽤情况 2. 可以通
过 jstack 来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁 3. 可以通过 jstat 命
令来查看垃圾回收的情况,特别是 fullgc,如果发现 fullgc⽐较频繁,那么就得进⾏ 调优了
4. 通过各个命令的结果,或者 jvisualvm 等⼯具来进⾏分析 5. ⾸先,初步猜测频繁发送
fullgc 的原因,如果频繁发⽣fullgc 但是⼜⼀直没有出现内存溢出,那么表 示 fullgc 实际上
是回收了很多对象了,所以这些对象最好能在 younggc 过程中就直接回收掉,避免 这些对
象进⼊到⽼年代,对于这种情况,就要考虑这些存活时间不⻓的对象是不是⽐较⼤,导致
年 轻代放不下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤⼩,如果改完之后,fullgc 减
少,则证明 修改有效 6. 同时,还可以找到占⽤CPU 最多的线程,定位到具体的⽅法,优化
这个⽅法的执⾏,看是否能避免 某些对象的创建,从⽽节省内存 对于已经发⽣了 OOM 的
系统: 1. ⼀般⽣产系统中都会设置当系统发⽣了 OOM 时,⽣成当时的 dump⽂件(-
XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base) 2. 我们可以利⽤
jsisualvm 等⼯具来分析 dump⽂件 3. 根据 dump⽂件找到异常的实例对象,和异常的线程
(占⽤CPU⾼),定位到具体的代码 4. 然后再进⾏详细的分析和调试 总之,调优不是⼀蹴
⽽就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题
JVM 中哪些是线程共享区
堆区和⽅法区是所有线程共享的,栈、本地⽅法栈、程序计数器是每个线程独有的 【图片
地址:20210810/30ecffc756df93d4ab3ecf045153906c.png】
JAVA 并发.xls
谈谈你对 AQS 的理解,AQS 如何实现可重⼊锁?
1. AQS 是⼀个 JAVA 线程同步的框架。是 JDK 中很多锁⼯具的核⼼实现框架。 2. 在 AQS
中,维护了⼀个信号量 state 和⼀个线程组成的双向链表队列。其中,这个线程队列,就是
⽤ 来给线程排队的,⽽state 就像是⼀个红绿灯,⽤来控制线程排队或者放⾏的。 在不同
的场景下, 有不⽤的意义。 3. 在可重⼊锁这个场景下,state 就⽤来表示加锁的次数。0 标
识⽆锁,每加⼀次锁,state 就加 1。释 放锁 state 就减 1。
Sychronized 和 ReentrantLock 的区别
1. sychronized 是⼀个关键字,ReentrantLock 是⼀个类 2. sychronized 会⾃动的加锁与释放
锁,ReentrantLock 需要程序员⼿动加锁与释放锁 3. sychronized 的底层是 JVM 层⾯的锁,
ReentrantLock 是 API 层⾯的锁 4. sychronized 是⾮公平锁,ReentrantLock 可以选择公平锁或
⾮公平锁 5. sychronized 锁的是对象,锁信息保存在对象头中,ReentrantLock 通过代码中
int 类型的 state 标识 来标识锁的状态 6. sychronized 底层有⼀个锁升级的过程
Sychronized 的偏向锁、轻量级锁、重量级锁
1. 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程 ID,该线程下次如果⼜来
获取该锁就 可以直接获取到了 2. 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后,
此时这把锁是偏向锁,此时如果有第⼆个 线程来竞争锁,偏向锁就会升级为轻量级锁,之
所以叫轻量级锁,是为了和重量级锁区分开来,轻 量级锁底层是通过⾃旋来实现的,并不
会阻塞线程 3. 如果⾃旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导
致线程阻塞 4. ⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓
唤醒线程,阻塞和唤醒 这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,⾃旋锁是
线程通过 CAS 获取预期的⼀个标 记,如果没有获取到,则继续循环获取,如果获取到了则
表示获取到了锁,这个过程线程⼀直在运 ⾏中,相对⽽⾔没有使⽤太多的操作系统资源,
⽐较轻量。
CountDownLatch 和 Semaphore 的区别和底层原理
CountDownLatch 表示 计 数 器 , 可 以 给 CountDownLatch 设 置 ⼀ 个 数 字 , ⼀ 个 线 程 调 ⽤
CountDownLatch 的 await()将会阻塞,其他线程可以调⽤CountDownLatch 的 countDown()⽅
法来对 CountDownLatch 中的数字减⼀,当数字被减成 0 后,所有 await 的线程都将被唤
醒。 对应的底层原理就是,调⽤await()⽅法的线程会利⽤AQS 排队,⼀旦数字被减为 0,
则会将 AQS 中 排队的线程依次唤醒。 Semaphore 表示信号量,可以设置许可的个数,表
示同时允许最多多少个线程使⽤该信号量,通 过 acquire()来获取许可,如果没有许可可⽤
则线程阻塞,并通过 AQS 来排队,可以通过 release() ⽅法来释放许可,当某个线程释放了
某个许可后,会从 AQS 中正在排队的第⼀个线程开始依次唤 醒,直到没有空闲许可。
ReentrantLock 中 tryLock()和 lock()⽅法的区别
1. tryLock()表示尝试加锁,可能加到,也可能加不到,该⽅法不会阻塞线程,如果加到锁
则返回 true,没有加到则返回 false 2. lock()表示阻塞加锁,线程会阻塞直到加到锁,⽅法也
没有返回值
ReentrantLock 中的公平锁和⾮公平锁的底层实现
⾸先不管是公平锁和⾮公平锁,它们的底层实现都会使⽤AQS 来进⾏排队,它们的区别在
于:线程在使 ⽤lock()⽅法加锁时,如果是公平锁,会先检查 AQS 队列中是否存在线程在
排队,如果有线程在排队, 则当前线程也进⾏排队,如果是⾮公平锁,则不会去检查是否
有线程在排队,⽽是直接竞争锁。 不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进
⾏排队,当锁释放时,都是唤醒排在最前⾯的线 程,所以⾮公平锁只是体现在了线程加锁
阶段,⽽没有体现在线程被唤醒阶段。 另外,ReentrantLock 是可重⼊锁,不管是公平锁还
是 ⾮ 公 平 锁 都 是 可 重 ⼊ 的 。 【 图 片 地 址 :
20210810/e8a3b10c37048b765be1d7657c21cf46.png 】 【 图 片 地 址 :
20210810/cc7e34f06563774e6ce844279fcea447.png 】 【 图 片 地 址 :
20210810/c7f3c8214687b9eb6262118bf672bc16.png 】 【 图 片 地 址 :
20210810/1508859af889e55893cea4fc561386a5.png】
线程池中线程复⽤原理
线程池将线程和任务进⾏解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线
程时的⼀个 线程必须对应⼀个任务的限制。 在线程池中,同⼀个线程可以从阻塞队列中不
断获取新任务来执⾏,其核⼼原理在于线程池对 Thread 进⾏了封装,并不是每次执⾏任务
都会调⽤ Thread.start() 来创建新线程,⽽是让每个线程去执⾏⼀ 个“循环任务”,在这个“循
环任务”中不停检查是否有任务需要被执⾏,如果有则直接执⾏,也就是调⽤ 任务中的 run
⽅法,将 run ⽅法当成⼀个普通的⽅法执⾏,通过这种⽅式只使⽤固定的线程就将所有任
务的 run ⽅法串联起来。
线程池中阻塞队列的作⽤?为什么是先添加列队⽽不是先创建
最 ⼤线程?
1、⼀般的队列只能保证作为⼀个有限⻓度的缓冲区,如果超出了缓冲⻓度,就⽆法保留当
前的任务了, 阻塞队列通过阻塞可以保留住当前想要继续⼊队的任务。 阻塞队列可以保证
任务队列中没有任务时阻塞获取任务的线程,使得线程进⼊wait 状态,释放 cpu 资 源。 阻
塞队列⾃带阻塞和唤醒的功能,不需要额外处理,⽆任务执⾏时,线程池利⽤阻塞队列的
take⽅法挂 起,从⽽维持核⼼线程的存活、不⾄于⼀直占⽤cpu 资源 2、在创建新线程的时
候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。 就好⽐⼀个企业⾥
⾯有 10 个(core)正式⼯的名额,最多招 10 个正式⼯,要是任务超过正式⼯⼈数 (task >
core)的情况下,⼯⼚领导(线程池)不是⾸先扩招⼯⼈,还是这 10⼈,但是任务可以稍
微积 压⼀下,即先放到队列去(代价低)。10 个正式⼯慢慢⼲,迟早会⼲完的,要是任务
还在继续增加,超 过正式⼯的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是
临时⼯)要是正式⼯加上外包还 是不能完成任务,那新来的任务就会被领导拒绝了(线程
池的拒绝策略)。
线程池的底层⼯作原理
线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时: 1. 如果此时线程池
中的线程数量⼩于 corePoolSize,即使线程池中的线程都处于空闲状态,也要创建 新的线
程来处理被添加的任务。 2. 如果此时线程池中的线程数量等于 corePoolSize,但是缓冲队
列 workQueue 未满,那么任务被放⼊ 缓冲队列。 3. 如果此时线程池中的线程数量⼤于等
于 corePoolSize,缓冲队列 workQueue 满,并且线程池中的数 量⼩于 maximumPoolSize,
建新的线程来处理被添加的任务。 4. 如果此时线程池中的线程数量⼤于 corePoolSize,缓
冲队列 workQueue 满,并且线程池中的数量等 于 maximumPoolSize,那么通过 handler 所
指定的策略来处理此任务。 5. 当线程池中的线程数量⼤于 corePoolSize 时,如果某线程空
闲时间超过 keepAliveTime,线程将被 终⽌。这样,线程池可以动态的调整池中的线程数
为什么⽤线程池?解释下线程池参数?
1、降低资源消耗;提⾼线程利⽤率,降低创建和销毁线程的消耗。 2、提⾼响应速度;任
务来了,直接有线程可⽤可执⾏,⽽不是先创建线程,再执⾏。 3、提⾼线程的可管理
性;线程是稀缺资源,使⽤线程池可以统⼀分配调优监控。 corePoolSize 代表核⼼线程
数,也就是正常情况下创建⼯作的线程数,这些线程创建后并不会 消除,⽽是⼀种常驻线
程 maxinumPoolSize 代表的是最⼤线程数,它与核⼼线程数相对应,表示最⼤允许被创建
的线程 数,⽐如当前任务较多,将核⼼线程数都⽤完了,还⽆法满⾜需求时,此时就会创
建新的线程,但 是线程池内线程总数不会超过最⼤线程数 keepAliveTime 、 unit 表示超出
核⼼线程数之外的线程的空闲存活时间,也就是核⼼线程不 会消除,但是超出核⼼线程数
的部分线程如果空闲⼀定的时间则会被消除,我们可以通过 setKeepAliveTime 来设置空闲时
间 workQueue ⽤来存放待执⾏的任务,假设我们现在核⼼线程都已被使⽤,还有任务进来
则全部 放⼊队列, 直到整个队列被放满但任务还再持续进⼊ 则会开始创 建新的线程
ThreadFactory 实际上是⼀个线程⼯⼚,⽤来⽣产线程执⾏任务。我们可以选择使⽤默认的
创 建⼯⼚,产⽣的线程都在同⼀个组内,拥有相同的优先级,且都不是守护线程。当然我
们也可以选 择⾃定义线程⼯⼚,⼀般我们会根据业务来制定不同的线程⼯⼚ Handler 任务
拒绝策略,有两种情况,第⼀种是当我们调⽤ shutdown 等⽅法关闭线程池后, 这时候即
使线程池内部还有没执⾏完的任务正在执⾏,但是由于线程池已经关闭,我们再继续想线
程池提交任务就会遭到拒绝。另⼀种情况就是当达到最⼤线程数,线程池已经没有能⼒继
续处理新 提交的任务时,这是也就拒绝
如何理解 volatile 关键字
保证被 volatile 修饰的共享变量对所有线程总是可⻅的,也就是当⼀个线程修改了⼀个被
volatile 修饰共 享变量的值,新值总是可以被其他线程⽴即得知。 如果线程 2 改变了 stop
的值,线程 1⼀定会停⽌吗?不⼀定。当线程 2 更改了 stop 变量的值之后,但是还 没来得
及写⼊主存当中,线程 2 转去做其他事情了,那么线程 1 由于不知道线程 2 对 stop 变量的
更 改 , 因 此 还 会 ⼀ 直 循 环 下 去 。 【 图 片 地 址 :
20210810/bc18d42e95042bc173df7dda71fdb118.png】 禁⽌指令重排序优化 【图片地址:
20210810/a75313e8cbc2f64b23be65ec6cda2432.png】 write⽅法⾥的 1 和 2 做了重排序,线
程 1 先对 flag 赋值为 true,随后执⾏到线程 2,ret 直接计算出结果,再 到线程 1,这时候
a 才赋值为 2,很明显迟了⼀步。但是⽤volatile 修饰之后就变得不⼀样了: 1. 使⽤volatile 关
键字会强制将修改的值⽴即写⼊主存; 2. 使⽤volatile 关键字的话,当线程 2 进⾏修改时,
会导致线程 1 的⼯作内存中缓存变量 stop 的缓存⾏⽆ 效(反映到硬件层的话,就是 CPU
的 L1 或者 L2 缓存中对应的缓存⾏⽆效); 3. 由于线程 1 的⼯作内存中缓存变量 stop 的缓
存⾏⽆效,所以线程 1 再次读取变量 stop 的值时会去主存 读取。 inc++; 其实是两个步骤,
先加加,然后再赋值。不是原⼦性操作,所以 volatile 不能保证线程安全。
Java 死锁如何避免?
造成死锁的⼏个原因: 1. ⼀个资源每次只能被⼀个线程使⽤ 2. ⼀个线程在阻塞等待某个资
源时,不释放已占有资源 3. ⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺
4. 若⼲线程形成头尾相接的循环等待资源关系 这是造成死锁必须要达到的 4 个条件,如果
要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前 3 个条件是作为锁要符合的条
件,所以要避免死锁就需要打破第 4 个条件,不出现循环等待锁的关系。 在开发过程中:
1. 要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁 2. 要注意加锁时限,可以针对所
剩余231页未读,继续阅读
资源评论
青青♡
- 粉丝: 1
- 资源: 3
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功