JVM13-垃圾回收-垃圾回收相关概念

1 垃圾回收相关概念

1.1 System.gc()的理解

在程序中,调用System.gc()或者Runtime.getRuntime().gc()方法,会显式触发Full GC,同时对年轻代和老年代进行垃圾回收,尝试释放失去引用的垃圾对象所占用的内存空间,但一般情况下,垃圾回收应该是自动进行的,除非一些特殊情况,如测试某模块性能前,可以先调用一次System.gc()

System.gc()实质上是调用了Runtime.getRuntime().gc()方法,而Runtime.getRuntime().gc()本质上执行了一个本地方法gc()
在这里插入图片描述
在这里插入图片描述

System.gc()附带一个免责声明,它无法保证对垃圾收集器的成功调用,它只是提醒JVM希望进行一次Full GC,但Full GC是否进行,什么时候进行无法保证

来看一个示例:

创建一个对象并重写该对象的finalize()方法
让该对象没有任何的引用关系 成为一个垃圾对象
在程序结束前 主动调用一次System.gc()进行垃圾回收
如果System.gc()触发了Full GC 垃圾对象的finalize()方法将会被执行

结果1,使用System.gc()成功触发了GC:
在这里插入图片描述
结果2,System.gc()并没有触发GC:
在这里插入图片描述
上述的测试证实了System.gc()并不能直接触发GC,而是提醒JVM进行GC,至于JVM执不执行GC,什么时候执行无从得知

1.2 内存溢出和内存泄漏

1.2.1 内存溢出

内存溢出是引发程序崩溃的罪魁祸首之一,内存溢出会直接引起OOM(OutOfMemoryError),即没有空闲内存,且垃圾收集器也无法提供更多内存空间

1,JVM堆内存设置太小,不够用

2,代码中创建了大量需要长时间存活的对象,占用了堆中大量空间,且不能被回收

3,当创建新对象时发现内存不足后,垃圾收集器进行GC
   GC执行完后,发现内存还是不足

由于GC一直在发展,一般情况下,除非应用程序占用的内存增长速度非常快,造成GC释放内存空间的速度跟不上内存消耗的速度,否则不太容易出现OOM的情况,大多数情况下,GC会进行各年龄段的垃圾回收,实在不行,就进行一次Full GC,这时候会回收大量的内存,供应用程序继续使用

1.2.2 内存泄漏

内存泄漏即某对象不再被程序使用,但GC又无法回收它所占的空间,尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中可用的内存就会减少,直到所有内存耗尽,最终引起内存溢出,导致程序崩溃

由于JVM使用可达性分析算法,所有处于引用链上的对象都被视为可达的对象,无法被回收,但处于引用链上的某些对象可能不再被使用,但是由于没有断开指向它们的指针,导致它们仍然挂在引用链上,本应该被回收的垃圾被视为了可达对象,它们被占用的空间无法被释放,这就造成了内存泄露
在这里插入图片描述
如对于某些变量,可以在方法内定义成局部变量,但是却将其定义为成员,甚至是静态成员,这样做明显地增长了该变量的生命周期,但该变量只在某个方法内起作用,其所占用的空间在使用后迟迟不能释放,这种也可以称为宽泛意义上的内存泄漏

内存泄漏的例子:

1,单例模式所生产的单例对象,它们的生命周期和应用程序一样长
在程序中,单例对象如果持有对外部对象的引用
那么这个外部对象的生命周期就和单例对象一样长 不能被回收 导致内存泄漏

2,一些资源没有close()导致内存泄漏
如数据库连接,网络连接socket和io等必须手动关闭,否则不能被回收 导致内存泄漏

1.3 Stop The World的理解

STW指在GC过程中,会产生应用程序的停顿,停顿产生时,整个应用程序线程都会被暂停,这个停顿称为STW

标记垃圾阶段,使用的可达性分析算法会导致所有用户线程停顿,原因:

分析工作必须在一个能确保一致性的快照中进行,即应用程序必须暂停
因为分析过程中,如果应用程序还在执行,那么分析结果的准确性无法保证
避免GC时引用关系又发生变化,导致分析结果出现误差

被STW中断的用户线程会在GC结束后恢复,频繁的STW产生的中断,会给用户一种程序卡顿的感觉,为此要尽量减少STW的发生和STW产生停顿的时长

关于STW的几个说明:

1,STW和采用哪款GC无关,所有的GC都会出现STW
   即使是G1也不能避免STW发生,只能说垃圾回收器越优秀
   回收效率越高,尽可能缩短了STW的停顿时长

2,STW是JVM在后台自动发起和自动完成的
   在用户不可见的情况下,停止了所有正常执行任务的线程

3,开发中不要使用System.gc() 它可能导致STW的发生
   对于STW,越少越好,越短越好

1.4 安全点和安全区域

程序在执行时,并非在所有地方都能停顿下来进行GC,只有在特定的位置才能停顿下来开始GC,这些位置称为“安全点”(Safe Point)

安全点的选择很重要,安全点太少可能导致GC等待的时间太长,太多可能导致程序运行时的性能问题,大部分指令的执行时间都非常短暂,通常会选择一些执行时间较长的指令作为安全点,如方法调用,循环跳转,异常跳转等

如何在发生GC时,检查所有的线程都跑到了最近的安全点停顿下来呢?
有两种方式:

1,抢先式中断(已无虚拟机采用)
首先中断所有线程,如果还有线程不在安全点
就恢复这些不在安全点的线程,让线程跑到安全点

2,主动式中断
设置一个中断标志,程序将准备进行GC时,中断标志变为真
各个线程运行到安全点时主动轮询这个标志
如果中断标志为真,线程将自己中断挂起

安全点的机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的安全点,但是当程序“不执行”时,如线程处于Sleep状态或Block状态,这时线程无法响应JVM的中断请求,走到安全点将自己挂起,JVM也无法将这类线程唤醒,对于这种情况,就需要 安区区域(Safe Region) 来解决

安全区域指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC都是安全的,可以把安全区域看做被扩展的安全点

实际执行时:
当线程运行到安全区域的代码时,首先标识已经进入了安全区域
如果这段时间内发生了GC,JVM会忽略标识为安全区域状态的线程

当线程即将离开安全区域时,会减产JVM是否已经完成GC
如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开安全区域的信号为止

1.5 Java中的引用

在Java中将引用分为强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference)这4种引用强度逐渐减弱
在这里插入图片描述

强引用(Strong Reference) 
代码中普遍存在的引用赋值 类似Object obj = new Object();这种引用关系
无论任何情况下,只要强引用关系还存在,垃圾收集器就不会回收被引用的对象

软引用(Soft Reference) 
在系统将要发生内存溢出前,将把软引用关联的对象列入回收范围进行第二次回收
如果这次回收后还没有足够的内存,才会抛出OOM

弱引用(Weak Reference) 
被弱引用关联的对象只能生存到下一次垃圾收集之前
当垃圾收集器工作时,无论空间是否足够 都会回收掉被弱引用关联的对象

虚引用(Phantom Reference)
一个对象是否有虚引用的存在 完全不会对其生存时间构成影响
也无法通过虚引用获得一个对象的实例
为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知

1.5.1 强引用 Strong Reference

Java程序中,99%的引用类型是强引用,也就是最常见的普通对象引用,也是默认的引用类型,使用new操作符创建一个新的对象,并将其赋值给一个变量时,这个变量就称为指向该对象的一个强引用

当强引用的对象仍挂在引用链上时,GC就不会回收它,为此强引用是Java内存泄漏的主要原因之一

强引用的特点:

1,强引用可以直接访问目标对象

2,强引用的对象只要仍能被GC Root触及,就不会被回收

3,强引用可能导致内存泄漏

1.5.2 软引用 Soft Reference

软引用被用来描述一些还有用,但非必须的对象,被软引用关联的对象再系统将要抛出OOM前,将会被列入回收范围,进行第二次回收,将可达的软引用对象也进行回收,回收后内存空间还不足就会抛OOM
在这里插入图片描述
软引用通常迎来实现内存敏感的缓存,如高速缓存就有用到软引用,如果还有空闲内存,就暂时保留缓存,当内存不足时再清理掉,这样就保证了使用缓存的同时,不会耗尽内存

软引用的特点:

当内存足够时,不回收软引用的可达对象
内存不足时,回收软引用的可达对象

1.5.3 弱引用 Weak Reference

弱引用也是用来描述那些非必须对象,只被弱引用关联的对象只能生存到下一次GC发生为止,在系统GC到来时,只要该对象是弱引用,不管堆空间是否足够,不管该对象是否可达,都会直接回收

但是由于GC线程的优先度通常很低,因此并不一定能很快发现持有弱引用的对象,这种情况下,弱引用对象可以存在较长的时间

软引用和弱引用都非常适合保存那些可有可无的缓存数据,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出,当内存充足时,这些数据又可以存在较长的时间,从而加速系统

弱引用的特点:

GC时只要发现弱引用对象,就直接回收

1.5.4 虚引用 Phantom Reference

虚引用是所有引用类型中最弱的一个,一个对象是否有虚引用的存在,完全不会决定该对象的生命周期,如果一个对象仅持有虚引用,那么它和没有引用几乎是一样的,随时可能被当作垃圾回收

虚引用不能单独使用,也无法通过虚引用获取被引用的对象,当使用虚引用的get()方法试图获取对象时,结果总是null,为一个对象设置虚引用的唯一目的就是跟踪垃圾回收过程,当持有虚引用的对象被回收后,会收到一个系统通知

虚引用在创建时,必须要和引用队列一起使用,当垃圾收集器准备回收一个对象时,发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序该对象已被回收
在这里插入图片描述
虚引用的特点:

无法单独使用 不影响对象的生命周期 无法通过虚引用获取对象实例
只能跟踪垃圾回收过程 当持有虚引用的对象被回收后 可以收到一个系统通知
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值