深入理解JVM(二)

深入理解JVM(二)

GC

我们都知道Java中是有垃圾回收机制会自动帮我们回收内存, 以让程序员将精力放在业务上.

而C, C++没有垃圾回收机制, 只能手动回收垃圾,这就有可能导致忘记回收或者多次回收的问题.

什么是垃圾

在上图中, 堆中有两块内存, 一块有引用指向, 一块没有引用指向.

没有引用指向的那块内存就被称为垃圾.

如何找到垃圾

引用计数法

每有一个引用就会将这块内存的计数加1, 当不再引用的时候将这个计数减1.

当计数为0的时候,就代表这块已经是垃圾了.

但是引用计数法有个缺点.

如果两块内存区域互相引用, 这就导致这两个计数都为1, 但是没有其他引用指向他们, 所以即使他们引用计数不为0, 但是他们仍是垃圾, 需要被回收.

根可达算法

从GC Root来算, 可以到达的对象为存活对象, 无法到达的对象就是垃圾.

之前我们聊过java运行时, 存在线程栈, 栈中方法执行的方法都是一个个的栈帧. 栈帧中的对象就是GC Root, 他们指向的对象都不会被回收.

哪些是GC Root

  • 栈中的对象
  • 本地方法栈中的对象
  • 运行时常量池
  • 静态变量
  • class

GC算法

复制

复制就是将存活的对象拷贝到另一块内存, 然后将整块内存全部清空.

适用于存活对象较少的情况, 只扫描一次, 效率较高, 没有碎片.

但是这个算法需要两倍的空间, 比较耗费空间.

而且移动复制对象, 需要调整对象引用.

标记清除

标记清除就是寻找存活的对象,将垃圾标记出来, 然后清除.

算法较简单,在存活对象比较多的情况下效率较高.

两遍扫描, 效率较低. 容易产生碎片.

标记整理

标记整理就是将存活的对象向前移动, 使用原来垃圾占用的空间.

这样就不会产生内存碎片, 方便新对象空间的分配, 也不会需要两倍的空间.

但是这种算法需要扫描两次, 同时也需要移动空间, 效率较低.

堆内存逻辑分区

堆空间逻辑上分为年轻代和老年代,大概可以按1:3的比例, 年轻代占1, 老年代占3.

年轻代主要存放新创建出来的对象, 死亡较多, 存活较少.

老年代存放的对象都是年轻代中经过多次GC仍存活的对象.

年轻代又分为eden区和servivor区, eden区存放新建的对象, servivor又分为s0,s1(也可以叫from, to), 用来使用复制算法.

栈上分配

  • 线程私有小对象
  • 无逃逸 仅在这块方法中使用, 出了这个方法就没有用了.
  • 支持标量替换 如果一个类如T, 只有int m, int n. 可以用两个变量替换这个对象.
  • 无需调整

线程本地分配 TLAB (Thread Local Allocation Buffer)

  • 占用eden, 默认1%
  • 多线程的时候不用竞争eden就可以申请空间, 提高效率
  • 小对象
  • 无需调整

对象何时进入老年代

  • 超过 XX:MaxTenuringThreshold 指定次数 (YGC)

    • Parallen Scavenge 15
    • CMS 6
    • G1 15
  • 动态年龄

    • s1 -> s2 拷贝时超过50%
    • 把年龄最大的放入老年区

常见垃圾回收器

  1. Serial

    STW, 当线程到达安全点(safe point)的时候停止.

    以单线程回收.

  2. Serial Old

  3. Parallel Old

    多个线程同时回收.

  4. Parallel Scavenge

  5. ParNew

    Parallel Scavenge的一个变种, 做了一些增强和CMS配合使用.

  6. CMS

    垃圾回收的线程和工作线程同时进行.

    并发的垃圾回收是因为无法忍受STW.

    初始标记 -> 并发标记 -> 重新标记 -> 并发清理

    初始标记会标记一些最开始的对象, 会产生STW

    并发标记会顺着初始标记的结果继续标记, 和工作线程同时继续.

    重新标记会标记在并发标记这一阶段产生的新垃圾, 也会产生STW

    并发清理就是垃圾回收线程和工作线程同时工作,清理掉前面阶段标记的垃圾.

    算法: 三色标记+ incremental Update

  7. G1

    算法: 三色标记+SATB

  8. ZGC

    算法:ColoredPointer + 读屏障

CMS的缺点

浮动垃圾

在CMS回收的最后一个阶段, 并发清理的时候, 因为工作线程没有停止, 也可能会产生垃圾.

对于这个阶段产生的垃圾, CMS无法处理, 只能等到下一次垃圾回收的时候再做处理.

内存碎片

因此CMS是标记清除算法, 所以会产生内存碎片.

可以指定 -XX:CMSFullGCsBeforeCompaction来指定经过多少次FGC再做一次内存整理.

当碎片太多的时候, 新的空间已经不够用了, 此时CMS会使用使用一个线程来做标记整理.

因为浮动垃圾的问题, CMS必须要在堆内存预留一定的内存留给浮动垃圾.

-XX:CMSInitiatingOccupancyFraction 92% java8时,在老年代装满92%的时候会触发FGC.

可以通过减少这个数值来防止堆内存被堆满的情况.

常见垃圾回收器组合参数设定 : (1.8)

  • -XX:+UseSerialGC = Serial New(DefNew) + Serial Old

    • 小型程序. 默认情况下不会是这种选项, HotSpot会根据计算及配置和JDK版本自动选择收集器
  • -XX:+UseParNewGC = ParNew + SerialOld

    • 这个自合已经很少用(在某些版本中已经废弃)
  • -XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old

  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) [PS+SerialOld]

  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old

  • -XX:+UseG1GC = G1

  • Linux中没找到默认GC的查看方法, 而windows中会打印UseParallelGC

    • java +XX:+PrintCommandLineFlags -version
    • 通过GC的日志来分辨
  • Linux下1.8版本默认的垃圾回收器到底是什么?

    • 1.8.0_181 默认(看不出来) Copy MarkCompact
      1. 8.0_222 默认 PS+PO

JVM调优

了解JVM参数

内存泄露和内存溢出

内存泄露 memory leak

内存溢出 out of memory

内存泄露的意思是, 分配了一块内存, 然后一直在占用. 这块内存再也用不了了, 也没有回收.

内存溢出的意思是, 产生了太多的对象, 内存撑不住了.

java -XX:+PrintFlagsFinal -version | grep CMS

  • JVM的命令行参数参考
public class Test01 {

    public static void main(String[] args) {
        System.out.println("Hello GC");
        List list = new LinkedList<>();
        for (;;){
            byte[] b = new byte[1024 * 1024];
            list.add(b);
        }
    }
}

根据一段代码看JVM参数

-XX:+PrintCommandLineFlags

-XX:InitialHeapSize=132847360 -XX:MaxHeapSize=2125557760 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
Hello GC
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.echi.jvm.day02.Test01.main(Test01.java:16)

加上-XX:+PrintCommandLineFlags后会输出这么一段. 其中:

  • InitialHeapSize 起始堆大小
  • MaxHeapSize 最大堆大小
  • UseCompressedClassPointers 压缩类对象指针
  • UseCompressedOops 压缩普通对象指针

指定堆内存各区大小

  • -Xmn10M 指定新生代大小最大10m
  • -Xms40M 指定老年代最小40m
  • -Xmx60M 指定老年代最大60m

-XX:+PrintGC

打印GC信息

  • -XX:+PrintGCDetails 打印GC更详细信息
  • -XX:+PrintGCTimeStamps 打印GC的系统时间
  • -XX:+PrintGCCauses 打印GC产生的原因
-XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
Hello GC
[GC (Allocation Failure)  7436K->5968K(39936K), 0.0032245 secs]
[GC (Allocation Failure)  13292K->13088K(39936K), 0.0067339 secs]
[GC (Allocation Failure)  20570K->20208K(39936K), 0.0056591 secs]
[GC (Allocation Failure)  27528K->27360K(39936K), 0.0037826 secs]
[Full GC (Ergonomics)  27360K->27319K(54272K), 0.0156873 secs]
[GC (Allocation Failure)  34643K->34583K(53760K), 0.0027736 secs]
[GC (Allocation Failure)  41900K->41751K(53760K), 0.0025903 secs]
[Full GC (Ergonomics)  41751K->41656K(59904K), 0.0127951 secs]
[GC (Allocation Failure)  47934K->47896K(59904K), 0.0021653 secs]
[Full GC (Ergonomics)  47896K->47800K(59904K), 0.0059815 secs]
[Full GC (Ergonomics)  54072K->53944K(59904K), 0.0132849 secs]
[Full GC (Ergonomics)  57140K->57016K(59904K), 0.0027407 secs]
[Full GC (Allocation Failure)  57016K->56998K(59904K), 0.0326752 secs]

[GC (Allocation Failure) 7436K->5968K(39936K), 0.0032245 secs]

产生了YGC(因为申请空间失败), 这次GC的空间从xxx变为xxx, 耗时

-XX:+UseConcMarkSweepGC

使用CMS垃圾回收器

[GC (Allocation Failure)  7436K->5854K(39936K), 0.0031520 secs]
[GC (Allocation Failure)  13262K->13526K(39936K), 0.0055059 secs]
[GC (Allocation Failure)  20899K->20244K(39936K), 0.0024109 secs]
[GC (CMS Initial Mark)  21268K(39936K), 0.0001118 secs]
[GC (Allocation Failure)  27565K->27365K(39936K), 0.0026108 secs]
[GC (Allocation Failure)  34689K->34510K(45076K), 0.0033197 secs]
[GC (Allocation Failure)  41835K->41677K(52272K), 0.0053983 secs]
[GC (Allocation Failure)  49003K->48845K(59468K), 0.0031548 secs]
[Full GC (Allocation Failure)  56172K->55998K(59468K), 0.0124776 secs]
[Full GC (Allocation Failure)  58210K->58045K(60416K), 0.0124974 secs]
[Full GC (Allocation Failure)  58045K->58027K(60416K), 0.0103527 secs]
[GC (CMS Initial Mark)  58027K(60416K), 0.0001122 secs]

可以看到, 出现了CMS Initial Mark, 之前我们提过的初始标记阶段.

-XX:+PrintGCDetails

[GC (Allocation Failure) [ParNew: 7436K->714K(9216K), 0.0030391 secs] 7436K->5839K(39936K), 0.0030936 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

调优前的基础概念

  1. 吞吐量 : 用户代码时间 / (用户diam执行时间 + 垃圾回收时间)

  2. 响应时间: STW越短, 响应时间越好

吞吐量优先的, 如科学计算, 数据挖掘, 选择 PS + PO

响应时间优先的, 比如网站, GUI, API, 1.8 选择G1, 之前 PN + CMS

什么是调优

  1. 根据需求进行JVM规划和预调优
  2. 优化运行JVM运行环境
  3. 解决JVM运行过程中出现的各种问题

调优, 从规划开始

  • 调优, 从业务场景开始

  • 无监控(压测, 能看到结果), 不调优

  • 步骤:

    1. 熟悉业务场景 (没有最好的垃圾回收器, 只有最合适的垃圾回收器)
      1. 响应时间, 停顿时间 (CMS G1 ZGC) (需要给用户做响应)
      2. 吞吐量 = 用户时间 / (用户时间 + GC时间)
    2. 选择回收器组合
    3. 计算内存需求
    4. 选定CPU (越高越好)
    5. 设定年代大小, 升级年龄
    6. 设定日志参数
      1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:UseGcLogFileRotation -XX:NumberOfGCLogFile=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
      2. 或者每天产生一个日志文件
    7. 观察日志情况
  • 案例1: 垂直电商, 最高每日百万订单, 处理订单系统需要什么样的服务器配置

    找高峰, 如果 某日有个时间段比如2小时内卖出80w, 那么每秒平均下来就是110单.

    估计计算一个订单产生需要多少内存.

    比如一个订单从开始到结束需要512k, 512k*100 = 50M

  • 案例2: 12306遭遇春节大规模抢票应该如何支撑?

    12306应该是中国并发量最大的秒杀网站

    号称并发量100W最高.

    CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1w并发, 100台机器

    普通电商订单 -> 下单 -> 订单系统减库存(IO) -> 等待用户付款

    12306的一种可能的模型: 下单 -> 减库存和订单同时异步进行 -> 等付款

    减库存最后还会把压力压到一台服务器

    可以做分布式本地库存 + 单独服务器做库存均衡.

优化环境

  1. 有一个50万PV的资料类网站(从磁盘提取文档到内存) 原服务器32位, 1.5G的堆, 用户反馈网站比较缓慢, 因此公司决定升级, 新的服务器为64位, 16G的堆内存, 结果用户反馈卡顿十分严重, 反而比以前效率更低了.

    1. 为什么?

      很多用户浏览数据, 很多数据load到内存, 内存不足吗频繁GC, STW长, 相应时间变慢.

    2. 为什么会更卡顿

      内存越大, FGC时间越长

    3. 如何优化

      PS换成 PN+CMS 或者G1

  2. 系统CPU经常100%, 如何调优?

    CPU100%一定有线程在占用系统资源,

    1. 找出哪个进程CPU高 (top)
    2. 找出该进程中哪个线程CPU高 (top -Hp)
    3. 到处该线程的堆栈 (jstack)
    4. 查找哪个方法消耗时间 (jstack)
  3. 系统内存彪高, 如何查找问题?

    1. 导出堆内存 (jmap)
    2. 分析 (jhat, jvisualvm, mat, jprofiler…)
  4. 如何监控JVM

    jstat, jvisualvm, jprofiler, arthas, top…

常用调优命令

  1. top显示出系统中CPU和内存占用较高的进程

  2. top -Hp pid 显示出该进程中每个线程的资源占用情况

  3. jstack -l pid

    关注 WAITING, BLOCKED

    waiting on <0x000xxxx> (a java.lang.Object) 说明在等待这个锁

    假如有一个进程中100个线程, 很多线程都在waiting on, 一定要找到是哪个线程持有这把锁

    怎么找?搜索jstack dump的信息, 找, 看哪个线程持有这把锁.

  4. jps 显示出系统中java的进程

  5. 为什么阿里规范规定, 线程的名称必须要有意义的.

  6. jinfo

  7. jstat -gc pid [间隔毫秒]

  8. jmap -histo pid 查找有多少对象产生

  9. jmap -dump:format=b,file=xxx pid 堆转储

  10. JVM检测工具

    1. 图形化界面 jconsole / jvisualVM 测试时用
    2. 不用图形化界面: jmap -histo pid
    3. 线上系统, 内存特别大, jmap执行期间会对进程产生影响, 甚至卡顿
      1. 设定参数 -XX:+HeapDumpOnOutOfMemoryError
      2. 很多服务器备份, 做了高可用, 停掉这台服务器对其他服务器不影响
    4. 使用MAT / jhat进行dump文件分析
public class DeadLock {

    public static void main(String[] args) {
        String a = "A";
        String b = "B";

        new Thread(new HoldLock(a,b)).start();
        new Thread(new HoldLock(b,a)).start();

    }

}

class HoldLock implements Runnable{

    String lockA;
    String lockB;

    public HoldLock(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + "持有 : " + lockA);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + " 持有 : " + lockB);
            }
        }
    }
}

这是一段死锁的代码,

  • 首先使用jps查询java进程

    7700 Jps
    3548 Launcher
    4348
    4428 DeadLock

  • 然后使用 jstack pid 查询堆栈信息

Found one Java-level deadlock:

“Thread-1”:
waiting to lock monitor 0x00000000575ea258 (object 0x00000000d5da3460, a java.lang.String),
which is held by “Thread-0”
“Thread-0”:
waiting to lock monitor 0x00000000575ecb98 (object 0x00000000d5da3490, a java.lang.String),
which is held by “Thread-1”

Java stack information for the threads listed above:

“Thread-1”:
at com.echi.deadlock.HoldLock.run(DeadLock.java:40)
- waiting to lock <0x00000000d5da3460> (a java.lang.String)
- locked <0x00000000d5da3490> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
“Thread-0”:
at com.echi.deadlock.HoldLock.run(DeadLock.java:40)
- waiting to lock <0x00000000d5da3490> (a java.lang.String)
- locked <0x00000000d5da3460> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

这是一段, 多线程竞争锁, 一个线程独占不释放

“Thread-9” #20 prio=5 os_prio=0 tid=0x000000005a1e8000 nid=0x1f2c waiting for monitor entry [0x000000005b6ee000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-8” #19 prio=5 os_prio=0 tid=0x000000005a134800 nid=0x1704 waiting for monitor entry [0x000000005b5bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-7” #18 prio=5 os_prio=0 tid=0x000000005a131800 nid=0x1068 waiting for monitor entry [0x000000005b4af000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-6” #17 prio=5 os_prio=0 tid=0x000000005a131000 nid=0x11b8 waiting for monitor entry [0x000000005b2fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-5” #16 prio=5 os_prio=0 tid=0x000000005958a000 nid=0x6d4 waiting for monitor entry [0x000000005b19f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-4” #15 prio=5 os_prio=0 tid=0x0000000059589800 nid=0x1e98 waiting for monitor entry [0x000000005b03e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-3” #14 prio=5 os_prio=0 tid=0x0000000059588800 nid=0x1f64 waiting for monitor entry [0x000000005ae6e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-2” #13 prio=5 os_prio=0 tid=0x0000000059545000 nid=0x1dc waiting for monitor entry [0x000000005ad2f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-1” #12 prio=5 os_prio=0 tid=0x0000000059544800 nid=0x1de0 waiting for monitor entry [0x000000005a9df000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)
- waiting to lock <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

“Thread-0” #11 prio=5 os_prio=0 tid=0x000000005a1e7800 nid=0x860 runnable [0x000000005ab3f000]
java.lang.Thread.State: RUNNABLE
at com.echi.deadlock.InfiniteLock.m(InfiniteLock.java:17)

    - locked <0x00000000d5da1958> (a java.lang.Class for com.echi.deadlock.InfiniteLock)
            at com.echi.deadlock.InfiniteLock.lambda$main$0(InfiniteLock.java:11)
            at com.echi.deadlock.InfiniteLock$$Lambda$1/824909230.run(Unknown Source)
            at java.lang.Thread.run(Thread.java:748)

可以看到, Thread 1-9全都是BLOCKED (on object monitor) 状态, 而且waiting to lock <0x00000000d5da1958> 都在等待同一个锁.

而Thread-0是RUNNABLE状态, 且locked <0x00000000d5da1958>状态, 因此这里的问题就是有一个线程持有锁后一直不释放, 而且一直在运行, 导致其他线程获取不到锁.

arthas在线排查工具

  1. 下载arthas
  2. 启动arthas java -jar arthas-boot.jar
  3. arthas会寻找java的进程, 并且给与编号
  4. 选择相应编号的进程之后, arthas会将自己挂载到相应进程上
  5. arthas常用命令
    • jvm 类似jinfo, 显示java运行信息
    • thread 显示线程信息
    • dashboard 类似于top, 显示出各个线程的资源使用情况
    • heapdump 相当于jmap到处堆内存
    • jad 反编译
      • 动态代理生成类的问题定位
      • 第三方的类(观察代码)
      • 版本问题 (确认自己最新提交的版本是不是被使用)
    • redefine 热替换
      • 目前有些限制条件, 只能改方法实现(方法已经运行完成), 不能改方法名, 不能改属性
  6. OQL语句

案例汇总

OOM产生的原因多种多样, 有些程序未必产生OOM, 不断FGC (CPU彪高, 但内存回收特别少)

  1. 硬件升级系统反而卡顿的问题

  2. 线程池不当运用产生OOM问题

  3. lambda表达式导致方法区溢出

  4. 直接内存溢出问题

    使用Unsafe分配直接内存, 或者使用NIO的问题

  5. 栈溢出问题

    -Xss设定太小

  6. 重写finalize引发频繁GC

    小米, HBase同步系统, C++程序员重写finalize引发频繁GC问题.

    C++中是手动回收内存, 会调用一个析构函数, 所以认为java中也会有一个类似的方法, 认为finalize()就是java中的析构函数

    重写的finalize耗时较长, 而GC的时候统一回收, 多个对象同时回收, 可能会导致总时间过长.

  7. 如果有一个系统, 内存一直小号不超过10%, 但是观察GC日志, 发现FGC总是频繁发生, 会是什么引起的.

    显示调用了System.gc()


   转载规则


《深入理解JVM(二)》 echi1995 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
深入理解JVM(三) 深入理解JVM(三)
深入理解JVM(三)GC算法CMSCMS在任何一个JAVA版本中都不是默认的垃圾回收器, 但是同时他又是一个非常重要的GC. Serial和Parallel都是无法和工作线程同时相应, 必须垃圾收集结束才可以进行工作线程的继续. 这就导致了
下一篇 
深入理解JVM(一) 深入理解JVM(一)
深入理解JVM(一)Java从编码到执行 从一个 .java文件到执行, 首先需要经过javac编译成.class文件, 然后使用java执行这个class文件. 在java命令开始后, .class文件会被classLoader加载到内存
  目录