大厂面试题之JVM+GC解析

1. JVM垃圾回收的时候如何确定垃圾?是否知道什么是 GC Roots

什么是垃圾

简单的说就是内存中已经不再被使用到的空间就是垃圾

要进行垃圾回收,如何判断一个对象是否可以被回收

  • 引用计数法

  • 枚举根节点做可达性分析(根搜索路径)

跟踪收集器采用的为集中式的管理方式,全局记录对象之间的引用状态,执行时从一些列GC Roots的对象做为起点,从这些节点向下开始进行搜索所有的引用链,当一个对象到GC Roots 没有任何引用链时,则证明此对象是不可用的。

图中,对象Object6、Object7、Object8虽然互相引用,但他们的GC Roots是不可到达的,所以它们将会被判定为是可回收的对象。

哪些对象可以作为 GC Roots 的对象:

  • 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象
  • 方法区中的类静态属性引用的对象
  • 方法去常量引用的对象
  • 本地方法栈中 JNI (Native方法)引用的对象

2. 你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认值

2.1 JVM 的参数类型

  • 标配参数
    • -version
    • -help
  • X 参数(了解)
    • -Xint:解释执行
    • -Xcomp:第一次使用就编译成本地代码
    • -Xmixed:混合模式
  • XX 参数
    • Boolean 类型:-XX:+ 或者 - 某个属性值(+ 表示开启,- 表示关闭)
      • -XX:+PrintGCDetails:打印 GC 收集细节
      • -XX:-PrintGCDetails:不打印 GC 收集细节
      • -XX:+UseSerialGC:使用了串行收集器
      • -XX:-UseSerialGC:不使用了串行收集器
    • KV 设置类型:-XX:key=value
      • -XX:MetaspaceSize=128m
      • -XX:MaxTenuringThreshold=15

jinfo举例,如何查看当前运行程序的配置

公式:jinfo -flag 配置项 进程编号

1
2
3
4
5
6
7
8
9
10
public class HelloGC {
public static void main(String[] args) {
System.out.println("hello GC...");
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

我们可以使用 jps -l 命令,查出进程 id

在使用 jinfo -flag PrintGCDetails 1933 命令查看

1
-XX:-PrintGCDetails

可以看出默认是不打印 GC 收集细节(下图多测试几个)

也可是使用jinfo -flags 1196 查看所以的参数

两个经典参数:-Xms 和 - Xmx(如 -Xms1024m)

  • -Xms 等价于 -XX:InitialHeapSize
  • -Xmx 等价于 -XX:MaxHeapSize

2.2 盘点家底查看 JVM 默认值

查看初始默认值:-XX:+PrintFlagsInitial

公式:

  • java -XX:+PrintFlagsInitial -version

  • java -XX:+PrintFlagsInitial

查看修改更新-XX:+PrintFlagsFinal

公式:

  • java -XX:+PrintFlagsFinal -version

  • java -XX:+PrintFlagsFinal

= 与 := 的区别是,一个是默认,一个是人为改变或者 jvm 加载时改变的参数

打印命令行参数(可以看默认垃圾回收器):-XX:+PrintCommandLineFlags

3. 你平时工作用过的JVM常用基本配置参数有哪些?

  • Xms
    • 初始大小内存,默认为物理内存 1/64
    • 等价于 -XX:InitialHeapSize
  • -Xmx
    • 最大分配内存,默认为物理内存的 1/4
    • 等价于 -XX:MaxHeapSize
  • -Xss
    • 设置单个线程栈的大小,一般默认为 512-1024k
    • 等价于 -XX:ThreadStackSize
  • -Xmn
    • 设置年轻代的大小
    • 整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小,持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
  • -XX:MetaspaceSize
    • 设置元空间大小(元空间的本质和永久代类似,都是对 JVM 规范中的方法区的实现,不过元空间于永久代之间最大区别在于,元空间并不在虚拟中,而是使用本地内存,因此默认情况下,元空间的大小仅受本地内存限制)
    • 元空间默认比较小,我们可以调大一点
  • -XX:+PrintGCDetails
    • 输出详细 GC 收集日志信息
      • 设置 JVM 参数为: -Xms10m -Xmx10m -XX:+PrintGCDetails
      • 代码
1
2
3
4
5
public class HelloGC {
public static void main(String[] args) {
byte[] bytes = new byte[20 * 1024 * 1024];
}
}
  • 打印结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[GC (Allocation Failure) [PSYoungGen: 1231K->448K(2560K)] 1231K->456K(9728K), 0.0015616 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 448K->384K(2560K)] 456K->392K(9728K), 0.0016999 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 384K->0K(2560K)] [ParOldGen: 8K->358K(7168K)] 392K->358K(9728K), [Metaspace: 3028K->3028K(1056768K)], 0.0066696 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 358K->358K(9728K), 0.0005321 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 358K->340K(7168K)] 358K->340K(9728K), [Metaspace: 3028K->3028K(1056768K)], 0.0051543 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 81K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 3% used [0x00000000ffd00000,0x00000000ffd14668,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 340K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 4% used [0x00000000ff600000,0x00000000ff655188,0x00000000ffd00000)
Metaspace used 3060K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.cuzz.jvm.HelloGC.main(HelloGC.java:12)
    • GC

    • FullGC

  • -XX:SurvivorRatio

    • 设置新生代中 eden 和 S0/S1 空间比例
    • 默认 -XX:SurvivorRatio=8,Eden : S0 : S1 = 8 : 1 : 1
  • -XX:NewRatio

    • 配置年轻代和老年代在堆结构的占比
    • 默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3
  • -XX:MaxTenuringThreshold

    • 设置垃圾最大年龄

4. 强引用、软引用、弱引用和虚引用分别是什么?

整体架构

在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为4类。

  • 强引用

    • 特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

  • 软引用

    • 特点:软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
    • 应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
    • 代码验证
      我设置 JVM 参数为 -Xms10m -Xmx10m -XX:+PrintGCDetails
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SoftReferenceDemo {
public static void main(String[] args) {
Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj);
obj = null;

try {
// 分配 20 M
byte[] bytes = new byte[20 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("软引用:" + softReference.get());
}

}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[GC (Allocation Failure) [PSYoungGen: 1234K->448K(2560K)] 1234K->456K(9728K), 0.0016748 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 448K->384K(2560K)] 456K->392K(9728K), 0.0018398 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 384K->0K(2560K)] [ParOldGen: 8K->358K(7168K)] 392K->358K(9728K), [Metaspace: 3030K->3030K(1056768K)], 0.0057246 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 358K->358K(9728K), 0.0006038 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 358K->340K(7168K)] 358K->340K(9728K), [Metaspace: 3030K->3030K(1056768K)], 0.0115080 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
软引用:null
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.cuzz.jvm.SoftReferenceDemo.main(SoftReferenceDemo.java:21)
Heap
PSYoungGen total 2560K, used 98K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd18978,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 340K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 4% used [0x00000000ff600000,0x00000000ff6552f8,0x00000000ffd00000)
Metaspace used 3067K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, reserved 1048576K

发现当内存不够的时候就会被回收。

  • 弱引用
    • 特点:弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
    • 应用场景:弱应用同样可用于内存敏感的缓存。
    • 代码验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class WeakReferenceDemo {
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> weakReference = new WeakReference<>(obj);
System.out.println(obj);
System.out.println(weakReference.get());

obj = null;
System.gc();
System.out.println("GC之后....");

System.out.println(obj);
System.out.println(weakReference.get());
}
}
  • 输出

    1
    2
    3
    4
    5
    java.lang.Object@1540e19d
    java.lang.Object@1540e19d
    GC之后....
    null
    null

    值得注意的是String name = "zzxx" 这种会放入永久代,以及 Integer age = 1 在 int 中 -128 到 127 会被缓存,所以是强引用,然后 GC 也不会被回收。

  • 引用队列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class ReferenceQueueDemo {
    public static void main(String[] args) throws InterruptedException {
    Object obj = new Object();
    ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
    WeakReference<Object> weakReference = new WeakReference<>(obj, referenceQueue);
    System.out.println(obj);
    System.out.println(weakReference.get());
    System.out.println(weakReference);

    obj = null;
    System.gc();
    Thread.sleep(500);

    System.out.println("GC之后....");
    System.out.println(obj);
    System.out.println(weakReference.get());
    System.out.println(weakReference);
    }
    }

    输出

    1
    2
    3
    4
    5
    6
    7
    java.lang.Object@1540e19d
    java.lang.Object@1540e19d
    java.lang.ref.WeakReference@677327b6
    GC之后....
    null
    null
    java.lang.ref.WeakReference@677327b6

    会把该对象的包装类即weakReference放入到ReferenceQueue里面,我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。

    软引用和弱引用的适用场景(mybatis缓存中大量使用了软引用和弱引用)

  • 虚引用

    • 特点:虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
      ReferenceQueue queue = new ReferenceQueue ();
      PhantomReference pr = new PhantomReference (object, queue);
      程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
  • 应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。

GCRoots和四大引用的小总结

5. 请谈谈你对OOM的认识

  • java.lang.StackOverflowError
    • 在一个函数中调用自己就会产生这个错误

  • java.lang.OutOfMemoryError : Java heap space
    • new 一个很大对象
    • 配置参数:-Xms10m -Xmx10m

  • java.lang.OutOfMemoryError : GC overhead limit exceeded

    • 执行垃圾收集的时间比例太大, 有效的运算量太小,默认情况下,,如果GC花费的时间超过 **98%**, 并且GC回收的内存少于 **2%**, JVM就会抛出这个错误。

    配置参数:-Xms10m -Xmx10m

  • java.lang.OutOfMemoryError : Direct buffer memory
    配置参数:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
1
2
3
4
5
6
public class DirectBufferDemo {
public static void main(String[] args) {
System.out.println("maxDirectMemory : " + sun.misc.VM.maxDirectMemory() / (1024 * 1024) + "MB");
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
maxDirectMemory : 5MB
[GC (System.gc()) [PSYoungGen: 1315K->464K(2560K)] 1315K->472K(9728K), 0.0008907 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 464K->0K(2560K)] [ParOldGen: 8K->359K(7168K)] 472K->359K(9728K), [Metaspace: 3037K->3037K(1056768K)], 0.0060466 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at com.cuzz.jvm.DirectBufferDemo.main(DirectBufferDemo.java:17)
Heap
PSYoungGen total 2560K, used 56K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0e170,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 359K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 5% used [0x00000000ff600000,0x00000000ff659e28,0x00000000ffd00000)
Metaspace used 3068K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, reserved 1048576K
  • java.lang.OutOfMemoryError : unable to create new native thread
    • 创建线程数太多了

非root用户登录Linux系统测试

  • java.lang.OutOfMemoryError : Metaspace
    • Java 8 之后的版本使用元空间(Metaspace)代替了永久代,元空间是方法区在 HotSpot 中的实现,它与持久代最大的区别是:元空间并不在虚拟机中的内存中而是使用本地内存。
    • 元空间存放的信息:
      • 虚拟机加载的类信息
      • 常量池
      • 静态变量
      • 即时编译后的代码

使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:MetaspaceSize=21810376B(约20M)

6. GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈

  • 四种 GC 垃圾回收算法
    • 引用计数
    • 复制回收
    • 标记清除
    • 标记整理
  • GC 算法是内存回收的方法论,垃圾收集其就是算法的落实的实现。
  • 目前为止还没有完美的收集器的出现,更加没有万能的收集器,只是针对具体应用最适合的收集器,进行分代收集。
  • 串行垃圾回收器(Serial)
    • 它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,所以不适合服务环境。
  • 并行垃圾回收器(Parallel)
    • 多个垃圾收集线程并行工作,此时用户线程是暂停的,用于科学计算、大数据处理等弱交互场景。
  • 并发垃圾回收器(CMS)
    • 用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替执行),不需要停顿用户线程,互联网公司多用它,适用对相应时间有要求的场景。
  • G1 垃圾回收器
    • G1 垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收。

7. 怎么查看服务器默认的垃圾收集器是那个?

7.1 怎么查看服务器默认垃圾收集器是哪个?

  • Java -XX:+PrintCommandLineFlags

7.2 默认的垃圾收集器有哪些

Java 的 GC 回收的类型主要有:

  • UseSerialGC,UseParallelGC,UseConcMarkSweepGC,UseParNewGC,UseParallelOldGC,UseG1GC
  • Java 8 以后基本不使用 Serial Old

部分参数预先说明

  • DefNew : Default New Generation
  • Tenured : Old
  • ParNew : Parallel New Generation
  • PSYoungGen : Parallel Scavenge
  • ParOldGen : Parallel Old Generation

Server/Client 模式分别是什么意思

  • 最主要的差别在于:-Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。
  • 当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器,C2比C1编译器编译的相对彻底,服务起来之后,性能更高。
  • 所以通常用于做服务器的时候我们用服务端模式,如果你的电脑只是运行一下java程序,就客户端模式就可以了。当然这些都是我们做程序优化程序才需要这些东西的,普通人并不关注这些专业的东西了。其实服务器模式即使编译更彻底,然后垃圾回收优化更好,这当然吃的内存要多点相对于客户端模式。

7.3 新生代垃圾收集器

  1. 串行GC (Serial)/(Serial Copying)

  1. 并行GC (ParNew)

  1. 并行回收GC (Parallel)/(Parallel Scavenge)

==-XX:+UseParallelGC==、==-XX:+UseParallelOldGC==

7.4 老年代垃圾收集器

  1. 串行GC(Serial Old)/(Serial MSC)

  1. 并行GC(Parallel Old)/(Parallel MSC)

    Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Parallel Old收集器在JDK1.6开始提供

    1.6之前,新生代使用ParallelScavenge收集器只能搭配年老代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在1.6之前(ParallelScavenge+SerialOld)

    Parallel Old正式为了在老年代同样提供吞吐量游戏的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以考虑新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略

    常用参数:==-XX:+UseParallelOldGC== 设置该参数后新生代Paralle+老年代Paralle Old

  2. 并发标记清除GC(CMS)

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

    适合在互联网站或者B/S系统的服务器上,这列应用尤其中使服务器的响应速度,希望系统停顿时间最短。

    CMS非常适合堆内存大、CPU核数多的服务区端应用,也是G1出现之大型应用的首选收集器。

    并发标记清除收集器:ParNew+CMS+Serial Old

    CMS,并发收集低停顿,并发指的是与用户线程一起执行

    JVM参数:**==-XX:+UseConcMarkSweepGC==**,开启该参数后会自动将-XX:UseParNewGC打开

    开启该参数后,使用ParNew+CMS+Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器

    4步过程:

    • 初始标记(CMS initial mark)

      只是标记一下GC Roots能够直接关联的对象,速度很快,仍然需要暂停所有的工作线程。

    • 并发标记(CMS concurrent mark)和用户线程一起

      进行GC Roots跟踪过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象

    • 重新标记(CMS remark)

      为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程,由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正

    • 并发清除(CMS concurrent sweep)和用户线程一起

      清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象

      由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上看来CMS收集器的内存回收和用户线程是一起并发的执行。

    优缺点:

    • 并发收集停顿低

    • 并发执行,cpu资源压力大

      由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,也就是说,==CMS必须要在老年代堆内存用尽之前完成垃圾回收==,否则CMS回收失败时,将出发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间。

    • 采用的标记清除算法会导致大量的碎片

      标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制堆堆内存进行压缩。CMS也提供了参数==-XX:CMSFullGCsBeForeCompaction==(默认0,即每次都进行内存整理)来制定多少次CMS收集之后,进行一次压缩的FullGC。

7.5 如何选择垃圾选择器

  • 单CPU或小内存,单机内存

    -XX:+UseSerialGC

  • 多CPU,需要最大吞吐量,如后台计算型应用

    -XX:+UseParallelGC -XX:+UseParallelOldGC

  • 多CPU,最求低停顿时间,需快速相应,如互联网应用

    -XX:+ParNewGC -XX:+UseConcMarkSweepGC

参数 新生代垃圾收集器 新生代算法 老年代垃圾收集器 老年代算法
UseSerialGC SerialGC 复制 SerialOldGC 标整
UseParNewGC ParNew 复制 SerialOldGC 标整
UseParallelGC
UseParallelOldGC
Parallel[Scavenge] 复制 Parallel Old 标整
UseConcMarkSweepGC ParNew 复制 CMS+Serial Old的收集器组合(Serial Old 作为CMS出错的后备收集器) 标清
UseG1GC G1整体上采用标整 局部是通过复制算法

8. G1垃圾收集器

将堆内存分割城不同的区域然后并发的对其进行垃圾回收

8.1 其他收集器特点

  • 年轻代和老年代是各自独立且了连续的内存块
  • 年轻代收集使用单eden+S0+S1进行复制算法
  • 老年代收集必须扫描真个老年代区域
  • 都是以尽可能少而快速地执行GC为设计原则

8.2 G1是什么

G1(Garbage-First)收集器,是一款面向服务端应用的收集器

应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求

  • 和CMS收集器一样,能与应用程序线程并发执行
  • 整理空闲空间更快
  • 需要更多的时间来预测GC停顿时间
  • 不希望牺牲大量的吞吐性能
  • 不需要更大的Java Heap

G1收集器的设计目标是取代CMS收集器,和CMS相比,在以下放木表现更出色:

  • G1是一个由整理内存过程的垃圾收集器,不会产生很多内存碎片
  • G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。

CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是他还是存在着内存碎片问题。于是为了取出内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了G1垃圾收集器。

主要改变的是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region(区域化),每个region从1M到32M不等。一个region有可能属于Eden,Survivor或Tenured内存区域。

8.2.1 特点
  1. G1能充分利用多CPU、多核环境优势,尽量缩短STW。
  2. G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。
  3. 宏观上G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region),可以近似理解为一个棋盘
  4. G1收集器里面讲整个的内存去都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同GC方式来处理不同的区域。
  5. G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换

8.3 底层原理

  • Region区域化垃圾收集器

    最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可

    区域化内存划片Region,整体编为了 一系列不连续的内存区域,避免了全内存区的GC操作。

    核心思想是讲整个堆内存区域分成大小相同的子区域,在JVM启动时会自动共设置这些子区域的大小

    在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数==-XX:G1HeapRegionSize==可指定分区大小(1~32M,且必须是2的幂),默认将整堆划分为2048个分区。

    大小范围在1-32M,最多能设置2048个区域,也即能够支持的最大内存为64G

    G1算法将堆划分为诺干个区域,他仍然属于分代收集器

    • 这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或Survivor空间,这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩,这样也就不会有CMS内存碎片问题的存在了

    • 在G1中,还有一种特殊区域,Humongous区域,如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为i这是一个巨型对象。这些巨型对象默认直接会被分配在年老代,但是如果他是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,他用来专门存放巨型对象。如果一个H区装不下,那么G1就会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC

  • 回收步骤

    针对Eden区进行收集,Eden区耗尽后会被触发,主要小区域收集+形成连续的内存块,避免内存碎片

    • Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据就会晋升到Old区
    • Survivor区的数据移动到新的Survivor区,部分数据晋升到Old区
    • 最后Eden区收拾干净了,GC结束,用户的应用程序继续执行。

  • 4步过程

    1. 初始标记:只标记GC Roots能直接关联到的对象
    2. 并发标记:进行GC Roots Tracing的过程
    3. 最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
    4. 筛选回收:根据时间来进行价值最大化的回收

8.4 case

8.5 常用配置参数(不是重点)

  • -XX:+UseG1Gc

  • -XX:G1HeapRegionSize=n

    设置的G1区域的大小,值是2的幂,范围是1-32MB,目标是根据最小的java堆大小划分出约2048个区域

  • -XX:MaxGCPauseMillis=n

    最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间

  • -XX:InitiatingHeapOccupancyRercent=n

    堆占用了多少的时候就触发GC,默认45

  • -XX:ConcGcThreads=n

    并发GC使用的线程数

  • -XX:G1ReservePercent=n

    设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认10%

8.6 和CMS相比优势

  1. G1不会产生内存碎片
  2. 可以精确控制停顿。该收集器是把整个堆(新生代、老生代)划分成多个固定大小的区域,每根据允许停顿的时间去收集垃圾最多的区域

9. 生产环境服务器变慢,诊断思路和性能评估谈谈?

整机:top

uptime,系统性能命令的精简版

首先看cpu 其次看mem(内存),然后看id(idle,cpu空闲率)越大越好。

用top命令查看整机性能,这个命令敲出来之后,根据我过往的经验,我主要是看几下几个参数,第一个首行会有内存和cpu的时间百分比和占用率。还有load average(系统负载率)这个有三个值,表示系统一分钟,五分钟,十五分钟的系统平均负载,如果这三个值相加之后除以3乘以100%,如果这个值大于60%,说明系统负担重,高于80%说明系统快崩溃了,退出top界面可以用 字母 q 。

CPU:vmstat

  • 查看所有CPU核信息: mpstat -P ALL 2

  • 每个进程使用cpu的用量分解信息: pidstat -u 1 -p 进程编号

内存:free

应用程序可用内存数

  • 查看额外:pidstat -p 进程号 -r 采样间隔秒数

硬盘:df

查看磁盘剩余空闲数

磁盘IO:iostat

磁盘I/O性能评估

  • 查看额外: pidstat -d 采样间隔秒数 -p 进程号

网络IO:ifstat

默认本地没有,下载ifstat

查看网络IO

10. 假如生产环境出现CPU占用过高,请谈谈你的分析思路和定位

结合Linux和JDK命令一块分析

  • 先用 top 命令找出 CPU 占比最高的
  • ps -ef 或者 jps 进一步定位,得知是一个怎么样的一个后台程序
  • 定位到具体的线程或代码
    • ps -mp 11111 -o THREAD,tid,time
    • -m 显示所有的线程
    • -p 进程使用cpu的时间
    • -o 该参数后是用户自定义格式

  • 将需要的线程 ID 转化为 16 进制格式
  • jstat <进程ID> | grep <线程ID(16进制)> -A60

11. GitHub骚操作

关键字解释

  • watch:会持续收到该项目的动态

  • **fork:**复制项目到自己的github仓库

  • **star:**点赞

  • **clone:**将项目下载到本地

  • follow:关注你感兴趣的作者,会收到他们的动态

in 关键词限制搜索范围

1
2
3
4
5
6
7
8
9
10
公式   xx 关键词   in:name 或description或readme

xx in:name 项目名称包含XX的

xx in:description 项目描述中包含xx的

xx in:readme 项目readme文件中包含xx的

组合使用 项目名称或者description中包含springboot
springboot in:name,description

stars 或fork 数量关键词去查找

1
2
3
公式   xx 关键词 stars 通配符   :> 或者 :<= 

区间范围数字 数字1..数字2
1
2
查找stars数字=大于等于5000得springboot项目 
springboot stars:>=5000

查找forks数大于等于500的springcloud项目
springcloud forks:>500

1
2
3
组合使用:查找fork在100200并且stars 数在 80100 之间的springcloud项目

springcloud fork:100..200 stars:80..100

awesome加强搜索

​ awesome 释义为极好的,了不起的 此关键字一般用来搜索优秀的框架

例如 搜索 redis相关的优秀项目

1
awesome redis

高亮项目中某一行的代码

高亮显示一行 地址后面+#L行号 如高亮显示第13行

1
https://github.com/527515025/springBoot/blob/master/springboot-SpringSecurity1/src/main/java/com/us/example/controller/HomeController.java#L13

高亮显示 多行 地址#L数字1-L数字2 如高亮显示 第13行到第20行

1
https://github.com/527515025/springBoot/blob/master/springboot-SpringSecurity1/src/main/java/com/us/example/controller/HomeController.java#L13-L20

项目内搜索

使用快捷键t 可快速查找(类名)

搜索某个区域的大佬(活跃度高的人)

1
2
如查找北京区域的java 大佬
location:beijing language:java