目录
2.1 JVM垃圾回收的时候如何确定垃圾?什么是GC Roots?
2.5.1 java.lang.StackOverflowError 是个错误
2.5.2 java.lang.OutOfMemoryError: Java heap space 是错误
2.5.3 java.lang.OutOfMemoryError: GC overhead limit exceeded
2.5.4 java.lang.OutOfMemoryError: Direct buffer memory
2.5.5 java.lang.OutOfMemoryError: unable to create new native thread
2.5.6 java.lang.OutOfMemoryError: Metaspace
2.7 怎么查看服务器默认的垃圾收集器是哪个?生产上如何配置垃圾收集器的?对垃圾收集器的理解
2.9 JVMGC + SpringBoot微服务的生产部署和调参优化
JVM+GC解析
1 复习
1.1 JVM内存结构
1.1.1 体系概述
1.1.2 Java8以后的JVM
-Xms 堆的初始值是多大 (初始化堆内存默认是1/64)
-Xmx 堆的最大值是多大 (最大堆内存默认是物理内存的1/4)
1.2 GC的作用域
1.3 常见的垃圾回收算法
1.3.1 引用计数
1.3.2 复制
1.3.3 标记清除 (会导致内存碎片)
1.3.4 标记整理
2 进一步学习
2.1 JVM垃圾回收的时候如何确定垃圾?什么是GC Roots?
什么是垃圾——简单的说就是内存中已经不再被使用到的空间就是垃圾
要进行垃圾回收,如何判断一个对象是否可以被回收?
(1)引用计数法
(2)枚举根节点做可达性分析(根搜索路径)
case:
Java中可以作为GC Roots的对象
虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
方法区中的类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(Native方法)引用的对象。
/**
* @author haitao.you
* @date 2021/1/24
*/
public class GCRootDemo {
private byte[] byteArray = new byte[100*1024*1024];
// private static GCRootDemo2 t2;
// private static final GCRootDemo3 t3 = new GCRootDemo3(8);
public static void m1(){
GCRootDemo t1 = new GCRootDemo();
System.gc();
System.out.println("第一次GC完成");
}
public static void main(String[] args) {
m1();
}
}
2.2 JVM调优和参数配置,如何盘点查看JVM系统默认值
2.2.1 JVM的参数类型
(1)标配参数
-version
-help
java -showversion
(2)X参数(了解)
-Xint 解释执行
-Xcomp 第一次使用就编译成本地代码
-Xmixed 混合模式
(3)XX参数
Boolean类型
公式: -XX:+ 或者 - 某个属性 (+ 表示开启;- 表示关闭)
Case:1、是否打印GC收集细节 -XX:-printGCDetails +printGCDetails
2、是否使用串行垃圾回收器 -XX:-UseSerialGC; -XX:+UseSerialGC
KV设值类型
公式:-XX:属性key=属性值value
case:-XX:MetaspaceSize=128M
-XX:MaxTenuringThreshold=15 (表示多大的年龄可以升养老区)
jinfo举例,如何查看当前运行程序的配置
题外话(坑题)
两个经典参数:-Xms和-Xmx
这个你如何解释:-Xms 等价于-XX:InitialHeapSize
-Xmx 等价于-XX:MaxHeapSize
2.2.2 盘点家底查看JVM默认值
-XX:+PrintFlagsInitial 主要查看初始默认 公式:java -XX:+PrintFlagsInitial -version 或 java -XX:PrintFlagsInitial
-XX:+PrintFlagsFinal 主要查看修改更新 公式:java -XX:PrintFlagsFinal -version
图中 = 代表没更改过的 := 代表人为更改过的
PrintFlagsFinal举例,运行Java命令的同时打印出参数
-XX:+PrintCommandLineFlags (偏重于看默认的垃圾回收器)
公式:java -XX:+PringCommandLineFlags -version
2.3 平时工作用过的JVM常用基本配置参数有哪些?
2.3.1 基础参数
2.3.2 常用参数
-Xms 初始大小内存,默认为物理内存1/64 等价于 -XX:InitialHeapSize
-Xmx 最大分配内存,默认为物理内存1/4 等价于 -XX:MaxHeapSize
-Xss 设置单个线程栈的大小,一般默认为512K~1024K 等价于 -XX:ThreadStackSize
-Xmn 设置年轻代大小
-XX:MetaspaceSize 设置元空间大小
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
-Xms10m -Xmx10m -XX:Metaspace=1024m -XX:PrintFlagsFinal
典型设置案例
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
-XX:+PrintGCDetails
输出详细GC收集日志信息
-XX:SurvivorRatio
设置新生代中eden和S0/S1空间的比例,
默认
-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1
假如
-XX:SurvivorRatio=4,Eden:S0:S1=4:1:1
SurvivorRatio值就是设置eden区的比例占多少,S0/S1相同
-XX:NewRatio
配置年轻代与老年代在堆结构的占比
默认
-XX:NewRatio=2新生代占1,老年代2,年轻代占整个堆的1/3
假如
-XX:NewRatio=4新生代占1,老年代4,年轻代占整个堆的1/5
NewRatio值就是设置老年代的占比,剩下的1给新生代
-XX:MaxTenuringThreshold 设置垃圾的最大年龄
但是,如果将MaxTenuringThreshold 设置为20,会报错
2.4 强引用、软引用、弱引用、虚引用分别是什么?
2.4.1 整体架构
java.lang.ref.Reference 下面
2.4.2 强引用(默认支持模式)
* 当内存不足JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不回收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当让具体回收时机还是要看垃圾收集策略)。
/**
* @author haitao.you
* @date 2021/1/26
*/
public class StrongReferenceDemo {
public static void main(String[] args) {
Object obj1 = new Object();//这样定义的默认就是强引用
Object obj2 = obj1; //obj2引用赋值
obj1 = null; //置空
System.gc();
System.out.println(obj2);
}
}
2.4.3 软引用
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说,
当系统内存充足时它 不会 被回收,
当系统内存不足时它 会 被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
主要演示内存不够用时,软引用对象会被回收的情况
import java.lang.ref.SoftReference;
/**
* @author haitao.you
* @date 2021/1/26
*/
public class SoftReferenceDemo {
/**
* 内存够用就保留,不够用就回收!
*/
public static void softRef_Memory_Enough() {
Object o1 = new Object();
SoftReference<Object> o2 = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(o2.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(o2.get());
}
/**
* JVM配置,故意产生大对象并配置小的内存,让它内存不够用了导致OOM,看软引用的回收情况
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*/
public static void softRef_Memory_NotEnough() {
Object o1 = new Object();
SoftReference<Object> o2 = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(o2.get());
o1 = null;
try {
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(o1);
System.out.println(o2.get());
}
}
public static void main(String[] args) {
// SoftReferenceDemo.softRef_Memory_Enough();
SoftReferenceDemo.softRef_Memory_NotEnough();
}
}
2.4.4 弱引用
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
import java.lang.ref.WeakReference;
/**
* @author haitao.you
* @date 2021/1/26
*/
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> objectWeakReference = new WeakReference<>(o1);
System.out.println(o1);
System.out.println(objectWeakReference.get());
o1 = null;
System.gc();
System.out.println("============");
System.out.println(o1);
System.out.println(objectWeakReference.get());
}
}
2.4.5 软引用和弱引用的适用场景
假如有一个应用需要读取大量的本地图片:
* 如果每次读取图片都从硬盘读取则会严重影响性能,
* 如果一次性全部加载到内存中又可能造成内存溢出。
此时使用软引用可以解决这个问题。
设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
你知道弱引用的话,能谈谈WeakHashMap吗?
WeakHashMap 继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。
和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap
import java.util.HashMap;
import java.util.WeakHashMap;
/**
* @author haitao.you
* @date 2021/1/26
*/
public class WeakHashMapDemo {
public static void main(String[] args) {
myHashMap();
System.out.println("==================");
myWeakHashMap();
}
private static void myHashMap() {
HashMap<Integer, String> hashMap = new HashMap<>();
Integer key = new Integer(1);
String value = new String("hashMap");
hashMap.put(key,value);
System.out.println(hashMap);
key = null;
System.out.println(hashMap);
System.gc();
System.out.println(hashMap+"\t"+hashMap.size());
}
private static void myWeakHashMap() {
WeakHashMap<Integer, String> hashMap = new WeakHashMap<>();
Integer key = new Integer(1);
String value = new String("weakHashMap");
hashMap.put(key,value);
System.out.println(hashMap);
key = null;
System.out.println(hashMap);
System.gc();
System.out.println(hashMap+"\t"+hashMap.size());
}
}
2.4.6 引用队列
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
/**
* @author haitao.you
* @date 2021/1/26
*/
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
ReferenceQueue refQueue = new ReferenceQueue();
WeakReference<Object> objectWeakReference = new WeakReference<>(o1,refQueue);
System.out.println(o1);
System.out.println(objectWeakReference.get());
System.out.println(refQueue.poll());
o1 = null;
System.gc();
System.out.println("============");
System.out.println(o1);
System.out.println(objectWeakReference.get());
System.out.println(refQueue.poll());
}
}
2.4.7 虚引用
虚引用需要java.lang.ref.PhantomReference类来实现。
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
/**
* @author haitao.you
* @date 2021/1/28
*
* java提供了4种引用类型,在垃圾回收的时候,都有自己各自的特点。
* ReferenceQueue 是用来配合引用工作的,没有ReferenceQueue 一样可以运行。
*
* 创建引用的时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入到引用队列,
* 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
* 这相当于是一种通知机制。
*
* 当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后,
* 做一些我们自己想做的事情。
*
*/
public class PhantomReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue();
PhantomReference<Object> phanRef = new PhantomReference<>(o1, refQueue);
System.out.println(o1);
System.out.println(phanRef.get());
System.out.println(refQueue.poll());
System.out.println("================");
o1=null;
System.gc();
System.out.println(o1);
System.out.println(phanRef.get());
System.out.println(refQueue.poll());
}
}
2.4.8 GCRoots和四大引用小总结
2.5 对OOM的认识
2.5.1 java.lang.StackOverflowError 是个错误
/**
* @author haitao.you
* @date 2021/1/28
*/
public class StackOverflowErrorDemo {
public static void main(String[] args) {
stackOverflowErrorDemo();
}
private static void stackOverflowErrorDemo() {
stackOverflowErrorDemo();//Exception in thread "main" java.lang.StackOverflowError
}
}
2.5.2 java.lang.OutOfMemoryError: Java heap space 是错误
/**
* @author haitao.you
* @date 2021/1/28
*/
public class JavaHeapSpaceDemo {
public static void main(String[] args) {
byte[] bytes = new byte[80 * 1024 * 1024];//Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
}
}
2.5.3 java.lang.OutOfMemoryError: GC overhead limit exceeded
GC回收时间过长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。假如不抛出GC overhead limit 错误会发生什么情况呢?
那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环,CPU使用率一直是100%,而GC却没有任何成果。
JVM参数演示配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
import java.util.ArrayList;
/**
* @author haitao.you
* @date 2021/1/28
*/
public class GCOverheadDemo {
public static void main(String[] args) {
int i = 0;
ArrayList<String> list = new ArrayList<>();
try {
while (true){
list.add(String.valueOf(++i).intern()); //java.lang.OutOfMemoryError: GC overhead limit exceeded
}
}catch (Exception e){
System.out.println("****************i:" + i);
e.printStackTrace();
throw e;
}
}
}
2.5.4 java.lang.OutOfMemoryError: Direct buffer memory
2.5.5 java.lang.OutOfMemoryError: unable to create new native thread
高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError: unable to create new native thread
准确的讲该native thread异常与对应的平台有关
导致原因:
1. 你的应用创建了太多线程,一个应用进程创建多个线程,超过该系统承载极限
2. 你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,
你的应用创建超过这个数量,就会报java.lang.OutOfMemoryError: unable to create new native thread
解决办法:
1. 想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
2. 对于有的应用,的确需要创建很多线程,远超过linux系统的默认1024个线程的限制,可以通过修改linux服务器配置,扩大Linux默认限制: vim /etc/security/limits.d/90-nproc.conf
/**
* @author haitao.you
* @date 2021/1/28
*/
public class UnableCreateNewThreadDemo {
public static void main(String[] args) {
for (int i = 1; ; i++) {
System.out.println("************ i = "+ i);
new Thread(()->{
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
},""+i).start();
}
}
}
2.5.6 java.lang.OutOfMemoryError: Metaspace
2.6 GC垃圾回收算法和垃圾收集器的关系?分别是什么?
2.6.1 GC算法
引用计数/复制/标记清除/标记整理 是内存回收的方法论,垃圾收集器就是算法落地实现。
2.6.2 分代收集
因为到目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最适合的收集器,进行分代收集
2.6.3 四种主要垃圾收集器
串行垃圾回收器(Serial):它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。
并行垃圾回收器(Parallel):多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理首台处理等弱交互场景
并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,适用于对响应时间有要求的场景。
小结:
G1垃圾回收器
2.7 怎么查看服务器默认的垃圾收集器是哪个?生产上如何配置垃圾收集器的?对垃圾收集器的理解
2.7.1 怎么查看默认的垃圾收集器是哪个?
2.7.2 默认的垃圾收集器有哪些?
2.7.3 垃圾收集器
垃圾收集器就来具体实现这些GC算法并实现内存回收。不同厂商、不同版本的虚拟机实现差别很大,HotSpot中包含的收集器如下图所示:
部分参数预先说明
DefNew——Default New Generation
Tenured——Old
ParNew——Parallel New Generation
PSYoungGen——Parallel Scavenge
ParOldGen——Parallel Old Generation
Server/Client模式分别是什么意思
1)适用范围:只需要掌握Server模式即可,Client模式基本不会用
2)操作系统:32位Window操作系统,不论硬件如何都是Client的JVM模式
32位其它操作系统,2G内存同时有2个CPU以上用Server模式,低于该配置还是Client模式
64位only server模式
新生代
串行GC(Serial)/(Serial Copying)
一个单线程的收集器,在进行垃圾收集时候必须暂停其他所有的工作线程直到它收集结束。
串行收集器是最古老,最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾收集过程中可能会产生较长的停顿(Stop-The-World 状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
对应的JVM参数是:-XX:+UseSerialGC
开启后会使用:Serial(Young区用)+Serial Old(Old区用)的收集器组合
表示:新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC
并行(GC)(ParNew)
使用多线程进行垃圾回收,在垃圾收集时,会Stop-the-World暂停其他所有的工作线程直到它收集结束。
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多Java虚拟机运行在Server模式下新生代的默认垃圾收集器。
常用对应JVM参数:-XX:+UseParNewGC 启用ParNew收集器,只影响新生代的收集,不影响老年代
开启上述参数后,会使用:ParNew(Young区用)+Serial Old的收集器组合,新生代使用复制算法,老年代采用标记-整理算法
但是,ParNew+Tenured这样的搭配,java8已经不再被推荐
Java HotSpot(TM) 64-Bit Server VM warning:
Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
并行回收GC(Parallel)/(Parallel Scavenge)
Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。一句话:串行收集器在新生代和老年代的并行化
它重点关注的是:
可控制的吞吐量(Thoughput = 运行用户代码时间/(运行用户代码时间+垃圾收集时间),也即比如程序运行100分钟,垃圾收集时间1分钟,吞吐量就是99%)。高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务。
自适应调节策略也是ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis) 或最大的吞吐量。
常用JVM参数:-XX:+UseParallelGC 或 -XX:+UseParallelOldGC(可互相激活)使用Parallel Scanvenge收集器
注意:-XX:ParallelGCThreads=数字N 表示启动多少个GC线程
cpu>8 N = 5/8
cpu<8 N = 实际个数
老年代
并行回收GC(Parallel Old)/(Parallel MSC)
Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Parallel Old收集器在JDK1.6才开始提供。
在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配老年代的Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在JDK1.6之前(Parallel Scavenge + Serial Old)
Parallel Old 正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以优先考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配策略。在JDK1.8及后(Parallel Scavenge + Parallel Old)
JVM常用参数:
-XX:+UseParallelOldGC 使用Parallel Old收集器,设置该参数后,新生代Parallel+老年代Parallel Old
并发标记清除GC(CMS)
CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。
适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
Concurrent Mark Sweep:并发标记清除,并发收集降低停顿,并发指的是与用户线程一起执行
开启该收集器的JVM参数:-XX:+UseConcMarkSweepGC 开启该参数后会自动将 -XX:+UseParNewGC打开
开启该参数后,使用ParNew(Young区用) + CMS(Old区用) + Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器
4步过程:
初始标记(CMS initial mark):只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
并发标记(CMS concurrent mark)和用户线程一起:进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象。
重新标记(CMS remark):为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正。
并发清除(CMS concurrent sweep)和用户线程一起:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象。
由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户在一起并发工作,
所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。
优缺点:
优 - 并发收集低停顿
缺 - 1)并发执行,对cpu资源压力大:由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次 GC,从而造成较大停顿时间。
2)采用标记清除算法会导致大量碎片:标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定 多少次CMS收集之后,进行一次压缩的Full GC。
串行GC(Serial Old)/(serial MSC)理论知道即可,实践中已经被优化掉了
Serial Old 是 Serial 垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的Java虚拟机默认的老年代垃圾收集器。
垃圾收集器配置代码总结
2.7.4 如何选择垃圾收集器
组合选择:
1. 单CPU或小内存,单机程序
-XX:+UseSerialGC
2. 多CPU,需要最大吞吐量,如后台计算型应用
-XX:UseParallelGC 或者 -XX:+UseParallelOldGC
3. 多CPU,追求低停顿时间,需快速响应如互联网应用
-XX:+UseConcMarkSweepGC
-XX:+ParNewGC
2.8 G1垃圾收集器
2.8.1 以前收集器特点
年轻代和老年代是各自独立且连续的内存块;
年轻代收集使用单eden+S0+S进行复制算法;
老年代收集必须扫描整个老年代区域;
都是以尽可能少而快速地执行GC为设计原则。
2.8.2 G1是什么
G1(Garbage-First)收集器,是一款面向服务端应用的收集器
像CMS收集器一样,能与应用程序线程并发执行。从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集器暂停时间的要求。另外,它还具有以下特性:
整理空闲空间更快。
需要更多的时间来预测GC停顿时间。
不希望牺牲大量的吞吐性能。
不需要更大的Java Heap。
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器 - G1垃圾收集器。
G1是一款面向服务端应用的收集器,主要应用在多CPU和大内存服务器环境下,极大的减少垃圾收集的停顿时间,全面提升服务器的性能,逐步替换Java8以前的CMS收集器。
主要改变是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region,每个region从1M到32M不等。一个region有可能属于Eden,Survivor或者Tenured内存区域。
特点:
1:G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW。
2:G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。
3:宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(region),可以近似理解为一个围棋的棋盘。
4:G1收集器里面将整个的内存区都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
5:G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换。
2.8.3 底层原理
1. Region区域化垃圾收集器:最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。
核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小,
在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048 = 65536MB = 64G内存
2. 回收步骤:
G1收集器下的Young GC
针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
* Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会部分晋升到Old区
* Survivor区的数据移动到新的Survivor区,部分数据晋升到Old区
* 最后Eden区收拾干净了,GC结束,用户的应用程序继续执行。
3. 4步过程
2.8.4 case案例
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseG1GC
2.8.5 常用配置参数(了解)
可选项及默认值 | 描述 |
---|---|
-XX:+UseG1GC | 采用 Garbage First (G1) 收集器 |
-XX:MaxGCPauseMillis=n | 设置最大GC 暂停时间。这是一个大概值,JVM 会尽可能的将停顿小于此值 |
-XX:InitiatingHeapOccupancyPercent=n | 设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。默认值 45. |
-XX:NewRatio=n | new/old 年代的大小比例. 默认值 2. |
-XX:SurvivorRatio=n | eden/survivor 空间的大小比例. 默认值 8. |
-XX:MaxTenuringThreshold=n | 对象晋升年代的最大阀值。默认值 15.这个参数需要注意的是:最大值是15,不要超过这个数啊,要不然会被人笑话。原因为:JVM内部使用 4 bit (1111)来表示这个数。 |
-XX:ParallelGCThreads=n | 设置在垃圾回收器的并行阶段使用的线程数。默认值因与 JVM 运行的平台而不同。 |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数。默认值因与 JVM 运行的平台而不同。 |
-XX:G1ReservePercent=n | 设置作为空闲空间的预留内存百分比以降低晋升失败的可能性。默认值10 |
-XX:G1HeapRegionSize=n | 使用G1,Java堆被划分为大小均匀的区域。这个参数配置各个子区域的大小。此参数的默认值根据堆大小的人工进行确定。最小值为 1Mb 且最大值为 32Mb。 |
-XX:G1PrintRegionLivenessInfo | 默认值false, 在情理阶段的并发标记环节,输出堆中的所有 regions 的活跃度信息 |
-XX:G1PrintHeapRegions | 默认值false, G1 将输出那些 regions 被分配和回收的信息 |
-XX:+PrintSafepointStatistics | 输出具体的停顿原因 |
-XX: PrintSafepointStatisticsCount=1 | 输出具体的停顿原因 |
-XX:+PrintGCApplicationStoppedTime | 停顿时间输出到GC日志中 |
-XX:-UseBiasedLocking | 取消偏向锁 |
-XX:+UseGCLogFileRotation | 开启滚动日志输出,避免内存被浪费 |
-XX:+PerfDisableSharedMem | 关闭 jstat 性能统计输出特性,使用 jmx 代替 |
2.8.6 和CMS相比的优势
1. G1不会产生内存碎片
2. 是可以精确控制停顿。该收集器是把整个堆(新生代、老年代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域
2.9 JVMGC + SpringBoot微服务的生产部署和调参优化
要求微服务启动的时候,同时配置我们的JVM/GC 的调优参数
1. 内部启动
2. 外部启动 ====> 重点 :java -server jvm的各种参数 -jar jar/war包名字
2.9 生产环境服务器变慢,诊断思路和性能评估?
1. 整机:top
(其中load average : 1.35 0.81 0.37 代表着系统的负载均衡,三个值代表着1分钟 5分钟 15分钟 系统平均负载值,如果三个值相加除以3再乘上100%高于60%的话,说明系统的负担压力重。)
uptime,系统性能命令的精简版
2. CPU: vmstat
命令:vmstat -n 2 3 代表每2秒采样一次,共计采样3次
查看所有CPU核信息:mpstat -P ALL 2
每个进程使用CPU的用量分解信息:pidstat -u 1 -p 进程编号
3. 内存:free
4. 磁盘:df
5. 磁盘IO:iostat
6. 网络IO:ifstat