一、谈谈你对GCRoots的理解
1.什么是垃圾?
简单的说就是内存中已经不再被使用到的空间就是垃圾。
2.要进行垃圾回收,如何判断一个对象是否可以被回收?
3.引用计数法
Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。
因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,给对象中添加一个引用计数器,
每当有一个地方引用它,计数器值加1,
每当有一个引用失效时,计数器值减1。
任何时刻计数器值为零的对象就是不可能再被使用的,那么这个对象就是可回收对象。
那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。
该算法存在但目前无人用了,解决不掉循环引用的问题,了解即可。
4.枚举根节点做可达性分析(根搜索路径)
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。
所谓“GC roots”或者说tracing GC的“根集合”就是一组必须活跃的引用。
基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活;没有被遍历到的就自然被判定为死亡。
5.Java中可以作为GC Roots的对象
- 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。(举例:方法在栈里面执行,方法中有局部变量引用了其它对象)
- 方法区中的类静态属性引用的对象。(举例:static修饰的类变量在方法区中,它引用的对象就是)
- 方法区中常量引用的对象。(举例:static final引用的对象)
- 本地方法栈中JNI(Native方法)引用的对象。
二、JVM的标配参数和X参数
官方文档
如何盘点查看JVM系统默认值?
1、JVM的三种参数类型:
- 标配参数
- X参数(了解)
- XX参数(超重点,下面会细讲)
(1)JVM的XX参数之布尔类型
公式:-XX:+某个属性值 或者-XX:-某个属性值 (+表示开启,-表示关闭)
如何查看一个正在运行中的java程序,它的某个jvm参数是否开启?具体值是多少?
jps -l 查看一个正在运行中的java程序,得到Java程序号。jinfo -flag PrintGCDetails (Java程序号 ) 查看它的某个jvm参数(如PrintGCDetails )是否开启。jinfo -flags (Java程序号 ) 查看它的所有jvm参数
(2)JVM的XX参数之设值类型
公式:-XX:属性key=属性值value
举例:
- -XX:MetaspaceSize=128m
- -XX:MaxTenuringThreshold=15
(3)JVM的XX参数之两个经典参数
我们经常使用-Xms和-Xmx,它们两个是X参数呢还是XX参数呢?它们是XX参数,只是因为经常使用被缩写了:
- -Xms等价于-XX:InitialHeapSize,初始大小内存,默认物理内存1/64
- -Xmx等价于-XX:MaxHeapSize,最大分配内存,默认为物理内存1/4
2、JVM查看初始默认值
1.如何查看初始默认参数值?
公式:java -XX:+PrintFlagsInitial
C:\Users\abc>java -XX:+PrintFlagsInitial
[Global flags]
int ActiveProcessorCount = -1 {product} {default}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product} {default}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} {default}
uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} {default}
uintx AdaptiveSizePolicyInitializingSteps = 20 {product} {default}
uintx AdaptiveSizePolicyOutputInterval = 0 {product} {default}
uintx AdaptiveSizePolicyWeight = 10 {product} {default}
...
12345678910
2.如何查看修改更新参数值
公式:java -XX:+PrintFlagsFinal
C:\Users\abc>java -XX:+PrintFlagsFinal
...
size_t HeapBaseMinAddress = 2147483648 {pd product} {default}
bool HeapDumpAfterFullGC = false {manageable} {default}
bool HeapDumpBeforeFullGC = false {manageable} {default}
bool HeapDumpOnOutOfMemoryError = false {manageable} {default}
ccstr HeapDumpPath = {manageable} {default}
uintx HeapFirstMaximumCompactionCount = 3 {product} {default}
uintx HeapMaximumCompactionInterval = 20 {product} {default}
uintx HeapSearchSteps = 3 {product} {default}
size_t HeapSizePerGCThread = 43620760 {product} {default}
bool IgnoreEmptyClassPaths = false {product} {default}
bool IgnoreUnrecognizedVMOptions = false {product} {default}
uintx IncreaseFirstTierCompileThresholdAt = 50 {product} {default}
bool IncrementalInline = true {C2 product} {default}
size_t InitialBootClassLoaderMetaspaceSize = 4194304 {product} {default}
uintx InitialCodeCacheSize = 2555904 {pd product} {default}
size_t InitialHeapSize := 268435456 {product} {ergonomic}
...
12345678910111213141516171819
=表示默认,:=表示修改过的。
3.如何运行java命令的同时修改更新参数值并且打印出参数 PrintFlagsFinal举例,运行java命令的同时,修改更新MetaspaceSize为512m,并且打印出参数
java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m HelloWorld
...
size_t MetaspaceSize := 536870912 {pd product} {default}
...
123
4.查看默认的垃圾回收器
java -XX:+PrintCommandLineFlags -version 查看默认的垃圾回收器(尽管它打印的有很多信息,但是我们更喜欢用它查看默认的垃圾回收器)
C:\Users\abc>java -XX:+PrintCommandLineFlags -version
-XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=8 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=266613056 -XX:MarkStackSize=4
194304 -XX:MaxHeapSize=4265808896 -XX:MinHeapSize=6815736 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+Seg
mentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation
openjdk version "15.0.1" 2020-10-20
OpenJDK Runtime Environment (build 15.0.1+9-18)
OpenJDK 64-Bit Server VM (build 15.0.1+9-18, mixed mode)
1234567
3.常用参数:
(1)查看栈内存大小
-Xss 等价于-XX:ThreadStackSize,设置单个线程栈的大小,一般默认为512k~1024K
(2)查看元空间大小
-Xmn:设置年轻代大小
-XX:MetaspaceSize 设置元空间大小
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制
元空间是方法区,方法区就是装类的信息、类的元素、常量池等。
典型设置案例
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails-XX:+UseSerialGC - 上述代码含义:设置初始堆内存128M,最大堆内存4G,初始栈大小1024K,元空间512M,开启PrintCommandLineFlags功能,开启PrintGCDetails功能,开启UseSerialGC串行垃圾回收器功能(原来是并行的)
(3)输出GC的详细收集日志信息
-XX:+PrintGCDetails 输出GC的详细收集日志信息
public class PrintGCDetailsDemo {
public static void main(String[] args) throws InterruptedException {
byte[] byteArray = new byte[50 * 1024 * 1024];
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
}
输出结果:
[GC (Allocation Failure) [PSYoungGen: 778K->480K(2560K)] 778K->608K(9728K), 0.0029909 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 480K->480K(2560K)] 608K->616K(9728K), 0.0007890 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 136K->518K(7168K)] 616K->518K(9728K), [Metaspace: 2644K->2644K(1056768K)], 0.0058272 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 518K->518K(9728K), 0.0002924 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 518K->506K(7168K)] 518K->506K(9728K), [Metaspace: 2644K->2644K(1056768K)], 0.0056906 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.lun.jvm.PrintGCDetailsDemo.main(PrintGCDetailsDemo.java:9)
Heap
PSYoungGen total 2560K, used 61K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 3% used [0x00000000ffd00000,0x00000000ffd0f748,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 506K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 7% used [0x00000000ff600000,0x00000000ff67ea58,0x00000000ffd00000)
Metaspace used 2676K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 285K, capacity 386K, committed 512K, reserved 1048576K
上面的日志怎么看?下图给你详细解释:
(4)SurvivorRatio
- 新生代默认占堆空间的1/3,老年代默认占堆内存的2/3
- 调节新生代中 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相同。
(5)NewRatio
- 配置年轻代new 和老年代old 在堆结构的占比
- 默认:-XX:NewRatio=2 新生代占1,老年代2,新生代占整个堆的1/3
- 假如设置成-XX:NewRatio=4 新生代占1,老年代占4,新生代占整个堆的1/5,
- NewRadio值就是设置老年代的占比,剩下的1个新生代。
- 新生代特别小,会造成频繁的进行GC收集。
(6)MaxTenuringThreshold
-XX:MaxTenuringThreshold=? 设置晋升到老年代的对象年龄。
- SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区,部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认为15),最终如果还是存活,就存入老年代。
- 这里就是调整这个次数的,默认是15,并且设置的值 在 0~15之间。
-XX:MaxTenuringThreshold=0 :设置垃圾最大年龄。如果设置为0的话,则年轻对象不经过Survivor区,直接进入老年代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大的值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概念。
三、关于四大引用
1、强引用Reference
Reference类以及继承派生的类
当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
Object obj1 = new Object();
强引用是我们最常见的普通对象引用,在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。 你如果令obj1=null ,那么它就可以被垃圾回收了,否则它一直不会被回收
2、软引用SoftReference
对于只有软引用的对象来说,
- 当系统内存充足时它不会被回收,
- 当系统内存不足时它会被回收。
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
当内存充足的时候,软引用不用回收:
public class SoftReferenceDemo {
public static void softRefMemoryEnough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
public static void softRefMemoryNoEnough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
try {
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
public static void main(String[] args) {
softRefMemoryEnough();
}
}
3、弱引用WeakReference
对于只有弱引用的对象来说,只要垃圾回收机制一运行不管JVM的内存空间是否足够,都会回收该对象占用的内存。
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1);
System.out.println(weakReference.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(weakReference.get());
}
}
输出结果:
java.lang.Object@15db9742
java.lang.Object@15db9742
null
null
4、软引用和弱引用的适用场景
(1)场景:假如有一个应用需要读取大量的本地图片
- 如果读取图片的方式是每加载一张读一次,这会严重影响性能
- 如果一次性全部加载到内存中,又可能造成内存溢出
- 所以我们可以尽可能的加载这些图片到内存,内存不够了就回收了它们,应用要读取大不了再加载一次;内存够用就不用回收。
此时使用软引用可以解决这个问题。
设计思路:使用HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占的空间,从而有效地避免了OOM的问题。
Map<String, SoftReference<String>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
(2)WeakHashMap案例演示和解析
当key为空时,这个k-v键值对将会被移除
public class WeakHashMapDemo {
public static void main(String[] args) {
myHashMap();
System.out.println("==========");
myWeakHashMap();
}
private static void myHashMap() {
Map<Integer, String> map = new HashMap<>();
Integer key = new Integer(1);
String value = "HashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
}
private static void myWeakHashMap() {
Map<Integer, String> map = new WeakHashMap<>();
Integer key = new Integer(1);
String value = "WeakHashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
}
}
输出结果:
{1=HashMap}
{1=HashMap}
==========
{1=WeakHashMap}
{}
5、虚引用PhantomReference
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
在强引用、软引用、弱引用我们都可以通过get()方法得到它的对象,但是虚引用PhantomReference的gei方法总是返回null,因此无法访问对应的引用对象。它就是监控对象的回收状态的。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比fihalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
6、ReferenceQueue引用队列
弱引用、软引用、虚引用它们在垃圾回收时它们自己就没了,但是在GC之前它们会被放到引用队列里面。
(1)演示弱引用被放到引用队列里
public class ReferenceQueueDemo {
public static void main(String[] args) {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());
System.out.println("========执行GC操作后==========");
o1 = null;
System.gc();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());
}
}
输出结果:
java.lang.Object@15db9742
java.lang.Object@15db9742
null
========执行GC操作后==========
null
null
java.lang.ref.WeakReference@6d06d69c
可以看到,GC之前,强引用o1有值,弱引用weakReference有值,引用队列referenceQueue里面是空的; 在GC之后,强引用o1为空,弱引用weakReference为空,引用队列referenceQueue里面有值; 为什么?因为弱引用、软引用、虚引用它们在垃圾回收时它们自己就没了,但是在GC之前它们会被放到引用队列里面。
(2)演示虚引用被放到引用队列里
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
System.out.println("==================");
o1 = null;
System.gc();
Thread.sleep(500) ;
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
}
}
输出结果:
java.lang.Object@15db9742
null
null
==================
null
null
java.lang.ref.PhantomReference@6d06d69c
可以看到,GC之前,强引用o1有值,虚引用无论如何都获取不到值,引用队列referenceQueue里面是空的; 在GC之后,强引用o1为空,虚引用无论如何都获取不到值,引用队列referenceQueue里面有值; 为什么?因为弱引用、软引用、虚引用它们在垃圾回收时它们自己就没了,但是在GC之前它们会被放到引用队列里面。
7、GCRoots和四大引用小总结
四、OOM
1、总说
JVM中常见的两种Error
- 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
2、SOFE之StackOverflowError
栈内存溢出原因:深度的方法调用(一直递归调用方法)
public class StackOverflowErrorDemo {
public static void main(String[] args) {
main(args);
}
}
输出结果:
Exception in thread "main" java.lang.StackOverflowError
at com.lun.jvm.StackOverflowErrorDemo.main(StackOverflowErrorDemo.java:6)
at com.lun.jvm.StackOverflowErrorDemo.main(StackOverflowErrorDemo.java:6)
at com.lun.jvm.StackOverflowErrorDemo.main(StackOverflowErrorDemo.java:6)
...
3、OOM之Java heap space
堆内存溢出原因:对象太多了,太大了
public class OOMEJavaHeapSpaceDemo {
public static void main(String[] args) {
byte[] array = new byte[80 * 1024 * 1024];
}
}
输出结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.lun.jvm.OOMEJavaHeapSpaceDemo.main(OOMEJavaHeapSpaceDemo.java:6)
4、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 OOMEGCOverheadLimitExceededDemo {
public static void main(String[] args) {
int i = 0;
List<String> list = new ArrayList<>();
try {
while(true) {
list.add(String.valueOf(++i).intern());
}
} catch (Exception e) {
System.out.println("***************i:" + i);
e.printStackTrace();
throw e;
}
}
}
输出结果
[GC (Allocation Failure) [PSYoungGen: 2048K->498K(2560K)] 2048K->1658K(9728K), 0.0033090 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2323K->489K(2560K)] 3483K->3305K(9728K), 0.0020911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2537K->496K(2560K)] 5353K->4864K(9728K), 0.0025591 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2410K->512K(2560K)] 6779K->6872K(9728K), 0.0058689 secs] [Times: user=0.09 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 6360K->6694K(7168K)] 6872K->6694K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0894928 secs] [Times: user=0.42 sys=0.00, real=0.09 secs]
[Full GC (Ergonomics) [PSYoungGen: 2048K->1421K(2560K)] [ParOldGen: 6694K->6902K(7168K)] 8742K->8324K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0514932 secs] [Times: user=0.34 sys=0.00, real=0.05 secs]
[Full GC (Ergonomics) [PSYoungGen: 2048K->2047K(2560K)] [ParOldGen: 6902K->6902K(7168K)] 8950K->8950K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0381615 secs] [Times: user=0.13 sys=0.00, real=0.04 secs]
...省略89行...
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7044K->7044K(7168K)] 9092K->9092K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0360935 secs] [Times: user=0.25 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7046K->7046K(7168K)] 9094K->9094K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0360458 secs] [Times: user=0.38 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7048K->7048K(7168K)] 9096K->9096K(9728K), [Metaspace: 2651K->2651K(1056768K)], 0.0353033 secs] [Times: user=0.11 sys=0.00, real=0.04 secs]
***************i:147041
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7050K->7048K(7168K)] 9098K->9096K(9728K), [Metaspace: 2670K->2670K(1056768K)], 0.0371397 secs] [Times: user=0.22 sys=0.00, real=0.04 secs]
java.lang.OutOfMemoryError: GC overhead limit exceeded
[Full GC (Ergonomics) at java.lang.Integer.toString(Integer.java:401)
[PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7051K->7050K(7168K)] 9099K->9097K(9728K), [Metaspace: 2676K->2676K(1056768K)], 0.0434184 secs] [Times: user=0.38 sys=0.00, real=0.04 secs]
at java.lang.String.valueOf(String.java:3099)
at com.lun.jvm.OOMEGCOverheadLimitExceededDemo.main(OOMEGCOverheadLimitExceededDemo.java:19)
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
[Full GC (Ergonomics) [PSYoungGen: 2047K->0K(2560K)] [ParOldGen: 7054K->513K(7168K)] 9102K->513K(9728K), [Metaspace: 2677K->2677K(1056768K)], 0.0056578 secs] [Times: user=0.11 sys=0.00, real=0.01 secs]
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3099)
at com.lun.jvm.OOMEGCOverheadLimitExceededDemo.main(OOMEGCOverheadLimitExceededDemo.java:19)
Heap
PSYoungGen total 2560K, used 46K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0bb90,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 513K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 7% used [0x00000000ff600000,0x00000000ff6807f0,0x00000000ffd00000)
Metaspace used 2683K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 285K, capacity 386K, committed 512K, reserved 1048576K
123456789101112131415161718192021222324252627282930313233
5、OOM之Metaspace
使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:MetaspaceSize为21810376B(大约20.8M)
Java 8及之后的版本使用Metaspace来替代永久代。
Metaspace是方法区在Hotspot 中的实现,它与持久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存也即在Java8中,被存储在叫做Metaspace native memory中。
永久代(Java8后被原空向Metaspace取代了)存放了以下信息:
模拟Metaspace空间溢出,我们借助CGLib直接操作字节码运行时不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的。
首先添加CGLib依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
public class OOMEMetaspaceDemo {
static class OOMObject {}
public static void main(final String[] args) {
int i =0;
try {
while (true) {
i++;
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)
123456789
五、垃圾收集器
1、垃圾收集器的种类
GC算法(引用计数/复制/标清/标整)是内存回收的方法论,垃圾收集器就是算法落地实现。
(1)4种主要垃圾收集器
- Serial串行回收
- Parallel并行回收
- CMS并发标记清除
- G1
(2)四大垃圾回收方式:
- 串行垃级回收器(Serial) - 只使用一个线程进行垃圾收集,并且它进行垃圾收集期间会暂停所有的用户线程,只有当垃圾回收完成时,才会重新唤醒主线程继续执行。所以不适合服务器环境。
- 并行垃圾回收器(Parallel) - 多个垃圾收集线程并行工作,此时用户线程也是阻塞的,适用于科学计算 / 大数据处理等弱交互场景,也就是说Serial 和 Parallel其实是类似的,不过是多了几个线程进行垃圾收集,但是主线程都会被暂停,但是并行垃圾收集器处理时间,肯定比串行的垃圾收集器要更短。
- 并发垃圾回收器(CMS) - 用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替执行),不需要停顿用户线程,互联网公司都在使用,适用于响应时间有要求的场景。
- G1垃圾回收器 - G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收。
串行,并行,并发GC小总结(G1稍后)
2、如何查看默认的垃圾收集器
(1)方式一:
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,也就是说默认的垃圾收集器是并行垃圾回收器。
(2)方式二:
jps -l
得出Java程序号
jinfo -flags (Java程序号)
3、JVM默认的垃圾收集器有哪些
Java中一共有7大垃圾收集器
- 年轻代GC
- SerialGC:串行垃圾收集器
- ParallelGC:并行垃圾收集器
- ParNewGC:年轻代的并行垃圾回收器
- 老年代GC
- SerialOldGC:老年代的串行垃圾收集器(已经被移除)
- ParallelOldGC:老年代的并行垃圾收集器
- ConcMarkSweepGC:(CMS)并发标记清除
- 老嫩通吃
这幅图很重要,下面的所有组合都是基于这幅图
4、补充一个常识
5、新生代GC之Serial收集器
一句话:一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
STW: Stop The World
Serial收集器对应JVM参数是:-XX:+UseSerialGC,加上这个参数就会开启Serial收集器,那么Young区用Seria,Old区用Serial Old,表示新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法
6、新生代GC之ParNew收集器
一句话:使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World暂停其他所有的工作线程直到它收集结束。
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Seria收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多Java虚拟机运行在Server模式下新生代的默认垃圾收集器。
常用对应JVM参数:-XX:+UseParNewGC启用ParNew收集器,只影响新生代的收集,不影响老年代。
开启上述参数后,Young区会使用ParNew,Old区还是会使用Serial Old的收集器,新生代使用复制算法,老年代采用标记-整理算法
但是,ParNew+Serial Old这样的搭配,Java8已经不再被推荐
7、新生代GC之Parallel收集器
ParNew收集器是只是新生代使用并行,老年代使用串行; 而Parallel收集器是新生代和老年代都是并行。
Parallel收集器重点关注的是:
- 可控制的吞吐量(比如程序运行100分钟,垃圾收集时间1分钟,吞吐量就是99% )。高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务。
- 自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别。(自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMillis)或最大的吞吐量。
常用JVM参数:-XX:+UseParallelGC或-XX:+UseParallelOldGC都可以,使用的都是Parallel收集器。开启该参数后:新生代使用新生代Parallel Scavenge+老年代Parallel Old。
8、老年代GC之ParallelOld收集器
上面我们说过-XX:+UseParallelGC或-XX:+UseParallelOldGC都可以,使用的都是Parallel收集器。开启该参数后:新生代使用新生代Parallel Scavenge+老年代Parallel Old。
9、老年代GC之CMS收集器
1.简介
CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器。
适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
CMS非常适合地内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
2.收集器使用
开启CMS收集器的JVM参数:-XX:+UseConcMarkSweepGC开启该参数后会自动将-XX:+UseParNewGC打开,也就是说开启该参数后,Young区使用ParNew,Old区用CMS +Serial Old。
4步过程:
- 初始标记(CMS initial mark) - 只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
- 并发标记(CMS concurrent mark)和用户线程一起 - 进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象。
- 重新标记(CMS remark)- 为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正。
- 并发清除(CMS concurrent sweep) - 清除GCRoots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象,由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
4.优缺点(重点)
优点:并发收集低停顿。
缺点:并发执行,对CPU资源压力大,采用的标记清除算法会导致大量碎片。
由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间。
标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认O,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。
5.CMS收集器打印的日志
-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
[GC (Allocation Failure) [ParNew: 2274K->319K(3072K), 0.0016975 secs] 2274K->1043K(9920K), 0.0017458 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2844K->8K(3072K), 0.0010921 secs] 3568K->2287K(9920K), 0.0011138 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2040K->2K(3072K), 0.0037625 secs] 4318K->4257K(9920K), 0.0037843 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Initial Mark) [1 CMS-initial-mark: 4255K(6848K)] 6235K(9920K), 0.0003380 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-mark-start]
[GC (Allocation Failure) [ParNew: 2024K->2K(3072K), 0.0013295 secs] 6279K->6235K(9920K), 0.0013596 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 1979K->1979K(3072K), 0.0000116 secs][CMS[CMS-concurrent-mark: 0.001/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
(concurrent mode failure): 6233K->2508K(6848K), 0.0031737 secs] 8212K->2508K(9920K), [Metaspace: 2657K->2657K(1056768K)], 0.0032232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 2025K->2025K(3072K), 0.0000154 secs][CMS: 6462K->6461K(6848K), 0.0020534 secs] 8488K->6461K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0021033 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [CMS: 6461K->6448K(6848K), 0.0020383 secs] 6461K->6448K(9920K), [Metaspace: 2658K->2658K(1056768K)], 0.0020757 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Initial Mark) [1 CMS-initial-mark: 6448K(6848K)] 6448K(9920K), 0.0001419 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Final Remark) [YG occupancy: 50 K (3072 K)][Rescan (parallel) , 0.0002648 secs][weak refs processing, 0.0000173 secs][class unloading, 0.0002671 secs][scrub symbol table, 0.0004290 secs][scrub string table, 0.0001593 secs][1 CMS-remark: 6448K(6848K)] 6499K(9920K), 0.0012107 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-sweep-start]
java.lang.OutOfMemoryError: Java heap space
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.<init>(String.java:207)
at java.lang.StringBuilder.toString(StringBuilder.java:407)
at com.lun.jvm.GCDemo.main(GCDemo.java:22)
Heap
par new generation total 3072K, used 106K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 3% used [0x00000000ff600000, 0x00000000ff61a820, 0x00000000ff8b0000)
from space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
to space 320K, 0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
concurrent mark-sweep generation total 6848K, used 6447K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2689K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
12345678910111213141516171819202122232425262728293031323334
10、老年代GC之SerialOld收集器
Serial Old是Serial垃圾收集器老年代版本,这个收集器也主要是运行在 Client默认的java虚拟机默认的年老代垃圾收集器。
在Server模式下,主要有两个用途(了解,版本已经到8及以后):
- 在JDK1.5之前版本中与新生代的Parallel Scavenge 收集器搭配使用。(Parallel Scavenge + Serial Old )
- 作为老年代版中使用CMS收集器的后备垃圾收集方案。
在Java8中,-XX:+UseSerialOldGC已经不起作用了。
11、GC之如何选择垃圾收集器
组合的选择
- 单CPU或者小内存,单机程序
- 多CPU,需要最大的吞吐量,如后台计算型应用
- -XX:+UseParallelGC
- -XX:+UseParallelOldGC
- 多CPU,追求低停顿时间,需要快速响应如互联网应用
- -XX:+UseConcMarkSweepGC
- -XX:+ParNewGC
参数 | 新生代垃圾收集器 | 新生代算法 | 老年代垃圾收集器 | 老年代算法 |
---|
-XX:+UseSerialGC | SerialGC | 复制 | SerialOldGC | 标记整理 | -XX:+UseParNewGC | ParNew | 复制 | SerialOldGC | 标记整理 | -XX:+UseParallelGC | Parallel [Scavenge] | 复制 | Parallel Old | 标记整理 | -XX:+UseConcMarkSweepGC | ParNew | 复制 | CMS + Serial Old的收集器组合,Serial Old作为CMS出错的后备收集器 | 标记清除 | -XX:+UseG1GC | G1整体上采用标记整理算法 | 局部复制 | | |
12、GC之G1收集器(重点)
VM参数:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC
输出结果:
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0015787 secs]
[Parallel Time: 0.8 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 106.4, Avg: 106.5, Max: 106.5, Diff: 0.1]
[Ext Root Scanning (ms): Min: 0.2, Avg: 0.3, Max: 0.5, Diff: 0.4, Sum: 2.2]
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.3, Max: 0.3, Diff: 0.3, Sum: 2.1]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.4]
[Termination Attempts: Min: 1, Avg: 5.3, Max: 10, Diff: 9, Sum: 42]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.3]
[GC Worker Total (ms): Min: 0.6, Avg: 0.6, Max: 0.7, Diff: 0.1, Sum: 4.9]
[GC Worker End (ms): Min: 107.1, Avg: 107.1, Max: 107.1, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.3 ms]
[Other: 0.5 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.2 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.3 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 4096.0K(4096.0K)->0.0B(4096.0K) Survivors: 0.0B->1024.0K Heap: 7073.4K(10.0M)->2724.8K(10.0M)]
[Times: user=0.02 sys=0.02, real=0.00 secs]
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0004957 secs]
[GC concurrent-mark-start]
[GC concurrent-mark-end, 0.0001071 secs]
[GC remark [Finalize Marking, 0.0001876 secs] [GC ref-proc, 0.0002450 secs] [Unloading, 0.0003675 secs], 0.0011690 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC cleanup 4725K->4725K(10M), 0.0004907 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC pause (G1 Humongous Allocation) (young), 0.0009748 secs]
[Parallel Time: 0.6 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 111.8, Avg: 111.9, Max: 112.2, Diff: 0.5]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.8]
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 1.7]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[Termination Attempts: Min: 1, Avg: 3.3, Max: 5, Diff: 4, Sum: 26]
[GC Worker Other (ms): Min: 0.1, Avg: 0.1, Max: 0.1, Diff: 0.0, Sum: 0.8]
[GC Worker Total (ms): Min: 0.1, Avg: 0.5, Max: 0.6, Diff: 0.5, Sum: 3.6]
[GC Worker End (ms): Min: 112.3, Avg: 112.3, Max: 112.4, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 0.2 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 1024.0K(4096.0K)->0.0B(4096.0K) Survivors: 1024.0K->1024.0K Heap: 6808.1K(10.0M)->2595.2K(10.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0006211 secs]
[Parallel Time: 0.2 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 113.3, Avg: 113.3, Max: 113.4, Diff: 0.1]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 1.0]
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.1, Max: 1, Diff: 1, Sum: 1]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.3]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[GC Worker Total (ms): Min: 0.1, Avg: 0.2, Max: 0.2, Diff: 0.1, Sum: 1.4]
[GC Worker End (ms): Min: 113.5, Avg: 113.5, Max: 113.5, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 0.3 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 0.0B(4096.0K)->0.0B(2048.0K) Survivors: 1024.0K->1024.0K Heap: 4595.9K(10.0M)->4557.3K(10.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC concurrent-root-region-scan-start]
[GC pause (G1 Humongous Allocation) (young)[GC concurrent-root-region-scan-end, 0.0001112 secs]
[GC concurrent-mark-start]
, 0.0006422 secs]
[Root Region Scan Waiting: 0.0 ms]
[Parallel Time: 0.2 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 114.2, Avg: 114.3, Max: 114.4, Diff: 0.2]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.7]
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.1, Max: 1, Diff: 1, Sum: 1]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[GC Worker Total (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.9]
[GC Worker End (ms): Min: 114.4, Avg: 114.4, Max: 114.4, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 0.3 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 0.0B(2048.0K)->0.0B(2048.0K) Survivors: 1024.0K->1024.0K Heap: 4557.3K(10.0M)->4547.6K(10.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) 4547K->4527K(10M), 0.0023437 secs]
[Eden: 0.0B(2048.0K)->0.0B(3072.0K) Survivors: 1024.0K->0.0B Heap: 4547.6K(10.0M)->4527.6K(10.0M)], [Metaspace: 2658K->2658K(1056768K)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) 4527K->4513K(10M), 0.0021281 secs]
[Eden: 0.0B(3072.0K)->0.0B(3072.0K) Survivors: 0.0B->0.0B Heap: 4527.6K(10.0M)->4514.0K(10.0M)], [Metaspace: 2658K->2658K(1056768K)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC concurrent-mark-abort]
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at com.lun.jvm.GCDemo.main(GCDemo.java:22)
Heap
garbage-first heap total 10240K, used 4513K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
region size 1024K, 1 young (1024K), 0 survivors (0K)
Metaspace used 2689K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
①以前收集器特点:
- 年轻代和老年代是各自独立且连续的内存块;
- 年轻代收集使用单eden+s0+s1进行复机算法;
- 老年代收集必须扫描整个老年代区域;
- 都是以尽可能少而快速地执行GC为设计原则。
②G1是什么
G1 (Garbage-First)收集器,是一款面向服务端应用的收集器:
③G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器-G1垃圾收集器。
G1主要改变是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的运行在不同代之间前后切换。
13、G1的相关知识
适用场景:
- 同时注重吞吐量和低延迟(响应时间)。
- 超大堆内存(内存大的),会将堆内存划分为多个大小相等的区域。
- 整体上是标记-整理算法,两个区域之间是复制算法。
相关参数:JDK8 并不是默认开启的,需要参数开启:
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time
123456
① G1垃圾回收阶段
新生代伊甸园垃圾回收—–>内存不足,新生代回收+并发标记—–>回收新生代伊甸园、幸存区、老年代内存——>新生代伊甸园垃圾回收(重新开始)。
② Young Collection 新生代垃圾回收
分区算法region
分代是按对象的生命周期划分,分区则是将堆空间划分连续几个不同小区间,每一个小区间独立回收,可以控制一次回收多少个小区间,方便控制 GC 产生的停顿时间。
E:伊甸园 S:幸存区 O:老年代
垃圾回收时,会把伊甸园(E)的幸存对象复制到幸存区(S):
当幸存区(s)中的对象也比较多触发垃圾回收,且幸存对象寿命超过阈值时,幸存区(S)中的一部分对象(寿命达到阈值)会晋升到老年代(O),寿命未达到阈值的会被再次复制到另一个幸存区(S):
③ Young Collection + CM 新生代垃圾回收和并发标记
CM:并发标记!
- 在 Young GC 时会对 GC Root 进行初始标记。
- 在老年代占用堆内存的比例达到阈值时,进行并发标记(不会STW),阈值可以根据用户来进行设定:
-XX:InitiatingHeapOccupancyPercent=percent
④ Mixed Collection 混合收集
会对E、S 、O 进行全面的回收。
-XX:MaxGCPauseMillis=ms
问:为什么有的老年代被拷贝了,有的没拷贝?
因为指定了最大停顿时间,如果对所有老年代都进行回收,耗时可能过高。为了保证时间不超过设定的最大停顿时间,会根据最大停顿时间,有选择的回收最有价值的老年代(回收后,能够得到更多内存)。
G1在老年代内存不足时(老年代所占内存超过阈值):
- 如果垃圾产生速度慢于垃圾回收速度,不会触发Full GC,还是并发地进行清理。
- 如果垃圾产生速度快于垃圾回收速度,便会触发Full GC。
⑤ 新生代垃圾回收时的跨代引用问题:
回顾新生代垃圾回收的过程:要先找到根对象,然后根对象可达性分析,再找到存活对象,存活对象进行复制,复制到幸存者区。问题就来了,我们要找新生代的根对象时有一部分根对象是来自老年代的,老年代里的对象一般很多的,难度我们要一个一个遍历去找那个根对象吗?
- 卡表与Remembered Set
- Remembered Set 存在于E中,用于保存新生代对象对应的脏卡:
- 脏卡:O(老年代)被划分为多个区域(一个区域512K),如果该区域引用了新生代对象,则该区域被称为脏卡。
- 我们要找新生代的根对象时有一部分根对象是来自老年代的,有了这样的标记就不用遍历整个老年代了,直接看E里面Remembered Set的数据。
⑥ JDK 8u60 回收巨型对象
- H表示巨型对象,当一个对象占用大于region的一半时,就称为巨型对象。
- G1不会对巨型对象进行拷贝(拷贝代价太大了,浪费时间)。
- 回收时被优先考虑(巨型对象回收了那不是释放了很多内存)。
- G1会跟踪老年代所有引用,如果老年代引用为0的巨型对象就可以在新生代垃圾回收时处理掉。
巨型对象越早回收越好,最好是在新生代的垃圾回收就回收掉~
14、G1的底层原理
(1)底层原理
G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32 M B ? 2048 = 65536 M B = 64 G
G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。
这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。
在G1中,还有一种特殊的区域,叫Humongous区域。
如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
(2)回收步骤
G1收集器下的Young GC
针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
- Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会部会晋升到Old区。
- Survivor区的数据移动到新的Survivor区,部会数据晋升到Old区。
- 最后Eden区收拾干净了,GC结束,用户的应用程序继续执行。
4步过程:
- 初始标记:只标记GC Roots能直接关联到的对象
- 并发标记:进行GC Roots Tracing的过程
- 最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
- 筛选回收:根据时间来进行价值最大化的回收
15、G1参数配置及
- -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
- -Xmx32g
- -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=n:最大GC停顿时间单位毫秒,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
16、G1和CMS比较
- G1不会产生内碎片
- 是可以精准控制停顿。该收集器是把整个堆(新生代、老年代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。
|