主要讲述JVM内存结构,以及线上简单的调优场景。
前言
JVM系列应该属于Java高阶的内容,本来是想等Java基础知识积累一段时候之后再学习,但是因为工作中需要跟进线上问题,所以急需补充这块知识,本文主要是对学习的知识简单做个记录,然后再记录分析问题的过程。
JVM问题
刚转到小米人事部门,发现线上的2台机器一直在GC,而且频率非常高,下面是GC的日志:
如果需要看懂这个GC日志,下面的内容就一定需要掌握。
垃圾回收算法
如何确定对象已死?
通常,判断一个对象是否被销毁有两种方法:
- 引用计数算法:为对象添加一个引用计数器,每当对象在一个地方被引用,则该计数器加1;每当对象引用失效时,计数器减1。但计数器为0的时候,就表白该对象没有被引用。
- 可达性分析算法:通过一系列被称之为“GC Roots”的根节点开始,沿着引用链进行搜索,凡是在引用链上的对象都不会被回收。
就像上图的那样,绿色部分的对象都在GC Roots的引用链上,就不会被垃圾回收器回收,灰色部分的对象没有在引用链上,自然就被判定为可回收对象。
那么,问题来了,这个GC Roots又是什么?下面列举可以作为GC Roots的对象:
- Java虚拟机栈中被引用的对象,各个线程调用的参数、局部变量、临时变量等。
- 方法区中类静态属性引用的对象,比如引用类型的静态变量。
- 方法区中常量引用的对象。
- 本地方法栈中所引用的对象。
- Java虚拟机内部的引用,基本数据类型对应的Class对象,一些常驻的异常对象。
- 被同步锁(synchronized)持有的对象。
垃圾回收算法
标记--清除算法
见名知义,标记--清除算法就是对无效的对象进行标记,然后清除。
对于标记--清除算法,你一定会清楚看到,在进行垃圾回收之后,堆空间有大量的碎片,出现了不规整的情况。在给大对象分配内存的时候,由于无法找到足够的连续的内存空间,就不得不再一次触发垃圾收集。另外,如果Java堆中存在大量的垃圾对象,那么垃圾回收的就必然进行大量的标记和清除动作,这个势必造成回收效率的降低。
复制算法
标记--复制算法就是把Java堆分成两块,每次垃圾回收时只使用其中一块,然后把存活的对象全部移动到另一块区域。
标记--复制算法有一个很明显的缺点,那就是每次只使用堆空间的一半,造成了Java堆空间使用率的的下降。
标记--整理算法
标记--整理算法算是一种折中的垃圾收集算法,在对象标记的过程,和前面两个执行的是一样步骤。但是,进行标记之后,存活的对象会移动到堆的一端,然后直接清理存活对象以外的区域就可以了。这样,既避免了内存碎片,也不存在堆空间浪费的说法了。但是,每次进行垃圾回收的时候,都要暂停所有的用户线程,特别是对老年代的对象回收,则需要更长的回收时间,这对用户体验是非常不好的。
垃圾回收器
HotSpot VM中的垃圾回收器,以及适用场景: