跳至主要內容
JVM故障实践之GC篇

案例: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 visualVMMAT工具分析 用visualVM分析dump文件

holic-x...大约 13 分钟JAVA基础
⑥JVM JVM故障问题排查

学习核心

  • 内存泄漏问题排查
    • GC引起CPU飙升问题排查(5个步骤一步步定位)
    • 现场保留(瞬时信息和历史信息的记录保留,后续进行跟踪分析)
      • 常用工具:jinfo、jstat、jstack、jhsdb(jmap)等工具,尤其是jmap(分析处理内存泄漏的利器)
    • 一个卡顿实例分析(结合卡顿实例,掌握分析思路和实践过程)
      • 高并发业务场景中启用swap造成的服务卡顿
    • 内存泄漏 VS 内存溢出
      • 区分内存泄漏(原因)和内存溢出(结果)概念
      • 内存泄漏现象
      • 内存泄漏案例(HashMap使用不当、文件操作资源未及时释放、JavaAPI使用不当、ThrealLocal使用误区)

holic-x...大约 58 分钟JAVA基础
JAVA多线程代码题

学习核心

  • 掌握高频问题

    • ① 双线程 轮流打印 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
    • ② 多线程交替打印字母、数字

      • 核心思路:分别定义两个计数器(字母计数器、数字计数器)和打印标识(mark用于控制哪个线程可执行操作),然后参考上述思路实现交替打印操作
        • 需要理解每个计数器需要打印到什么位置(监听范围、监听打印标识)
      • 数字和字母转化:将数字转化为字母可以用Character.toChars('a' + 0)标识将数字0变成a,基于此可以通过计数器形式完成字母递增,只需要在打印的时候转化一下即可(还有一种方式是定义数组存储字母集,然后通过下标数字索引来与字母对照)
      • 常见题型:
        • a1b2c3d4....z26(字母数字交替打印)
        • a1b2c3d4a1b2c3d4.....(abcd、1234交替打印,打印10个轮次)
        • AbCdEf....(字母大小写交替打印)
    • ③ **如何控制多线程场景下任务的顺序执行?**例如要实现t1->t2->t3的线程任务的执行顺序

      • 思路1:纯join方式,每个线程严格遵循startjoin的执行组合,当前面的任务执行完成才开启下一个组合
      • 思路2:join + CountDownLatch
        • join:此处join用于确保所有子线程都执行完成,用于阻塞主线程(只有当所有任务执行完成,主线程才继续往下)
        • CountDownLatchCountDownLatch用于同步控制,定义两个计数器(初始化为1)分别用于控制t1->t2t2->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才可以继续执行
    • 模拟窗口卖票(开设4个窗口进行售票,如何确保正常售票?)

      • 分析:每个线程是1个购票窗口,模拟购票操作
      • 思路1:synchronized:对共享资源进行加锁
        • 特点:简单易用,适用于简单的线程同步场景
      • 思路2:AtomicInteger:用AtomicInteger保证余票的线程安全
        • 特点:无锁实现,性能较高
      • 思路3:ReentrantLock 可重入锁(lockunlock
        • 特点:更为灵活,支持公平锁和非公平锁
      • 思路4:Semaphore信号量
        • 特点:适合控制并发访问的场景

holic-x...大约 47 分钟JAVA基础
④JVM 类加载机制

学习核心

  • 类的生命周期
    • 掌握类的生命周期核心(每个节点的作用)
    • 结合案例理解类的生命周期构建流程
    • 自定义类加载器(自定义类加载器实现、场景应用、常见自定义加载器(一些破坏双亲委派的场景))
  • 类加载器和类加载机制
    • 类加载器(分类)
    • 双亲委派机制
    • JDK9之后类的加载委派关系
  • JVM优化-降低类加载开销
  • 类加载机制之开发常见实用技巧

holic-x...大约 30 分钟JAVA基础
①JVM JAVA内存区域

学习核心

  • JVM内存区域

    • JVM 核心概念
    • 内存结构图示和各个部件的作用
      • 宏观划分:线程栈、堆内存,以及各自的分类和实现
      • 不同JVM实现(不同厂商或者同一厂商的不同版本的变化)
    • JMM核心:规范线程之间的交互操作
    • 扩展概念:重排序、内存屏障
  • 结合内存区域的划分,分析OOM的可能性分别对应哪个内存区域的异常状况


holic-x...大约 38 分钟JAVA基础
②JVM 垃圾收集器和内存分配策略

学习核心

  • 对象已死
    • JAVA中的引用
    • 可达性分析算法
  • 垃圾收集算法
    • 垃圾回收算法
      • 标记-清除算法
      • 标记-整理算法
      • 复制
      • 分代收集
  • 垃圾收集器
    • Serial收集器
    • ParNew收集器
    • Parallel Scavenge收集器
    • Serial Old收集器
    • Parallel Old收集器
    • CMS收集器
    • G1收集器
  • 内存分配和回收策略

holic-x...大约 35 分钟JAVA基础
⑥JAVA 线程池和Exector框架

学习核心

  • JAVA线程池
    • 线程池原理、为什么引入线程池
    • 线程池的核心参数
  • Executor框架
    • 两级调度模型
    • 框架结构:核心API
    • 框架成员
  • ThreadPoolExecutor详解
  • 性能优化
    • 为什么建议用ThreadPoolExecutor自定义线程池而不使用默认提供的方法
    • 如何合理分配线程池大小
    • 线程池如何实现动态修改
    • 使用无界队列的线程池会导致什么问题?

holic-x...大约 40 分钟JAVA基础