如何通过jvm解决生产问题的实战总结

目录

jvm的基础知识

内存模型

程序计数器

java栈

本地方法栈

方法区

对象大小计算

对象结构大小

对象头

对其补充

线程模型

ThreadPoolExecutor创建线程池

GC详解

基础工具

jvm案例排查讲解



一般情况下,我们生产环境中所遇到的bug或问题基本可以分为四类:

  1. 第一类是比较简单的bug,一般日志会有错误堆栈,或者异常信息,这种基本都可以通过远程debug或者代码推理找到具体原因从而解决问题。
  2. 第二类是稍微难一点的bug,一般与集成的第三方jar包有关,比如spring的网关,ribbon,数据库连接池,fastjson等,基本可以通过源码定位问题。
  3. 第三类是难一点的bug,一般要求研发人员具有丰富的排查和解决问题的能力,程序性能问题、中间件,操作系统资源相关的问题,这类问题一般可通过jvm排查程序问题,操作系统命令查看系统资源,及排查相关中间件问题。
  4. 第四类是困难的,基本需要长期解决的,一般需要专业的经验丰富的研发人员,基本属于中间件的问题,比如hbase,es,kafka等。一般这类问题是比较有规律的出现,此规律可能与时间有关,可能与空间有关。

这篇文章主要讲述第三类的问题的jvm排查程序,通过学习了解jvm的基本构成及工具来达到解决此类问题的能力。

jvm的基础知识

下面讲解的版本是jdk1.8。

内存模型

                                              

程序计数器

我们知道对于一个处理器(如果是多核cpu那就是一核),在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。

java栈

每个方法被执行的时候都会创建栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息,每一个方法被调用的过程,就对应一个栈帧在虚拟机中从入栈到出栈的过程。

Java虚拟机栈可能出现两种类型的异常:

  1. 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
  2. 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。

存储new出来的对象,数组及字符串常量池,垃圾回收的主战场。

本地方法栈

本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++

方法区

又称非堆,线程共享区域,用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。

对象大小计算

在64位的操作系统中,开启指针压缩后,一个空对象占16Byte,真大a

                                                    

对象结构大小

名称(单位byte) 32位 64位 开启指针压缩后(指针对64位有效且默认开启)
对象头(Header) 8 16 12
数组对象头 12 24 16
引用(reference) 4 8 4

对象头

  1. Mark Word记录了对象和锁有关的信息
  2. 指向类的指针,Java对象的类数据保存在方法区
  3. 数组长度,仅在数组对象中存在,该数据在32位和64位JVM中长度都是32bit。

对其补充

第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

线程模型

ThreadPoolExecutor创建线程池

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

任务执行策略


用于传输和保存等待执行任务的阻塞队列。可以使用此队列与线程池进行交互: 
如果运行的线程数少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
如果运行的线程数等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。


线程饱和策略

当线程池和队列都满了,则表明该线程池已达饱和状态。 
ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常RejectedExecutionException。(默认策略)
ThreadPoolExecutor.CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
ThreadPoolExecutor.DiscardPolicy:无法执行的任务将被删除。
ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。

四种线程池对比

线程池方法 初始化线程池数 最大线程池数 线程存活时间 时间单位 工作队列
newCachedThreadPool 0 Integer.MAX_VALUE 60 s SynchronousQueue
newFixedThreadPool 入参指定大小 入参指定大小 0 ms LinkedBlockingQueue
newScheduledThreadPool 入参指定大小 Integer.MAX_VALUE 0 微秒 DelayedWorkQueue
newSingleThreadExecutor 1 1 0 ms LinkedBlockingQueue

GC详解

垃圾回收算法

  • 标记清理算法
  • 复制算法
  • 标记整理算法
  • 分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块并采用不用的垃圾收集算法。

一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

垃圾收集器

  1. serial(串行)收集器

         它的“单线程”的意义并不仅仅说明它只会使用一个 CPU 或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

     2.ParNew(串行)收集器

         serial(串行)收集器的多线程版本。

      3.Parallel Scavenge收集器

Parallel Scavenge 收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。

      4.Serial Old 收集器

      5.Parallel Old收集器

      6.CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。

      7.G1收集器

基础命令

  1. top:查看资源占用
  2. top -Hp pid:查看某一个进程的线程资源使用情况
  3. jps:查看java的进程信息
  4. jstack:查看java进程的堆栈信息
  5. jmap:  查看java的堆内存使用情况,及每个类占用字节
  6. jstat:  查看垃圾回收统计及其他
  7. free:查看linux的内存使用
  8. sar -n DEV 1 2:查看网卡流量
  9. iostat:查看磁盘读写情况
  10. jconsole:jvm桌面控制台
  11. jvisualvm:jvm桌面控制台
  12. df -Th:查看磁盘使用率

jvm案例排查讲解

  1. java大对象引发的频繁full gc及cpu飙高的调优历程
  2. phoenix整合springboot采用druid作为连接池一点时间后报Connection is null or closed

  3. oracle连接卡住,导致程序不运行

  4. hbase客户端认证缓慢解决

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章