JVM+GC
- 1.1 JVM体系概述
- 1.2 常见的垃圾回收算法
- 3.1 JVM的标配参数和X参数
- 3.2 JVM的XX参数之布尔类型
- 3.3 JVM的XX参数之设值类型
- 3.4 JVM的XX参数之XmsXmx坑题
- 3.5 JVM盘点家底查看初始默认值
- 3.6 JVM盘点家底查看修改变更值
- 4.1 堆内存初始大小快速复习
- 4.2 常用基础参数栈内存Xss
- 4.3 常用基础参数元空间MetaspaceSize
- 4.4 常用基础参数PrintGCDetails回收前后对比
- 4.5 常用基础参数SurvivorRatio
- 4.6 常用基础参数NewRatio
- 4.7 常用基础参数MaxTenuringThreshold
- 5.1 强引用
- 5.2 软引用
- 5.3 弱引用
- 5.4 软引用和弱引用的适用场景
- 5.5 WeakHashMap案例演示和解析
- 5.6 虚引用简介
- 5.7 ReferenceQueue引用队列介绍
- 5.8 虚引用PhantomReference
- 5.9 GCRoots和四大引用小总结
- 7.1 OOM之Java heap space
- 7.2 OOM之GC overhead limit exceeded
- 7.3 OOM之Direct buffer memory
- 7.4 OOM之unable to create new native thread故障演示
- 7.5 OOM之unable to create new native thread上限调整
- 7.6 OOM之Metaspace
- 8.1 垃圾收集器回收种类
- 8.2 串行并行并发G1四大垃圾回收方式
- 8.3 如何查看默认的垃圾收集器
- 8.4 JVM默认的垃圾收集器有哪些
- 8.5 GC之7大垃圾收集器概述
- 8.6 GC之约定参数说明
- 8.7 GC之Serial收集器
- 8.8 GC之ParNew收集器
- 8.9 GC之Parallel收集器
- 8.10 GC之ParallelOld收集器
- 8.10 GC之CMS收集器
- 8.11 GC之SerialOld收集器
- 8.12 GC之如何选择垃圾收集器
- 8.13 GC之G1收集器
- 8.14 GC之G1底层原理
- 8.15 GC之G1参数配置及和CMS的比较
- 8.16 JVMGC结合SpringBoot微服务优化简介
1. JVM快速回顾复习串讲
1.1 JVM体系概述
Java8以后的JVM
1.2 常见的垃圾回收算法
1.2.1.引用计数
1.2.2.复制
Java堆从GC的角度还可以细分为: 新生代(Eden 区、From Survivor 区和To Survivor 区)和老年代。
MinorGC的过程(复制->清空->互换):
a. Eden、SurvivorFrom复制到SurvivorTo,年龄+1
首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1。
b. 清空eden-SurvivorErom
然后,清空Eden和Survivor From中的对象,也即复制之后有交换,谁空谁是To。
c. Survivor To和 Survivor From互换
最后,Survivor/. To和Survivor From互换,原SurvivorTo成为下一次GC时的Survivor From区。部分对象会在From和To区域中复制来复制去,如此交换15次(由ⅣM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
1.2.3.标记清除
算法分成标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些对象。
1.2.4.标记整理
2.谈谈你对GC Roots的理解
什么是垃圾?
简单的说就是内存中已经不再被使用到的空间就是垃圾。
要进行垃圾回收,如何判断一个对象是否可以被回收?
* 引用计数法
* 枚举根节点做可达性分析(根搜索路径)
1.引用计数法
Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。
因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,给对象中添加一个引用计数器,
每当有一个地方引用它,计数器值加1,
每当有一个引用失效时,计数器值减1。
任何时刻计数器值为零的对象就是不可能再被使用的,那么这个对象就是可回收对象。
那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。
该算法存在但目前无人用了,解决不掉循环引用的问题,了解即可。
2.枚举根节点做可达性分析(根搜索路径)
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。
所谓“GC roots”或者说tracing GC的“根集合”就是一组必须活跃的引用。
基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活;没有被遍历到的就自然被判定为死亡。
Java中可以作为GC Roots的对象:
虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
方法区中的类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(Native方法)引用的对象。
3.JVM的参数类型
3.1 JVM的标配参数和X参数
JVM的参数类型:
标配参数
-version java -version
-help
X参数(了解)
-Xint:解释执行
-Xcomp:第一次使用就编译成本地代码
-Xmixed:混合模式
XX参数(下一节)
3.2 JVM的XX参数之布尔类型
公式:-XX:+ 或者 - 某个属性值(+表示开启,-表示关闭)
如何查看一个正在运行中的java程序,它的某个jvm参数是否开启?具体值是多少?
1.jps -l 查看一个正在运行中的java程序,得到Java程序号。
2.jinfo -flag PrintGCDetails (Java程序号 )查看它的某个jvm参数(如PrintGCDetails )是否开启。
3.jinfo -flags (Java程序号 )查看它的所有jvm参数
Case
是否打印GC收集细节
-XX:-PrintGCDetails
-XX:+PrintGCDetails
是否使用串行垃圾回收器
-XX:-UseSerialGC
-XX:+UserSerialGC
3.3 JVM的XX参数之设值类型
公式:-XX:属性key=属性值value
Case
-XX:MetaspaceSize=128m
-XX:MaxTenuringThreshold=15
3.4 JVM的XX参数之XmsXmx坑题
两个经典参数:
-Xms等价于-XX:InitialHeapSize,初始大小内存,默认物理内存1/64
-Xmx等价于-XX:MaxHeapSize,最大分配内存,默认为物理内存1/4
3.5 JVM盘点家底查看初始默认值
查看初始默认参数值
-XX:+PrintFlagsInitial
公式:java -XX:+PrintFlagsInitial
查看修改更新参数值
-XX:+PrintFlagsFinal
公式:java -XX:+PrintFlagsFinal
=表示默认,:=表示修改过的。
3.6 JVM盘点家底查看修改变更值
PrintFlagsFinal举例,运行java命令的同时打印出参数
java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m HelloWorld
打印命令行参数
-XX:+PrintCommandLineFlags
4. JVM常用基本配置参数
4.1 堆内存初始大小快速复习
JDK 1.8之后将最初的永久代取消了,由元空间取代。
在Java8中,永久代已经被移除,被一个称为元空间的区域所取代。元空间的本质和永久代类似。
元空间(Java8)与永久代(Java7)之间最大的区别在于:永久带使用的JVM的堆内存,但是Java8以后的元空间并不在虚拟机中而是使用本机物理内存。
因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。
public class HelloGCDemo { public static void main(String[] args) { // 返回Java虚拟机中内存的总量 long totalMemory = Runtime.getRuntime().totalMemory(); // 返回Java虚拟机中试图使用的最大内存量 long maxMemory = Runtime.getRuntime().maxMemory(); System.out.println(String.format("TOTAL_MEMORY(-Xms): %d B, %.2f MB.", totalMemory, totalMemory / 1024.0 / 1024)); System.out.println(String.format("MAX_MEMORY(-Xmx): %d B, %.2f MB.", maxMemory, maxMemory / 1024.0 / 1024)); } }
输出结果:
TOTAL_MEMORY(-Xms): 128974848 B, 123.00 MB.
MAX_MEMORY(-Xmx): 1883242496 B, 1796.00 MB.
-Xms等价于-XX:InitialHeapSize,初始大小内存,默认物理内存1/64
-Xmx等价于-XX:MaxHeapSize,最大分配内存,默认为物理内存1/4
4.2 常用基础参数栈内存Xss
设置单个线程栈的大小,一般默认为512k~1024K
等价于-XX:ThreadStackSize
4.3 常用基础参数元空间MetaspaceSize
-Xmn:设置年轻代大小
-XX:MetaspaceSize 设置元空间大小
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制
典型设置案例
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails-XX:+UseSerialGC
4.4 常用基础参数PrintGCDetails回收前后对比
-XX:+PrintGCDetails 输出详细GC收集日志信息
设置参数 -Xms10m -Xmx10m -XX:+PrintGCDetails 运行以下程序
public class HelloGCDemo { public static void main(String[] args) { byte[] byteArray = new byte[10 * 1024 * 1024]; } }
输出结果:
[GC (Allocation Failure) [PSYoungGen: 1810K->496K(2560K)] 1810K->880K(9728K), 0.0011201 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 496K->496K(2560K)] 880K->888K(9728K), 0.0009199 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 392K->750K(7168K)] 888K->750K(9728K), [Metaspace: 3087K->3087K(1056768K)], 0.0068392 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 750K->750K(9728K), 0.0004801 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 750K->734K(7168K)] 750K->734K(9728K), [Metaspace: 3087K->3087K(1056768K)], 0.0069433 secs] [Times: user=0.09 sys=0.02, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.test.HelloGCDemo.main(HelloGCDemo.java:5)
Heap
PSYoungGen total 2560K, used 122K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 5% used [0x00000000ffd00000,0x00000000ffd1eab8,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 734K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 10% used [0x00000000ff600000,0x00000000ff6b7800,0x00000000ffd00000)
Metaspace used 3193K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 347K, capacity 388K, committed 512K, reserved 1048576K
4.5 常用基础参数SurvivorRatio
调节新生代中 eden 和 S0、S1的空间比例,默认为 -XX:SuriviorRatio=8,Eden:S0:S1 = 8:1:1
假如设置成 -XX:SurvivorRatio=4,则为 Eden:S0:S1 = 4:1:1
SurvivorRatio值就是设置eden区的比例占多少,S0和S1相同。
4.6 常用基础参数NewRatio
配置年轻代new 和老年代old 在堆结构的占比
默认:-XX:NewRatio=2 新生代占1,老年代2,年轻代占整个堆的1/3
-XX:NewRatio=4:新生代占1,老年代占4,年轻代占整个堆的1/5,
NewRadio值就是设置老年代的占比,剩下的1个新生代。
新生代特别小,会造成频繁的进行GC收集。
4.7 常用基础参数MaxTenuringThreshold
晋升到老年代的对象年龄。
SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区,部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认为15),最终如果还是存活,就存入老年代。
这里就是调整这个次数的,默认是15,并且设置的值 在 0~15之间。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻对象不经过Survivor区,直接进入老年代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大的值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概念。
5 强引用 软引用 弱引用 虚引用
5.1 强引用
Reference类以及继承派生的类
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
举例:
public class StrongReferenceDemo { public static void main(String[] args) { Object obj1 = new Object(); Object obj2 = obj1; obj1 = null; System.gc(); System.out.println(obj2); } }
输出结果:
java.lang.Object@677327b6
obj2这样的引用就是强引用
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。
5.2 软引用
软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
对于只有软引用的对象来说,当系统内存充足时它不会被回收,当系统内存不足时它会被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
当内存充足的时候,软引用不用回收:
public class SoftReferenceDemo { public static void softRefMemoryEnough(){ Object obj1 = new Object(); SoftReference<Object> softReference = new SoftReference<Object>(obj1); obj1 = null; System.gc(); System.out.println(obj1); System.out.println(softReference); } public static void main(String[] args) { softRefMemoryEnough(); } }
输出结果:
null
java.lang.ref.SoftReference@677327b6
当内存不够用时,软引用就会被回收:
public class SoftReferenceDemo { public static void softRefMemoryEnough(){ Object obj1 = new Object(); SoftReference<Object> softReference = new SoftReference<Object>(obj1); obj1 = null; System.gc(); System.out.println(obj1); System.out.println(softReference); } /** * JVM配置,故意产生大对象并配置小的内存,让它的内存不够用了导致OOM,看软引用的回收情况 * -Xms5m -Xmx5m -XX:+PrintGCDetails */ public static void softRefMemoryNotEnough(){ Object obj1 = new Object(); SoftReference<Object> softReference = new SoftReference<Object>(obj1); obj1 = null; try { byte[] bytes = new byte[50 * 1024 * 1024]; } catch (Exception e){ e.printStackTrace(); } finally { System.out.println(obj1); System.out.println(softReference.get()); } } public static void main(String[] args) { //softRefMemoryEnough(); softRefMemoryNotEnough(); } }
输出结果:
[GC (Allocation Failure) [PSYoungGen: 1024K->504K(1536K)] 1024K->744K(5632K), 0.0016299 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1281K->512K(1536K)] 1521K->872K(5632K), 0.0009603 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 512K->512K(1536K)] 872K->888K(5632K), 0.0007720 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 512K->0K(1536K)] [ParOldGen: 376K->750K(4096K)] 888K->750K(5632K), [Metaspace: 3094K->3094K(1056768K)], 0.0059642 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 750K->750K(5632K), 0.0003527 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 750K->734K(4096K)] 750K->734K(5632K), [Metaspace: 3094K->3094K(1056768K)], 0.0073387 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
null
null
5.3 弱引用
弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,
对于只有弱引用的对象来说,只要垃圾回收机制一运行不管JVM的内存空间是否足够,都会回收该对象占用的内存。
public class WeakReferenceDemo { public static void main(String[] args) { Object obj1 = new Object(); WeakReference<Object> weakReference = new WeakReference<>(obj1); System.out.println(obj1); System.out.println(weakReference.get()); obj1 = null; System.gc(); System.out.println("=========="); System.out.println(obj1); System.out.println(weakReference.get()); } }
输出结果:
java.lang.Object@677327b6
java.lang.Object@677327b6
==========
null
null
5.4 软引用和弱引用的适用场景
场景:假如有一个应用需要读取大量的本地图片
如果每次读取图片都从硬盘读取则会严重影响性能
如果一次性全部加载到内存中,又可能造成内存溢出
此时使用软引用可以解决这个问题。
设计思路:使用HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占的空间,从而有效地避免了OOM的问题。
Map<String, SoftReference<String>> imageCache = new HashMap<String, SoftReference<Bitmap>>()
5.5 WeakHashMap案例演示和解析
public class WeakHashmapDemo { public static void main(String[] args) { myHashMap(); System.out.println("======================="); myWeakHashMap(); } public static void myHashMap(){ HashMap<Integer, String> map = new HashMap<>(); Integer key = new Integer("1"); String value = "hello"; map.put(key,value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map); } public static void myWeakHashMap(){ WeakHashMap<Integer, String> map = new WeakHashMap<>(); Integer key = new Integer("2"); String value = "hello weakHashmap"; map.put(key,value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map); } }
输出结果:
{1=hello}
{1=hello}
{1=hello}
=======================
{2=hello weakHashmap}
{2=hello weakHashmap}
{}
5.6 虚引用简介
虚引用需要java.lang.ref.PhantomReference类来实现。
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
PhantomReference的gei方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比fihalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
5.7 ReferenceQueue引用队列介绍
回收前需要被引用的,用队列保存下。
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(referenceQueue.poll()); System.out.println("==================="); obj = null; System.gc(); Thread.sleep(500); System.out.println(obj); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); } }
输出结果:
java.lang.Object@677327b6
java.lang.Object@677327b6
null
===================
null
null
java.lang.ref.WeakReference@14ae5a5
5.8 虚引用PhantomReference
Java提供了4种引用类型,在垃圾回收的时候,都有自己各自的特点。
ReferenceQueue是用来配合引用工作的,没有ReferenceQueue一样可以运行。
创建引用的时候可以指定关联的队列,当Gc释放对象内存的时候,会将引用加入到引用队列,如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动这相当于是一种通知机制。
当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVW允许我们在对象被销毁后,做一些我们自己想做的事情。
public class PhantomReferenceDemo { public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(obj1, referenceQueue); System.out.println(obj1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); System.out.println("========================"); obj1 = null; System.gc(); Thread.sleep(500); System.out.println(obj1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); } }
输出结果:
java.lang.Object@677327b6
null
null
========================
null
null
java.lang.ref.PhantomReference@14ae5a5
5.9 GCRoots和四大引用小总结
6 SOFE之StackOverflowError
JVM中常见的两种错误
StackoverFlowError
java.lang.StackOverflowError
OutofMemoryError
java.lang.OutOfMemoryError:java heap space
java.lang.OutOfMemoryError:GC overhead limit exceeeded
java.lang.OutOfMemoryError:Direct buffer memory
java.lang.OutOfMemoryError:unable to create new native thread
java.lang.OutOfMemoryError:Metaspace
StackOverflowError的展现
public class StackOverFlowErrorDemo { public static void main(String[] args) { main(args); } }
输出结果:
Exception in thread "main" java.lang.StackOverflowError
at com.test.StackOverFlowErrorDemo.main(StackOverFlowErrorDemo.java:5)
at com.test.StackOverFlowErrorDemo.main(StackOverFlowErrorDemo.java:5)
at com.test.StackOverFlowErrorDemo.main(StackOverFlowErrorDemo.java:5)
at com.test.StackOverFlowErrorDemo.main(StackOverFlowErrorDemo.java:5)
7 OOM-OutOfMemoryError
7.1 OOM之Java heap space
设置:-Xms10m -Xmx10m
public class JavaHeapSpaceDemo { public static void main(String[] args) { byte[] bytes = new byte[30 * 1024 * 1024]; } }
输出结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.test.oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:5)
7.2 OOM之GC overhead limit exceeded
GC overhead limit exceeded:超出GC开销限制
GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC 都只回收了不到2%的极端情况下才会抛出。
假如不抛出GC overhead limit错误会发生什么情况呢?那就是GC清理的这么点内存很快会再次填满,迫使cc再次执行。这样就形成恶性循环,CPU使用率一直是100%,而Gc却没有任何成果。
public class GCOverheadDemo { /** * -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m * @param args */ public static void main(String[] args) { int i = 0; List<String> list = new ArrayList<>(); try { while (true) { list.add(String.valueOf(++i)); } } catch (Exception e) { e.printStackTrace(); } } }
输出结果:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3087)
at com.test.oom.GCOverheadDemo.main(GCOverheadDemo.java:13)
7.3 OOM之Direct buffer memory
导致原因:
写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避兔了在Java堆和Native堆中来回复制数据。
ByteBuffer.allocate(capability) 第一种方式是分配VM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。
ByteBuffer.allocateDirect(capability) 第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。
但如果不断分配本地内存,堆内存很少使用,那么JV就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。
public class DirectBufferMemoryDemo { /** * -Xms5m -Xmx5m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { System.out.println(String.format("配置的maxDirectMemory: %.2f MB",// sun.misc.VM.maxDirectMemory() / 1024.0 / 1024)); TimeUnit.SECONDS.sleep(3); ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024); } }
输出结果:
[GC (Allocation Failure) [PSYoungGen: 1024K->488K(1536K)] 1024K->732K(5632K), 0.0010399 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1512K->496K(1536K)] 1756K->876K(5632K), 0.0008317 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
配置的maxDirectMemory: 5.00 MB
[GC (Allocation Failure) [PSYoungGen: 1520K->480K(1536K)] 1900K->1204K(5632K), 0.0034020 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (System.gc()) [PSYoungGen: 682K->496K(1536K)] 1406K->1320K(5632K), 0.0025532 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 824K->1102K(4096K)] 1320K->1102K(5632K), [Metaspace: 4086K->4086K(1056768K)], 0.0258919 secs] [Times: user=0.25 sys=0.00, real=0.03 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:658)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at com.test.oom.DirectBufferMemoryDemo.main(DirectBufferMemoryDemo.java:18)
7.4 OOM之unable to create new native thread故障演示
不能够创建更多的新的线程了,也就是说创建线程的上限达到了
高并发请求服务器时,经常会出现异常java.lang.OutOfMemoryError:unable to create new native thread,准确说该native thread异常与对应的平台有关
导致原因:
应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限
服务器并不允许你的应用程序创建这么多线程,linux系统默认运行单个进程可以创建的线程为1024个,如果应用创建超过这个数量,就会报 java.lang.OutOfMemoryError:unable to create new native thread
解决方法:
想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
对于有的应用,确实需要创建很多线程,远超过linux系统默认1024个线程限制,可以通过修改Linux服务器配置,扩大linux默认限制
public class UnableCreateNewThreadDemo { public static void main(String[] args) { for (int i = 0; ; i++) { System.out.println("************** i = " + i); new Thread(() -> { try { TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }, String.valueOf(i)).start(); } } }
Windows环境运行输出结果:
************** i = 28306
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:714)
at com.test.oom.UnableCreateNewThreadDemo.main(UnableCreateNewThreadDemo.java:15)
7.5 OOM之unable to create new native thread上限调整
非root用户登录Linux系统(CentOS)测试
服务器级别调参调优
查看系统线程限制数目
ulimit -u
修改系统线程限制数目
vim /etc/security/limits.d/90-nproc.conf
打开后发现除了root,其他账户都限制在1024个
假如我们想要张三这个用卢运行,希望他生成的线程多一些,我们可以如下配置
7.6 OOM之Metaspace
使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:MetaspaceSize为21810376B(大约20.8M)
Java 8及之后的版本使用Metaspace来替代永久代
Metaspace是方法区在Hotspot 中的实现,它与持久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存也即在Java8中, classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace native memory。
永久代(Java8后被原空向Metaspace取代了)存放了以下信息:
虚拟机加载的类信息
常量池
静态变量
即时编译后的代码
模拟Metaspace空间溢出,我们借助CGLib直接操作字节码运行时不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的。
public class OOMEMetaspaceDemo { // 静态类 static class OOMObject {} /** * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m * * @param args */ public static void main(final String[] args) { // 模拟计数多少次以后发生异常 int i =0; try { while (true) { i++; // 使用Spring的动态字节码技术 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, args); } }); enhancer.create(); } } catch (Throwable e) { System.out.println("发生异常的次数:" + i); e.printStackTrace(); } finally { } } }
输出结果:
发生异常的次数:569
java.lang.OutOfMemoryError: Metaspace
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:348)
at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:117)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
at com.lun.jvm.OOMEMetaspaceDemo.main(OOMEMetaspaceDemo.java:37)
8 垃圾回收器
8.1 垃圾收集器回收种类
GC算法(引用计数/复制/标清/标整)是内存回收的方法论,垃圾收集器就是算法落地实现。
因为目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集
4种主要垃圾收集器
Serial
Parallel
CMS
G1
8.2 串行并行并发G1四大垃圾回收方式
串行垃级回收器(Serial) - 它为单线程环境设计且值使用一个线程进行垃圾收集,会暂停所有的用户线程,只有当垃圾回收完成时,才会重新唤醒主线程继续执行。所以不适合服务器环境。
并行垃圾回收器(Parallel) - 多个垃圾收集线程并行工作,此时用户线程也是阻塞的,适用于科学计算 / 大数据处理等弱交互场景,也就是说Serial 和 Parallel其实是类似的,不过是多了几个线程进行垃圾收集,但是主线程都会被暂停,但是并行垃圾收集器处理时间,肯定比串行的垃圾收集器要更短。
并发垃圾回收器(CMS) - 用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替执行),不需要停顿用户线程,互联网公司都在使用,适用于响应时间有要求的场景。
G1垃圾回收器 - G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收。
ZGC(Java 11的,了解)
串行,并行,并发GC小总结(G1稍后)
8.3 如何查看默认的垃圾收集器
java -XX:+PrintCommandLineFlags -version
输出结果:
C:\Users\abc>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266613056 -XX:MaxHeapSize=4265808896 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)
从结果看到-XX:+UseParallelGC,也就是说默认的垃圾收集器是并行垃圾回收器。
或者 jps -l 得出Java程序号
jinfo -flags (Java程序号)
8.4 JVM默认的垃圾收集器有哪些
Java中一共有7大垃圾收集器
年轻代GC
UserSerialGC:串行垃圾收集器
UserParallelGC:并行垃圾收集器
UseParNewGC:年轻代的并行垃圾回收器
老年代GC
UserSerialOldGC:串行老年代垃圾收集器(已经被移除)
UseParallelOldGC:老年代的并行垃圾回收器
UseConcMarkSweepGC:(CMS)并发标记清除
老嫩通吃
UseG1GC:G1垃圾收集器
8.5 GC之7大垃圾收集器概述
垃圾收集器就来具体实现这些GC算法并实现内存回收。
不同厂商、不同版本的虚拟机实现差别很大,HotSpot中包含的收集器如下图所示:
新生代
串行GC(Serial)
并行GC(ParNew)
并行回收GC(Parallel)
老年代
CMS
Serial Old
Parallel Old
G1 新生代和老年代都有
8.6 GC之约定参数说明
DefNew:Default New Generation
Tenured:Old
ParNew:Parallel New Generation
PSYoungGen:Parallel Scavenge
ParOldGen:Parallel Old Generation
Server/Client模式分别是什么意思?
使用范围:一般使用Server模式,Client模式基本不会使用
操作系统
32位的Window操作系统,不论硬件如何都默认使用Client的JVM模式
32位的其它操作系统,2G内存同时有2个cpu以上用Server模式,低于该配置还是Client模式
64位只有Server模式
C:\Users\abc>java -version
java version "1.8.0_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)
8.7 GC之Serial收集器
一句话:一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
STW: Stop The World
串行收集器是最古老,最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾收集过程中可能会产生较长的停顿(Stop-The-World”状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
对应JVM参数是:-XX:+UseSerialGC
开启后会使用:Serial(Young区用) + Serial Old(Old区用)的收集器组合
表示:新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC
public class GCDemo { public static void main(String[] args) { System.out.println("*******GCDemo"); try { String str = "atguigu"; while (true) { str += str + new Random().nextInt(777777) + new Random().nextInt(888888); str.intern(); } } catch (Exception e) { e.printStackTrace(); } } }
输出结果:
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
*******GCDemo
[GC (Allocation Failure) [DefNew: 2666K->319K(3072K), 0.0027472 secs] 2666K->992K(9920K), 0.0028018 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2680K->317K(3072K), 0.0011588 secs] 3352K->1695K(9920K), 0.0011913 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2852K->3K(3072K), 0.0006832 secs] 4230K->1991K(9920K), 0.0007088 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 1884K->6K(3072K), 0.0010337 secs] 3872K->3209K(9920K), 0.0010712 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2446K->6K(3072K), 0.0010655 secs] 5650K->5641K(9920K), 0.0010911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2515K->2515K(3072K), 0.0000119 secs][Tenured: 5634K->3385K(6848K), 0.0026601 secs] 8149K->3385K(9920K), [Metaspace: 3356K->3356K(1056768K)], 0.0027238 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 2431K->0K(3072K), 0.0005763 secs][Tenured: 5817K->4601K(6848K), 0.0022283 secs] 5817K->4601K(9920K), [Metaspace: 3356K->3356K(1056768K)], 0.0028456 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 4601K->4407K(6848K), 0.0025304 secs] 4601K->4407K(9920K), [Metaspace: 3356K->3356K(1056768K)], 0.0025651 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:647)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.test.oom.GCDemo.main(GCDemo.java:11)
Heap
def new generation total 3072K, used 160K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 5% used [0x00000000ff600000, 0x00000000ff628030, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
tenured generation total 6848K, used 4407K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 64% used [0x00000000ff950000, 0x00000000ffd9df38, 0x00000000ffd9e000, 0x0000000100000000)
Metaspace used 3388K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K
8.8 GC之ParNew收集器
一句话:使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World暂停其他所有的工作线程直到它收集结束。
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Seria收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多Java虚拟机运行在Server模式下新生代的默认垃圾收集器。
常用对应JVM参数:-XX:+UseParNewGC启用ParNew收集器,只影响新生代的收集,不影响老年代。
开启上述参数后,会使用:ParNew(Young区)+ Serial Old的收集器组合,新生代使用复制算法,老年代采用标记-整理算法
但是,ParNew+Tenured这样的搭配,Java8已经不再被推荐
备注:-XX:ParallelGCThreads限制线程数量,默认开启和CPU数目相同的线程数。
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC
public class GCDemo { public static void main(String[] args) { System.out.println("*******GCDemo"); try { String str = "atguigu"; while (true) { str += str + new Random().nextInt(777777) + new Random().nextInt(888888); str.intern(); } } catch (Exception e) { e.printStackTrace(); } } }
输出结果:
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
*******GCDemo
[GC (Allocation Failure) [ParNew: 2656K->320K(3072K), 0.0008949 secs] 2656K->1088K(9920K), 0.0009358 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2648K->319K(3072K), 0.0006588 secs] 3416K->1824K(9920K), 0.0006872 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2854K->30K(3072K), 0.0010394 secs] 4359K->3341K(9920K), 0.0010672 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1887K->20K(3072K), 0.0008710 secs] 5198K->5729K(9920K), 0.0008988 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2426K->2426K(3072K), 0.0000108 secs][Tenured: 5708K->3995K(6848K), 0.0025879 secs] 8135K->3995K(9920K), [Metaspace: 3344K->3344K(1056768K)], 0.0026322 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2480K->34K(3072K), 0.0006775 secs] 6476K->6427K(9920K), 0.0007054 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2432K->2432K(3072K), 0.0000114 secs][Tenured: 6393K->4620K(6848K), 0.0033206 secs] 8825K->4620K(9920K), [Metaspace: 3358K->3358K(1056768K)], 0.0033661 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 4620K->4357K(6848K), 0.0026846 secs] 4620K->4357K(9920K), [Metaspace: 3358K->3358K(1056768K)], 0.0027170 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:647)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.test.oom.GCDemo.main(GCDemo.java:11)
Heap
par new generation total 3072K, used 159K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 5% used [0x00000000ff600000, 0x00000000ff627fa0, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
to space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
tenured generation total 6848K, used 4357K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 63% used [0x00000000ff950000, 0x00000000ffd91658, 0x00000000ffd91800, 0x0000000100000000)
Metaspace used 3390K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K
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
8.9 GC之Parallel收集器
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=实际个数
复用上一节GCDemo
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC
输出结果:
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
*******GCDemo
[GC (Allocation Failure) [PSYoungGen: 2045K->480K(2560K)] 2045K->900K(9728K), 0.0011702 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2220K->480K(2560K)] 2640K->1707K(9728K), 0.0010769 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2377K->456K(2560K)] 4820K->3202K(9728K), 0.0011679 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1783K->0K(2560K)] [ParOldGen: 6390K->3806K(7168K)] 8174K->3806K(9728K), [Metaspace: 3343K->3343K(1056768K)], 0.0075776 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 1288K->96K(2560K)] 6309K->6331K(9728K), 0.0006559 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 96K->0K(2560K)] [ParOldGen: 6235K->3209K(7168K)] 6331K->3209K(9728K), [Metaspace: 3357K->3357K(1056768K)], 0.0061975 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 20K->0K(2560K)] 5660K->5639K(9728K), 0.0003544 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 5639K->4424K(7168K)] 5639K->4424K(9728K), [Metaspace: 3357K->3357K(1056768K)], 0.0079309 secs] [Times: user=0.09 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 4424K->4424K(8704K), 0.0002588 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 4424K->4405K(7168K)] 4424K->4405K(8704K), [Metaspace: 3357K->3357K(1056768K)], 0.0078444 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:647)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.test.oom.GCDemo.main(GCDemo.java:11)
Heap
PSYoungGen total 1536K, used 71K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 6% used [0x00000000ffd00000,0x00000000ffd11e00,0x00000000ffe00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 7168K, used 4405K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 61% used [0x00000000ff600000,0x00000000ffa4d490,0x00000000ffd00000)
Metaspace used 3389K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K
8.10 GC之ParallelOld收集器
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。
复用上一节GCDemo
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelOldGC
输出结果:
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelOldGC
*******GCDemo
[GC (Allocation Failure) [PSYoungGen: 2045K->483K(2560K)] 2045K->867K(9728K), 0.0010490 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2224K->511K(2560K)] 2608K->1400K(9728K), 0.0010450 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2425K->352K(2560K)] 4529K->2760K(9728K), 0.0009995 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1680K->304K(2560K)] 7735K->6967K(9728K), 0.0007555 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 304K->0K(2560K)] [ParOldGen: 6663K->3807K(7168K)] 6967K->3807K(9728K), [Metaspace: 3351K->3351K(1056768K)], 0.0074382 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 1286K->144K(2560K)] 6310K->6383K(9728K), 0.0006030 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 144K->0K(2560K)] [ParOldGen: 6239K->3224K(7168K)] 6383K->3224K(9728K), [Metaspace: 3357K->3357K(1056768K)], 0.0033178 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 20K->0K(1536K)] 5676K->5655K(8704K), 0.0003095 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 5655K->5655K(9216K), 0.0002930 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 5655K->4426K(7168K)] 5655K->4426K(9216K), [Metaspace: 3357K->3357K(1056768K)], 0.0072647 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4426K->4426K(9216K), 0.0002344 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4426K->4407K(7168K)] 4426K->4407K(9216K), [Metaspace: 3357K->3357K(1056768K)], 0.0071868 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:647)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.test.oom.GCDemo.main(GCDemo.java:11)
Heap
PSYoungGen total 2048K, used 71K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 6% used [0x00000000ffd00000,0x00000000ffd11e00,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 7168K, used 4407K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 61% used [0x00000000ff600000,0x00000000ffa4de48,0x00000000ffd00000)
Metaspace used 3389K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K
8.10 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) - 清除GCRoots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象,由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
优点:并发收集低停顿。
缺点:并发执行,对CPU资源压力大,采用的标记清除算法会导致大量碎片。
由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间。
标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认O,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。
复用上一节GCDemo
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
输出结果:
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=3497984 -XX:MaxTenuringThreshold=6 -XX:NewSize=3497984 -XX:OldPLABSize=16 -XX:OldSize=6987776 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
*******GCDemo
[GC (Allocation Failure) [ParNew: 2635K->320K(3072K), 0.0023859 secs] 2635K->1000K(9920K), 0.0024485 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2583K->320K(3072K), 0.0008402 secs] 3263K->1685K(9920K), 0.0008778 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2757K->33K(3072K), 0.0013170 secs] 4123K->3146K(9920K), 0.0013557 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1836K->10K(3072K), 0.0006770 secs] 4949K->4286K(9920K), 0.0007088 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Initial Mark) [1 CMS-initial-mark: 4276K(6848K)] 6623K(9920K), 0.0003413 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-mark-start]
[GC (Allocation Failure) [ParNew (promotion failed): 2347K->2353K(3072K), 0.0005285 secs][CMS[CMS-concurrent-mark: 0.001/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
(concurrent mode failure): 4276K->3681K(6848K), 0.0053157 secs] 6623K->3681K(9920K), [Metaspace: 3317K->3317K(1056768K)], 0.0058874 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [ParNew: 2428K->40K(3072K), 0.0006832 secs] 6109K->6053K(9920K), 0.0007168 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Initial Mark) [1 CMS-initial-mark: 6013K(6848K)] 8382K(9920K), 0.0001468 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-preclean-start]
[GC (Allocation Failure) [ParNew: 2368K->2368K(3072K), 0.0000148 secs][CMS[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
(concurrent mode failure): 6013K->4277K(6848K), 0.0027295 secs] 8382K->4277K(9920K), [Metaspace: 3357K->3357K(1056768K)], 0.0027944 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [CMS: 4277K->4258K(6848K), 0.0029918 secs] 4277K->4258K(9920K), [Metaspace: 3357K->3357K(1056768K)], 0.0030373 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:647)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.test.oom.GCDemo.main(GCDemo.java:11)
Heap
par new generation total 3072K, used 159K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 5% used [0x00000000ff600000, 0x00000000ff627f30, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
concurrent mark-sweep generation total 6848K, used 4258K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3389K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K
8.11 GC之SerialOld收集器
Serial Old是Serial垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client默认的java虚拟机默认的年老代垃圾收集器。
在Server模式下,主要有两个用途(了解,版本已经到8及以后):
在JDK1.5之前版本中与新生代的Parallel Scavenge 收集器搭配使用。(Parallel Scavenge + Serial Old )
作为老年代版中使用CMS收集器的后备垃圾收集方案。
复用上一节GCDemo
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialOldGC
输出结果:
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Unrecognized VM option 'UseSerialOldGC'
Did you mean '(+/-)UseSerialGC'?
在Java8中,-XX:+UseSerialOldGC不起作用。
8.12 GC之如何选择垃圾收集器
组合的选择
单CPU或者小内存,单机程序
-XX:+UseSerialGC
多CPU,需要最大的吞吐量,如后台计算型应用
-XX:+UseParallelGC(这两个相互激活)
-XX:+UseParallelOldGC
多CPU,追求低停顿时间,需要快速响应如互联网应用
-XX:+UseConcMarkSweepGC
-XX:+ParNewGC
8.13 GC之G1收集器
复用上一节GCDemo
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC
输出结果:
略
以前收集器特点:
- 年轻代和老年代是各自独立且连续的内存块;
- 年轻代收集使用单eden+s0+s1进行复机算法;
- 老年代收集必须扫描整个老年代区域;
- 都是以尽可能少而快速地执行GC为设计原则。
G1是什么
G1 (Garbage-First)收集器,是一款面向服务端应用的收集器:
从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。另外,它还具有以下特性:
像CMS收集器一样,能与应用程序线程并发执行。
整理空闲空间更快。
需要更多的时间来预测GC停顿时间。
不希望牺牲大量的吞吐性能。
不需要更大的Java Heap。
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器-G1垃圾收集器。
G1是在2012年才在jdk1.7u4中可用。oracle官方计划在JDK9中将G1变成默认的垃圾收集器以替代CMS。它是一款面向服务端应用的收集器,主要应用在多CPU和大内存服务器环境下,极大的减少垃圾收集的停顿时间,全面提升服务器的性能,逐步替换java8以前的CMS收集器。
主要改变是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region ,每个region从1M到32M不等。一个region有可能属于Eden,Survivor或者Tenured内存区域。
特点:
G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW。
G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。
宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region),可以近似理解为一个围棋的棋盘。
G1收集器里面讲整个的内存区都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换。
8.14 GC之G1底层原理
Region区域化垃圾收集器 - 最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。
核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小,在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为: 32 M B ∗ 2048 = 65536 M B = 64 G 32MB*2048=65536MB=64G 32MB∗2048=65536MB=64G内存
G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。
这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。
在G1中,还有一种特殊的区域,叫Humongous区域。
如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
回收步骤
G1收集器下的Young GC
针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会部会晋升到Old区。
Survivor区的数据移动到新的Survivor区,部会数据晋升到Old区。
最后Eden区收拾干净了,GC结束,用户的应用程序继续执行。
4步过程:
- 初始标记:只标记GC Roots能直接关联到的对象
- 并发标记:进行GC Roots Tracing的过程
- 最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
- 筛选回收:根据时间来进行价值最大化的回收
8.15 GC之G1参数配置及和CMS的比较
-XX:+UseG1GC
-XX:G1HeapRegionSize=n:设置的G1区域的大小。值是2的幂,范围是1MB到32MB。目标是根据最小的Java堆大小划分出约2048个区域。
-XX:MaxGCPauseMillis=n:最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间。
-XX:InitiatingHeapOccupancyPercent=n:堆占用了多少的时候就触发GC,默认为45。
-XX:ConcGCThreads=n:并发GC使用的线程数。
-XX:G1ReservePercent=n:设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认值是10%。
开发人员仅仅需要声明以下参数即可:
三步归纳:开始G1+设置最大内存+设置最大停顿时间
- -XX:+UseG1GC
2. -Xmx32g
3. -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=n:最大GC停顿时间单位毫秒,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
G1和CMS比较
G1不会产生内碎片
是可以精准控制停顿。该收集器是把整个堆(新生代、老年代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。
8.16 JVMGC结合SpringBoot微服务优化简介
1. IDEA开发微服务工程。
2. Maven进行clean package。
3. 要求微服务启动的时候,同时配置我们的JVM/GC的调优参数。
4. 公式:java -server jvm的各种参数 -jar 第1步上面的jar/war包名。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!