...大约 38 分钟
案例:ThreadLocal使用不当,导致内存泄漏,进而触发OOM
1.场景分析
故障问题排查说明
针对CPU飙升问题,可按照相关步骤进行排查,如果确认是由于线程大量GC导致的CPU飙升问题,则可进一步进入GC问题排查
| 步骤 | 指令 | 说明 |
|---|---|---|
| 1 | top |
查看占用cpu高的进程信息 |
| 2 | jstat -gcutil pid |
查看gc状况 |
| 3 | jmap -dump:format=b,file=name.dump pid |
导出dump文件 |
| 4 | visualVM、MAT工具分析 |
用visualVM分析dump文件 |
...大约 13 分钟
学习核心
- 内存泄漏问题排查
- GC引起CPU飙升问题排查(5个步骤一步步定位)
- 现场保留(瞬时信息和历史信息的记录保留,后续进行跟踪分析)
- 常用工具:jinfo、jstat、jstack、jhsdb(jmap)等工具,尤其是jmap(分析处理内存泄漏的利器)
- 一个卡顿实例分析(结合卡顿实例,掌握分析思路和实践过程)
- 高并发业务场景中启用swap造成的服务卡顿
- 内存泄漏 VS 内存溢出
- 区分内存泄漏(原因)和内存溢出(结果)概念
- 内存泄漏现象
- 内存泄漏案例(HashMap使用不当、文件操作资源未及时释放、JavaAPI使用不当、ThrealLocal使用误区)
...大约 58 分钟
学习核心
-
掌握高频问题:
- ① 双线程 轮流打印 1-100
- ② 单例模式
- ③ 生产者-消费者模式
-
额外扩展(关注LeetCode代码题目)
-
常见题型分类
-
① 多线程轮流打印数字、多线程分奇偶打印
- 思路1**(锁+条件变量)**:
synchronized(对象锁、wait/notifyAll) +counter(全局计数器)+mark(全局打印标识,标记目前轮到哪个线程的打印轮次) - 思路2**(基于信号量实现互斥锁)**:基于
counter(全局计数器) +Semaphore信号量实现互斥锁(一共仅有1个许可证,需要通过"传递"的方式处理保持互斥)- 每个线程有自己独立的
Semaphore对象(例如线程A向s1申请许可证,如果申请成功(s1许可证-1)则处理+1,然后传递给下一个(s2执行release操作,s2中许可证+1),依次类推,确保许可证始终只有1个可用)
- 每个线程有自己独立的
- 思路3**(借助
LockSupport完成线程的阻塞和唤醒)**:LockSupport是一个线程操作工具类(park阻塞当前线程,unpack唤醒指定线程)- 对于线程A:A先执行,唤醒B,随后阻塞A等待其他线程的唤醒
- 对于线程B:阻塞B(等待A的唤醒),执行操作,唤醒A
- 思路4(基于
AtomicInteger和自旋锁(while循环)) - 思路5(
BlockingQueue):定义两个BlockingQueue,一个队列取出任务执行后,将下一个任务加入另一个队列,交替执行 - 思路6(
ReentrantLock+Condition)
- 思路1**(锁+条件变量)**:
-
② 多线程交替打印字母、数字
- 核心思路:分别定义两个计数器(字母计数器、数字计数器)和打印标识(
mark用于控制哪个线程可执行操作),然后参考上述思路实现交替打印操作- 需要理解每个计数器需要打印到什么位置(监听范围、监听打印标识)
- 数字和字母转化:将数字转化为字母可以用
Character.toChars('a' + 0)标识将数字0变成a,基于此可以通过计数器形式完成字母递增,只需要在打印的时候转化一下即可(还有一种方式是定义数组存储字母集,然后通过下标数字索引来与字母对照) - 常见题型:
a1b2c3d4....z26(字母数字交替打印)a1b2c3d4a1b2c3d4.....(abcd、1234交替打印,打印10个轮次)AbCdEf....(字母大小写交替打印)
- 核心思路:分别定义两个计数器(字母计数器、数字计数器)和打印标识(
-
③ **如何控制多线程场景下任务的顺序执行?**例如要实现
t1->t2->t3的线程任务的执行顺序- 思路1:纯
join方式,每个线程严格遵循start、join的执行组合,当前面的任务执行完成才开启下一个组合 - 思路2:
join+CountDownLatchjoin:此处join用于确保所有子线程都执行完成,用于阻塞主线程(只有当所有任务执行完成,主线程才继续往下)CountDownLatch:CountDownLatch用于同步控制,定义两个计数器(初始化为1)分别用于控制t1->t2、t2->t3的同步- 当
cdl减少为0才能执行,否则会阻塞当前线程,因此其同步控制分析如下- 当任务
t1执行完成,cdl_1减1(执行countDown) - 在任务
t2执行前调用cdl_1.await()方法(只有当计数器减为0才继续执行),即当上述任务t1完成后t2就可以执行了。当t2执行完成,cdl_2减1 - 同理,在任务
t3执行前调用cdl_2.await()方法,即只有t2执行完成后cdl_2计数器减为0后t3才可以继续执行
- 当任务
- 当
- 思路1:纯
-
④ 模拟窗口卖票(开设4个窗口进行售票,如何确保正常售票?)
- 分析:每个线程是1个购票窗口,模拟购票操作
- 思路1:
synchronized:对共享资源进行加锁- 特点:简单易用,适用于简单的线程同步场景
- 思路2:
AtomicInteger:用AtomicInteger保证余票的线程安全- 特点:无锁实现,性能较高
- 思路3:
ReentrantLock可重入锁(lock、unlock)- 特点:更为灵活,支持公平锁和非公平锁
- 思路4:
Semaphore信号量- 特点:适合控制并发访问的场景
-
...大约 47 分钟
...大约 17 分钟
...大约 10 分钟
学习核心
- 类的生命周期
- 掌握类的生命周期核心(每个节点的作用)
- 结合案例理解类的生命周期构建流程
- 自定义类加载器(自定义类加载器实现、场景应用、常见自定义加载器(一些破坏双亲委派的场景))
- 类加载器和类加载机制
- 类加载器(分类)
- 双亲委派机制
- JDK9之后类的加载委派关系
- JVM优化-降低类加载开销
- 类加载机制之开发常见实用技巧
...大约 30 分钟
学习核心
-
JVM内存区域
- JVM 核心概念
- 内存结构图示和各个部件的作用
- 宏观划分:线程栈、堆内存,以及各自的分类和实现
- 不同JVM实现(不同厂商或者同一厂商的不同版本的变化)
- JMM核心:规范线程之间的交互操作
- 扩展概念:重排序、内存屏障
-
结合内存区域的划分,分析OOM的可能性分别对应哪个内存区域的异常状况
...大约 38 分钟
学习核心
- 对象已死
- JAVA中的引用
- 可达性分析算法
- 垃圾收集算法
- 垃圾回收算法
- 标记-清除算法
- 标记-整理算法
- 复制
- 分代收集
- 垃圾回收算法
- 垃圾收集器
- Serial收集器
- ParNew收集器
- Parallel Scavenge收集器
- Serial Old收集器
- Parallel Old收集器
- CMS收集器
- G1收集器
- 内存分配和回收策略
...大约 35 分钟
学习核心
- JAVA线程池
- 线程池原理、为什么引入线程池
- 线程池的核心参数
- Executor框架
- 两级调度模型
- 框架结构:核心API
- 框架成员
- ThreadPoolExecutor详解
- 性能优化
- 为什么建议用ThreadPoolExecutor自定义线程池而不使用默认提供的方法
- 如何合理分配线程池大小
- 线程池如何实现动态修改
- 使用无界队列的线程池会导致什么问题?
...大约 40 分钟
