1、进行垃圾回收的区域
(1)堆
(2)方法区
栈是线程的私有数据,所以不进行垃圾回收
2、垃圾回收的时间
对这个对象不再引用的时候
public class ReferenceCountingGC { private static final int MB=1024*1024; public Object instance=null; private byte[] size=new byte[2*MB]; public static void main(String[] args) { ReferenceCountingGC referenceCountingGC1=new ReferenceCountingGC(); ReferenceCountingGC referenceCountingGC2=new ReferenceCountingGC(); //循环引用 referenceCountingGC1.instance=referenceCountingGC2; referenceCountingGC2.instance=referenceCountingGC1; referenceCountingGC1=null; referenceCountingGC2=null; System.gc();; } }
[Full GC (System.gc()) [Tenured: 0K->640K(86016K), 0.0045506 secs] 6862K->640K(124736K),
[Metaspace: 3223K->3223K(1056768K)], 0.0046551 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap def new generation total 38784K, used 345K [0x0000000082200000, 0x0000000084c10000, 0x00000000ac150000) eden space 34496K, 1% used [0x0000000082200000, 0x0000000082256548, 0x00000000843b0000) from space 4288K, 0% used [0x00000000843b0000, 0x00000000843b0000, 0x00000000847e0000) to space 4288K, 0% used [0x00000000847e0000, 0x00000000847e0000, 0x0000000084c10000) tenured generation total 86016K, used 640K [0x00000000ac150000, 0x00000000b1550000, 0x0000000100000000) the space 86016K, 0% used [0x00000000ac150000, 0x00000000ac1f0040, 0x00000000ac1f0200, 0x00000000b1550000) Metaspace used 3230K, capacity 4496K, committed 4864K, reserved 1056768K class space used 350K, capacity 388K, committed 512K, reserved 1048576K
(1)引用计数
给对象添加一个引用计数器,每当这个对象进行一次引用,计数器就加1;每当引用失效的时候,计数器就减1。当这个计数器等于0的时候,表示这个对象不会再被引用了,可以作为垃圾进行回收。但是,存在循环引用的时候会导致内存泄漏
3、java的引用类型
(1)强引用
Object obj=new Object();这样的常规引用,只要引用还在就永远不会回收对象,在代码中有明显的new Object()这类引用,只要这种引用还在,垃圾回收器就不会回收它。就算内存不够,抛出OutOfMrmory异常也不会回收对象。
(2)弱引用
生存到下一次垃圾回收之前,无论当前内存是否够用,都回收掉被弱引用关联的对象
(3)软引用
在发生内存溢出之前,进行回收,如果这次回收之后还没有足够的内存,则报OOM。如果内存够用的情况下,不会回收,如果内存不够的话进行
(4)虚引用
不会对对象的生命周期有任何影响,也无法通过它得到对象的实例,唯一的作用也就是在垃圾回收之前收到一个系统通知
4、可达性分析算法
(1)可达与不可达
可达:
不可达:
此时对象已经不再被引用了,也就是说可以被回收了。
(2)在java语言中,可以作为GC Roots的对象(肯定不会变成垃圾被回收的对象)包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
5、标记、清除算法
(1)概念:
该算法包括“标记”和“清除”两个阶段:首先标记出所有要回收的对象,在标记完成后统一回收所有被标记的对象。
(2)缺点:
效率问题,标记和清除的效率都不高
空间问题,标记和清除之后会产生大量不连续的内存碎片
6、复制算法
(1)概念
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已经使用过的内存空间一次清理掉
(2)缺点
内存缩小为原来的一半,内存利用率太低(还要留出另外一半等着复制的时候使用)
(3)优化
为了克服该算法的缺点对该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存叫Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor区不够,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)
7、标记整理算法
(1)概念
标记过程依旧和“标记清除算法”一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后把其他的内存区域清理掉。可以消除内存碎片,但是整理的时候会降低算法的效率
8、分代算法
(1)概念
根据对象存活周期的不同将内存划分为几块
(2)回收原则
一般是把java堆分为新生代和老年代,这样可以根据各个年代的特点采用最适合的收集算法。在新生代中,每次垃圾回收的时候都会回收掉大量的对象,只有少量的对象存活,此时,选用复制算法,只需要付出少量存活对象的复制成本即可完成对象的收集,而老年代中因为对象的存活率高,没有额外空间进行分配担保,就必须使用“标记清理”或“标记整理”算法进行回收。新生代垃圾回收较为频繁,老年代不频繁
堆:
经过几次回收之后会进入老年代
(3)分区
伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。当伊甸区满的时候会触发Minor-GC,采用复制算法将伊甸区的幸存对象和from存活的对象复制到幸存区的to区域,存活的对象年龄加一,当在新生代的生存次数累积到一定程度(最大寿命是15,存储寿命的地方是4bit)可以进入老年代。
幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时(老年代空间不足,先尝试触发Minor-GC,当Minor-GC也不能腾出足够的空间的时候),就会触发一次完全收集(Major-GC或Full-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。
如果老年代仍旧不能分配出来空间,就会报异常
(4)STW
当进行Minor-GC或Full-GC的时候会触发stop the world把用户的其他线程暂停掉,因为Minor-GC的时候牵涉到对象的复制,如果还有其他线程操作该对象就会引起混乱。Full-GC的时间更长,因为它采用标记整理算法,速度更慢。
(5)虚拟机相关参数
9、GC类型
(1)保守式GC
在回收的时候,不对数据进行记录,而是扫描内存,效率低
(2)半保守式GC
在类里面记录信息,然后扫描
(3)精确式GC
OopMap存放运行信息(类型、地址等),回收的时候只需查询Map即可
10、安全点:safepoint
(1)概念
在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots的枚举,但如果每一条指令都生成对应的OopMap那将会需要大量的额外空间,这样GC的空间成本将会变得很高
(2)抢占式中断
在GC发生时,首先把所有的线程全部中断,如果发现有中断的地方不在安全点上,就恢复线程,让它跑到安全点上,必须要等到Java线程都进入到safepoint的时候VMThread才能开始执行GC
位置:
循环的末尾(防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint)
方法返回前
调用方法的call之后
抛出异常的位置
缺点:处于sleep或wait的时候不能到达安全点
(3)主动式中断
当GC需要中断线程的时候,不直接对线程进行操作,仅仅简单地设置一个标志,发现中断标志为真的时候就自己中断挂起
11、相关参数
与垃圾回收相关的JVM参数:
-Xms / -Xmx --- 堆的初始大小 / 堆的最大大小
-Xmn --- 堆中年轻代的大小
-XX:-DisableExplicitGC --- 让System.gc()不产生任何作用
-XX:+PrintGCDetail --- 打印GC的细节
-XX:+PrintGCDateStamps --- 打印GC操作的时间戳
12、程序分析
(1)jvm参数
最大堆空间:20M
打印GC详情
新生代:10M
垃圾回收器:SerialGC
(2)不对存储区进行任何操作:
public class Test3 { private static final int _512KB=512*1024; private static final int _1MB=1024*1024; private static final int _6MB=6*1024*1024; private static final int _7MB=7*1024*1024; private static final int _8MB=8*1024*1024; public static void main(String[] args) { } }
测试结果:
Heap
def new generation total 9216K, used 2025K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 24% used [0x00000000fec00000, 0x00000000fedfa778, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3224K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 349K, capacity 388K, committed 512K, reserved 1048576K
堆内存:
新生代:def new generation
老年代:tenured generation
(3)将7M的对象放入堆内存:
public class Test3 { private static final int _512KB=512*1024; private static final int _1MB=1024*1024; private static final int _6MB=6*1024*1024; private static final int _7MB=7*1024*1024; private static final int _8MB=8*1024*1024; public static void main(String[] args) { ArrayList<byte[]> list=new ArrayList<>(); list.add(new byte[_7MB]); } }
测试:
[GC (Allocation Failure) [DefNew: 1861K->641K(9216K), 0.0026541 secs] 1861K->641K(19456K), 0.0283396 secs]
[Times: user=0.00 sys=0.00, real=0.03 secs] Heap def new generation total 9216K, used 8055K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 90% used [0x00000000fec00000, 0x00000000ff33d8c0, 0x00000000ff400000) from space 1024K, 62% used [0x00000000ff500000, 0x00000000ff5a0710, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3229K, capacity 4496K, committed 4864K, reserved 1056768K class space used 350K, capacity 388K, committed 512K, reserved 1048576K
因为发生了Minor-GC,幸存代的空间也被占用了
(4)放入8M的对象
public class Test3 { private static final int _512KB=512*1024; private static final int _1MB=1024*1024; private static final int _6MB=6*1024*1024; private static final int _7MB=7*1024*1024; private static final int _8MB=8*1024*1024; public static void main(String[] args) { ArrayList<byte[]> list=new ArrayList<>(); list.add(new byte[_7MB]); list.add(new byte[_512KB]); list.add(new byte[_512KB]); } }
测试:
[GC (Allocation Failure) [DefNew: 1861K->614K(9216K), 0.0029223 secs] 1861K->614K(19456K), 0.0029845 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 8621K->514K(9216K), 0.0059726 secs] 8621K->8292K(19456K), 0.0060135 secs]
[Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 1191K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 8% used [0x00000000fec00000, 0x00000000feca93e0, 0x00000000ff400000)
from space 1024K, 50% used [0x00000000ff400000, 0x00000000ff480848, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 7778K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 75% used [0x00000000ff600000, 0x00000000ffd98a48, 0x00000000ffd98c00, 0x0000000100000000)
Metaspace used 3214K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 348K, capacity 388K, committed 512K, reserved 1048576K
此时,老年代也有占用,一部分对象已经晋升到老年代
(5)大对象直接晋升老年代(新生代放不下)
public class Test3 { private static final int _512KB=512*1024; private static final int _1MB=1024*1024; private static final int _6MB=6*1024*1024; private static final int _7MB=7*1024*1024; private static final int _8MB=8*1024*1024; public static void main(String[] args) { ArrayList<byte[]> list=new ArrayList<>(); list.add(new byte[_8MB]); } }
测试:
Heap def new generation total 9216K, used 2025K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 24% used [0x00000000fec00000, 0x00000000fedfa778, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000) Metaspace used 3229K, capacity 4496K, committed 4864K, reserved 1056768K class space used 350K, capacity 388K, committed 512K, reserved 1048576K
无GC,直接进入老年代
(6)放入两个8M的对象:新生代和老年代都放不下
public class Test3 { private static final int _512KB=512*1024; private static final int _1MB=1024*1024; private static final int _6MB=6*1024*1024; private static final int _7MB=7*1024*1024; private static final int _8MB=8*1024*1024; public static void main(String[] args) { ArrayList<byte[]> list=new ArrayList<>(); list.add(new byte[_8MB]); list.add(new byte[_8MB]); } }
测试:
[GC (Allocation Failure) [DefNew: 1861K->616K(9216K), 0.0016678 secs][Tenured: 8192K->8807K(10240K), 0.0274991 secs] 10053K->8807K(19456K), [Metaspace: 3201K->3201K(1056768K)], 0.0498951 secs] [Times: user=0.00 sys=0.02, real=0.05 secs] [Full GC (Allocation Failure) [Tenured: 8807K->8789K(10240K), 0.0025803 secs] 8807K->8789K(19456K), [Metaspace: 3201K->3201K(1056768K)], 0.0026219 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at pers.zhb.test.Test3.main(Test3.java:15) Heap def new generation total 9216K, used 410K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 5% used [0x00000000fec00000, 0x00000000fec66800, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 8789K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 85% used [0x00000000ff600000, 0x00000000ffe95630, 0x00000000ffe95800, 0x0000000100000000) Metaspace used 3255K, capacity 4496K, committed 4864K, reserved 1056768K class space used 353K, capacity 388K, committed 512K, reserved 1048576K