JVM之GC垃圾回收
GC垃圾回收
如何判断对象可以回收
-
引用计数法
如果有对象引用计数加一,没有对象引用,计数减一,如果计数为零,则回收
但是如果存在循环引用,即A对象引用B对象,B对象引用A对象,会造成内存泄漏
-
可达性分析算法
-
java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
-
扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收
-
哪些对象可以作为GG Root?
-
System Class
例如:Object,String,HashMap等
-
Native Stack
-
Thread
-
Busy Monitor
-
-
-
四种引用
-
强引用:new的对象,赋值的对象
Object obj= new Object()//new 的对象都是是强引用
只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了
-
软引用
Object obj= new Object() SoftReference sr = new SoftReference<>(obj);//obj就是软引用
在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
-
弱引用
Object obj= new Object() WeakReference wr = newWeakReference<>(obj);//obj就是软引用
弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
-
虚引用
Object obj= new Object() PhantomReference pr = new PhantomReference <>(obj);//obj就是软引用
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
-
终结器引用
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由Finalizer线程通过终结引用找到被引用对象并调用它的finalize方法,第二次GC时才能回收被引用的对象
-
垃圾回收算法
-
标记清除
标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
- 优点:速度快
- 缺点:容易产生内存碎片
-
标记整理
标记:标记-整理算法从根对象开始遍历整个对象图,标记所有与根对象可达的对象,即被引用的对象。
整理:标记-整理算法会对内存空间进行整理,将所有存活的对象移动到一端,而未标记的对象则被认为是垃圾对象。
- 优点:没有产生"碎片"
- 缺点:速度慢
-
复制
将活着的内存空间分为两块,每次只使用一块,在垃圾回收时将正在使用的内存块中的存活的对象复制到未被使用的内存块,之后清除正在使用的内存块中的所有对象,交换两块内存空间,完成垃圾回收
- 优点:没有标记清除过程,运行高效,不会出现“碎片”问题
- 缺点:需要两倍的内存空间
分代垃圾回收
老年代:存放长时间使用的对象
新生代:存放用完就可以丢弃的对象
-
MinorGC
每次产生的新的对象在伊甸园区,如果进行了垃圾回收,那么会进行遍历所有对象,释放所有的的未被使用的对象,将使用的对象通过复制的方式,复制到幸存区S1,并进行年龄+1操作,表示该对象每次回收都没有被回收,当该值超过一个阈值时,就会将该对象放入老年代区,表示该对象可能会被长时间使用
- minor gc 会引发stop the world:进行垃圾回收时,会暂停其他的用户线程,等垃圾回收后,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升到老年区,最大寿命是15(4bit)
-
FullGC
老年代空间都不足时,先触发Minorgc,如果之后内存空间仍然不足,则会触发FullGC,来对内存空间进行清理,STW(stop the world)的时间更长
相关VM的参数
大对象:如果说某个对象的大小超过了新生代内存大小,那么会将这个对象直接晋升到老年代内存块,不会发生GC回收
垃圾回收器
-
串行
-
单线程的垃圾回收器
-
适用于堆内存较小的场景,个人电脑
-
打开串行垃圾回收器 -XX:+UseSerialGC=Serial(新生代:复制算法) + SerialOld(老年代:标记整理算法)
-
-
吞吐量优先(1.8默认的垃圾回收器)
-
多线程的垃圾回收器
-
适用于堆内存较大的场景,需要多核CPU的支持
-
让单位时间内STW的时间最短
-
在进行垃圾回收时会占满CPU
-
-XX:parallelGCThreads=n //指定线程数量 -XX:UseAdaptiveSizePolicy //采用自适应的大小调整策略,调整新生代的大小 -XX:UseParallelGC //开启吞吐量优先垃圾回收器 //ratio默认为99,此时值为0.01,只能由1%的时间用来进行垃圾回收(100min,只能有1min进行垃圾回收) -XX:GCTimeRatio=ratio //(公式:1/1+ratio)
-
-
响应时间优先(基于标记清除算法)
-
多线程的垃圾回收器
-
适用于堆内存较大的场景,需要多核CPU的支持
-
尽可能的让单次STW的时间最短
-
-XX:UseConcMarkSweepGC
-
G1(JDK9之后的默认的垃圾回收器)
定义:Garbage First
- 适用场景
- 同时注重吞吐量和低延时,默认的暂时目标是200ms
- 超大堆内存,会将堆划分为多个大小相等的Region
- 整体上是标记+整理算法,两个区域之间是复制算法
- 相关参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=size //设置Region的大小(1,2,4,8,16)
-XX:MaxGCPauseMillis=time //暂停目标时间
-
G1垃圾回收阶段
3.1Young Collection(新生代)
会STW
会将幸存的对象,复制到幸存区,之后新生代内存满了,会将部分常用的对象复制到老年代内存
3.2Young Collection+Concurrent Mark(新生代+并发标记)
在YoungGC时会进行GC Root的初始标记
老年代占用堆空间比例达到阈值时(默认为45%),进行并发标记(不会STW)
3.3Mixed Collection(混合收集)
会对伊甸园,新生区,老年区进行全面垃圾回收
最终标记会STW
拷贝存活会STW
-
Young Collection 跨代引用
新生代回收的跨代引用(老年代引用新生代)问题
垃圾回收调优
- 调优领域
- 内存
- 锁竞争
- cpu占用
- io
- 最快的GC是不发生GC
- 查看FullGC前后内存占用,考虑几个问题
- 是不是数据太多了
- 数据表示是否太臃肿
- 是否存在内存泄漏
- 查看FullGC前后内存占用,考虑几个问题
- 新生代调优
- 所有new操作的内存分配非常廉价
- 死亡对象的回收代价为零
- 大部分对象用过即死
- minorgc时间远远低于fullgc
- 所以新生代内存块的大小应该设置为堆大小的1/4~1/2最佳,过大的话,会导致老年代内存过小,发生频繁 FullGC
- 幸存区要足够大到能保留当前活跃对象和需要晋升的对象
- 晋升阈值配置要得当,让长时间存活对象能尽快晋升
- 老年代调优(CMS)
- CMS的老年代内存越大越好
- 将老年代内存预设调至1/4~1/3