java基础篇之GC过程

概述

一个简单函数中生成的局部对象,在函数调用结束,就会被释放掉。但是一个对象如果被持有了很久才被释放,它有可能会被晋升到old区,这块内存就会一直被占用,在下次发生fullGC的时候才会被回收,这将是一个漫长的过程,当然这样的情况很多内存不够用的时候,会提前出发fullGC,stop the world这将是我们更不愿意看到的。

  • 晚申请,早释放,尤其是数组中的元素,使用完就可以设置为null
 Object[] objects = new Object[1024];
        for(Object o:objects){
            //业务操作 dododo
            o = null;
  • list,set,map等尽量给定capacity
  • 少开辟新对象,比如Stringbuilder 的重复利用

java垃圾回收

GC(Garbage Collection) 的历史比java还要久远,1960年诞生的Lisp语言当时就在考虑三个问题:
1. 哪些内存需要回收
2. 什么时候回收
3. 如何回收

  • 对于java来说,包括程序计数器,栈内存 ,他们随线程生,随线程灭,方法结束后内存也就回收了(内存泄漏后面在讨论);
  • 堆内存就不同了,一个接口的多个实现类可能需要的内存不同,一个方法的多个分支需要的内存也可能有区别,我们只有在程序运行的时候才知道创建了哪些对象,这部分的内存分配和回收都是动态的。所以堆是我们主要分析的区域。
    下面来回答上面的三个问题
    哪些内存需要回收
    已经“死掉”的对象,需要回收,所以垃圾收集器的第一件工作就是区分哪些对象还在存活。一般有两种策略
    ①引用计数算法, 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。该方法实现简单,效率高,但是它很难它很难解决对象之间相互循环引用的问题。所以,大多数jvm判断对象是否存活基本并没有采取该方法。
    ②可达性分析算法
    目前主流的编程语言(java,C#等)的主流实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
    在这里插入图片描述
    被认为GC Roots的有以下几种
  • 虚拟机栈中引用的对象
  • 方法区中静态属性、常量引用的对象(1.7之后的方法区应该说的是metaSpace?)
  • Native方法引用的对象
    当一个对象被认为“不可达对象” 以后,至少要经过2次标记,才会被真正的回收
    不可达对象->第一次标记和筛选
    需要注意的是
  1. 判断是否执行finalize() 方法的原则是 1)-有没有显示调用finalize方法 2)- 该对象的finalize方法有没有被调用过,也就是说finalize最多只会被调用一次
  2. 进入F-queue队列的对象,jvm会自动创建一个优先级很低的Finalizer线程去执行,但是并不承诺等待finalize方法执行完成
    结论是被jvm认为不可达对象,并且经过了两次标记以后仍然判断回收的

关于何时回收

  • jdk1.5中,当老年代使用了68%之后,便会激活CMS收集器,可以通过-XX:CMSInitiatingOccupancyFraction 来改变
  • jdk1.6中,CMS收集器的启动阈值已经达到92%
  • 大多数情况,对象出生在Eden,当Eden区没有足够空间的时候,触发一次Minor GC
Minor GC 和 Full GC
  • 新生代GC,大多数java对象朝生夕灭,Minor GC 比较频繁,速度也比较快
  • 老年代GC,Major GC 或者叫 Full GC,出现了Full GC,一般也会带着一次 Minor GC(并非一定,比如Parallel Scavenge收集器),Full GC一般比Minor GC 慢10倍以上

在这里插入图片描述

长期存活的对象,最终进入老年代

Eden中的对象经历一次minor GC ,如果还存活的话,进入s1,在s1中熬过一次 minor GC,进入s2,并且年龄增加1岁,当达到15岁(默认值),依然存活的话,进入老年代,这个年龄阈值可以通过-XX:MaxTenuringThreshold=1控制
大对象也可以通过-XX:PretenureSizeThreshold 分配到老年代

内存分配

既然有内存回收,就不得不考虑内存的分配问题,对象主要分配在eden区,如果启动了本地线程分配缓冲,将按线程优先在TLAB上 分配,少数情况下也有可能直接分配到老年代,通过-XX:PretenureSizeThreshold参数可以指定超过一定数值的对象,可以直接分配老年代。
所以开发的时候进来避免大对象,比如很长的字符串,很长的数组

内存泄漏 和 内存溢出

内存泄漏
是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样。
JDK1.6中substring有可能会导致内存泄漏,1.7中修复,返回的是 new String(value, beginIndex, subLen)

public String substring(int beginIndex, int endIndex) {
	if (beginIndex < 0) {
	    throw new StringIndexOutOfBoundsException(beginIndex);
	}
	if (endIndex > count) {
	    throw new StringIndexOutOfBoundsException(endIndex);
	}
	if (beginIndex > endIndex) {
	    throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
	}
	return ((beginIndex == 0) && (endIndex == count)) ? this :
	    new String(offset + beginIndex, endIndex - beginIndex, value); //这里的value,还是父字符串中的,引用还会一直存在,无法被回收
    }

1.7中

public static char[] copyOfRange(char[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
        char[] copy = new char[newLength];
        System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
        return copy;
    }

内存溢出
内存溢出(out of memory ): 通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出,举栗几种常见情况

java.lang.OutOfMemoryError: java heap space

堆栈溢出,如果代码没有确保没有问题,可以增大Xmx

java.lang.OutOfMemoryError:GC over head limit exceeded

GC很频繁,但是依然无法满意的回收,这种情况一般是产生了很多不可以被释放的对象,有可能是引用使用不当导致,或申请大对象导致

java.lang.OutOfMemoryError: PermGen space

永久代内存不足,1.8以后已无此错误
可以通过-XX:PermSize和-XX:MaxPermSize来设置

java.lang.OutOfMemoryError: Direct buffer memory

如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题,-XX:MaxDirectMemorySize

java.lang.StackOverflowError

如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么jvm将抛出StackOverflowError
-Xss设置问题,1.4以前默认是256K,1.5以后是1M

Stop-The-World

导致GC必须停顿所有线程的是,枚举根节点时,必须在一个能确保一致性的快照中进行——这里“一致性”的意思是指在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,所以在号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。
停顿以后,也不需要逐个检查全局的变量,虚拟机有办法知道哪些地方有对象引用,在hotspot中,一组OomMap数据结构,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,实际上,HotSpot也的确没有为每条指令都生成OopMap,只是在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint)。
即程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停

安全区域

程序不执行就是没有分配CPU时间,典型的例子就是线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到 安全点 去中断挂起,JVM也显然不太可能等待线程重新被分配CPU时间。对于这种情况,就需要安全区域(Safe Region)来解决。
安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。

GC参数和GC日志
jdk1.8
  • SurvivorRatio
    设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
/**
     * 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
     * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails  -XX:SurvivorRatio=8
     * */
    @Test
    public  void test() {
        byte[] allocation1, allocation2, allocation3,allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];//出现一次young GC
        // 新生代一共9m,eden已经占用6m,无法分配4m
    }

在这里插入图片描述

  • PSYoungGen PS是Parallel Scavenge收集器的缩写,它配套的新生代称为PSYoungGen,新生代又分化eden space、from space和to space这三部分
  • GC – 用来区分是 Minor GC 还是 Full GC 的标志(Flag). 这里的 GC 表明本次发生的是 Minor GC
  • Allocation Failure – 引起垃圾回收的原因 :年轻代中没有足够的空间能够存储新的数据
  • user – 此次垃圾回收, 垃圾收集线程消耗的所有CPU时间(Total CPU time)
  • sys – 操作系统调用(OS call) 以及等待系统事件的时间
  • real – 应用程序暂停的时间(Clock time),并发情况并不一定等于前两项的和
  • 测试是在1.8环境下,已经没了PSPermGen,代替的是metaSpace
附录 相关参数

在这里插入图片描述
在这里插入图片描述

jdk1.6
 private static final int _1MB = 1024 * 1024;

	    public static void main(String[] args) {
	    	 {
	             byte[] b = new byte[20 * 1024 * 1024];
	         }
	         System.gc();
	    }

在这里插入图片描述

  • 这是serial收集器,它称新生代为 “Default New Generation”,所以显示的是DefNew
  • 如果是parNew收集器,新生代叫 ParNew
  • 如果是parallel Scavenge,新生代叫PSYoungGen
  • tenured 老年代,与新生代一样,其名称也是收集器命名

5M结果

 private static final int _1MB = 1024 * 1024;
	    public static void main(String[] args) {
	    	 {
	             byte[] b = new byte[5 * 1024 * 1024];
	         }
	         System.gc();
	    }

在这里插入图片描述
4M及以下

private static final int _1MB = 1024 * 1024;
	    public static void main(String[] args) {
	    	 {
	             byte[] b = new byte[4 * 1024 * 1024];
	         }
	         System.gc();
	    }

在这里插入图片描述

一次实际案例分析
配置为:
-server -Xms8g -Xmx8g -XX:PermSize=96m -XX:MaxPermSize=512m -Xmn512m
 -XX:+UseConcMarkSweepGC
 -XX:+UseCMSCompactAtFullCollection
 -XX:CMSMaxAbortablePrecleanTime=5000
 -XX:+CMSClassUnloadingEnabled
 -XX:+UseCMSInitiatingOccupancyOnly
 -XX:CMSInitiatingOccupancyFraction=80

2018-10-26T15:04:56.565+0800: 4.199: [GC (Allocation Failure) 2018-10-26T15:04:56.565+0800: 4.199: [ParNew: 436992K->29946K(480640K), 0.0867648 secs] 436992K->29946K(8344960K), 0.0869238 secs] [Times: user=0.16 sys=0.02, real=0.09 secs] 
2018-10-26T15:04:56.655+0800: 4.288: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(7864320K)] 38686K(8344960K), 0.0062361 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
2018-10-26T15:04:56.667+0800: 4.300: [CMS-concurrent-mark-start]
2018-10-26T15:04:56.731+0800: 4.364: [CMS-concurrent-mark: 0.064/0.064 secs] [Times: user=0.16 sys=0.00, real=0.06 secs] 
2018-10-26T15:04:56.731+0800: 4.365: [CMS-concurrent-preclean-start]
2018-10-26T15:04:56.743+0800: 4.377: [CMS-concurrent-preclean: 0.012/0.012 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
2018-10-26T15:04:56.743+0800: 4.377: [CMS-concurrent-abortable-preclean-start]
2018-10-26T15:04:57.943+0800: 5.577: [GC (Allocation Failure) 2018-10-26T15:04:57.943+0800: 5.577: [ParNew: 466938K->31557K(480640K), 0.1959494 secs] 466938K->48131K(8344960K), 0.1960680 secs] [Times: user=0.43 sys=0.03, real=0.20 secs] 
2018-10-26T15:04:58.933+0800: 6.566: [CMS-concurrent-abortable-preclean: 1.090/2.190 secs] [Times: user=4.57 sys=0.46, real=2.19 secs] 
2018-10-26T15:04:58.934+0800: 6.568: [GC (CMS Final Remark) [YG occupancy: 294905 K (480640 K)]2018-10-26T15:04:58.934+0800: 6.568: [Rescan (parallel) , 0.0546321 secs]2018-10-26T15:04:58.989+0800: 6.622: [weak refs processing, 0.0000568 secs]2018-10-26T15:04:58.989+0800: 6.622: [class unloading, 0.0054825 secs]2018-10-26T15:04:58.994+0800: 6.628: [scrub symbol table, 0.0029629 secs]2018-10-26T15:04:58.997+0800: 6.631: [scrub string table, 0.0008593 secs][1 CMS-remark: 16573K(7864320K)] 311479K(8344960K), 0.0650457 secs] [Times: user=0.17 sys=0.00, real=0.07 secs] 
2018-10-26T15:04:59.000+0800: 6.633: [CMS-concurrent-sweep-start]
2018-10-26T15:04:59.028+0800: 6.662: [CMS-concurrent-sweep: 0.029/0.029 secs] [Times: user=0.06 sys=0.00, real=0.03 secs] 
2018-10-26T15:04:59.029+0800: 6.662: [CMS-concurrent-reset-start]
2018-10-26T15:04:59.351+0800: 6.984: [CMS-concurrent-reset: 0.322/0.322 secs] [Times: user=0.53 sys=0.20, real=0.32 secs] 
2018-10-26T15:05:19.770+0800: 27.403: [GC (Allocation Failure) 2018-10-26T15:05:19.770+0800: 27.404: [ParNew: 466302K->30966K(480640K), 0.1325390 secs] 486797K->55887K(8344
960K), 0.1327017 secs] [Times: user=0.33 sys=0.01, real=0.13 secs] 
2018-10-26T15:05:22.288+0800: 29.922: [GC (Allocation Failure) 2018-10-26T15:05:22.289+0800: 29.922: [ParNew: 467958K->42239K(480640K), 0.4066209 secs] 492879K->69742K(8344
960K), 0.4068094 secs] [Times: user=1.04 sys=0.01, real=0.40 secs] 
2018-10-26T15:05:24.663+0800: 32.297: [GC (Allocation Failure) 2018-10-26T15:05:24.663+0800: 32.297: [ParNew: 479231K->34843K(480640K), 0.1672432 secs] 506734K->77339K(8344
960K), 0.1674183 secs] [Times: user=0.36 sys=0.00, real=0.17 secs] 
2018-10-26T15:05:27.298+0800: 34.932: [GC (Allocation Failure) 2018-10-26T15:05:27.298+0800: 34.932: [ParNew: 471835K->33110K(480640K), 0.1596734 secs] 514331K->80875K(8344
960K), 0.1598419 secs] [Times: user=0.35 sys=0.00, real=0.16 secs] 
2018-10-26T15:05:30.850+0800: 38.483: [GC (Allocation Failure) 2018-10-26T15:05:30.850+0800: 38.483: [ParNew: 470102K->20351K(480640K), 0.1594544 secs] 517867K->77650K(8344
960K), 0.1596283 secs] [Times: user=0.32 sys=0.01, real=0.16 secs] 
2018-10-26T15:05:33.497+0800: 41.131: [GC (Allocation Failure) 2018-10-26T15:05:33.497+0800: 41.131: [ParNew: 457343K->33671K(480640K), 0.1619120 secs] 514642K->90969K(8344
960K), 0.1621137 secs] [Times: user=0.38 sys=0.01, real=0.16 secs] 
2018-10-26T15:05:35.477+0800: 43.111: [GC (Allocation Failure) 2018-10-26T15:05:35.477+0800: 43.111: [ParNew: 470663K->17724K(480640K), 0.2528831 secs] 527961K->94926K(8344
960K), 0.2531465 secs] [Times: user=0.59 sys=0.02, real=0.25 secs] 
2018-10-26T15:05:45.891+0800: 53.524: [GC (Allocation Failure) 2018-10-26T15:05:45.891+0800: 53.524: [ParNew: 454716K->22038K(480640K), 0.0854437 secs] 531918K->99240K(8344
960K), 0.0856540 secs] [Times: user=0.17 sys=0.00, real=0.09 secs] 
2018-10-26T15:18:46.461+0800: 834.094: [GC (Allocation Failure) 2018-10-26T15:18:46.461+0800: 834.094: [ParNew: 459030K->28732K(480640K), 0.0560694 secs] 536232K->105935K(8
344960K), 0.0563257 secs] [Times: user=0.15 sys=0.00, real=0.06 secs] 

频繁触发youngGC,计划Xmn调大至1.5G;
疑似Perm溢出导致的CMS GC ! 老年代使用量很少的时候,触发了CMS GC。
2018年11月13日 调整为

-XX:NewRatio=2  -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m

在这里插入图片描述
可以看到,young GC频率降低很多,目前还未发生full GC,继续观察……

优化前
在这里插入图片描述

-server
-Xms8g-Xmx8g
-XX: MetaspaceSize=96m
-XX: MaxMetaspaceSize=512m
-XX: +UseConcMarkSweepGC
-XX: +UseCMSCompactAtFullCollection
-XX: CMSMaxAbortablePrecleanTime=5000
-XX: +CMSClassUnloadingEnabled
-XX: +UseCMSInitiatingOccupancyOnly
-XX: CMSInitiatingOccupancyFraction=80
-XX: +HeapDumpOnOutOfMemoryError

2018-11-06T18:24:40.121+0800: 1497.851: [GC (Allocation Failure) 2018-11-06T18:24:40.121+0800: 1497.851: [ParNew: 2463365K->106768K(2563200K), 0.0927095 secs] 2611310K->254
712K(8155648K), 0.0929522 secs] [Times: user=0.25 sys=0.00, real=0.10 secs] 
2018-11-06T18:25:03.242+0800: 1520.972: [GC (Allocation Failure) 2018-11-06T18:25:03.242+0800: 1520.972: [ParNew: 2437008K->143681K(2563200K), 0.1007557 secs] 2584952K->291
626K(8155648K), 0.1009596 secs] [Times: user=0.27 sys=0.00, real=0.11 secs] 
2018-11-06T18:26:01.932+0800: 1579.662: [GC (Allocation Failure) 2018-11-06T18:26:01.932+0800: 1579.662: [ParNew: 2473921K->138595K(2563200K), 0.2314374 secs] 2621866K->286
539K(8155648K), 0.2316750 secs] [Times: user=0.30 sys=0.12, real=0.24 secs] 
2018-11-06T18:27:05.556+0800: 1643.286: [GC (Allocation Failure) 2018-11-06T18:27:05.556+0800: 1643.286: [ParNew: 2468835K->159056K(2563200K), 0.1707605 secs] 2616779K->307
000K(8155648K), 0.1710030 secs] [Times: user=0.36 sys=0.00, real=0.17 secs] 
2018-11-06T18:27:50.473+0800: 1688.203: [GC (Allocation Failure) 2018-11-06T18:27:50.473+0800: 1688.203: [ParNew: 2489296K->135559K(2563200K), 0.2043531 secs] 2637240K->317
512K(8155648K), 0.2045692 secs] [Times: user=0.43 sys=0.02, real=0.20 secs] 
2018-11-06T18:28:44.437+0800: 1742.167: [GC (Allocation Failure) 2018-11-06T18:28:44.437+0800: 1742.167: [ParNew: 2465799K->99561K(2563200K), 0.1079012 secs] 2647752K->2815
13K(8155648K), 0.1081174 secs] [Times: user=0.23 sys=0.00, real=0.11 secs] 

没有设置xmn为512m,young GC 没有那么频繁 (todo 有待验证)

promotion failed

发生Minor GC时,Survivor放不下,对象只能进入老年代,而此时老年代也放不下,promotion failed时老年代CMS还没有机会进行回收,因此会出现下一个问题
concurrent mode failure,就是执行cms过程中,有对象要放入老年代,而老年代空间不足,只能降级Serial old来回收

 [ParNew (promotion failed):……
Full GC无法回收

在这里插入图片描述

206 -207 次的full gc,老年代占用并没有降下来
– 由于Linux特殊的内存机制,判断问题的关键是占用的内存只要不超过-Xmn分配的最大内存即可

Full GC 诱因

可以看到采用Parallel GC的情况下,YGC之前会有两个检查:
1、在YGC前,min(目前新生代已使用的大小,之前平均晋升到old的大小中的较小值) > 旧生代剩余空间大小 ? 不执行YGC,直接执行Full GC : 执行YGC;
2、在YGC执行后,平均晋升到old的大小 > 旧生代剩余空间大小 ? 触发Full GC : 什么都不做

metaspace

2019-05-23T16:14:47.169+0800: 32.014: [Full GC (Metadata GC Threshold) [PSYoungGen: 15803K->0K(356352K)] [ParOldGen: 64990K->38442K(91648K)] 80793K->38442K(448000K), [Metaspace: 20740K->20740K(1069056K)], 0.1066172 secs] [Times: user=0.48 sys=0.00, real=0.11 secs] 
2019-05-23T16:14:53.284+0800: 38.129: [GC (Metadata GC Threshold) [PSYoungGen: 225183K->15473K(480768K)] 263626K->53924K(572416K), 0.0150901 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 

metaspace初始化
-XX:MetaspaceSize=256m -Xms512m -Xmx1g -XX:NewRatio=2 -XX:+UseConcMarkSweepGC

Full GC (Ergonomics)

自适应调优策略

2019-05-23T16:16:55.209+0800: 160.055: [Full GC (Ergonomics) [PSYoungGen: 40416K->0K(644096K)] [ParOldGen: 140205K->132056K(276480K)] 180622K->132056K(920576K), [Metaspace: 60898K->60898K(1103872K)], 0.3728147 secs] [Times: user=1.72 sys=0.01, real=0.37 secs] 
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值