System.gc()
System.gc();
Runtime.getRuntime().gc();
通过上面两个方法的调用,会显式的触发Full GC,同时对年轻代和老年代进行回收,尝试释放被丢弃对象所占用的内存
System.gc() 无法保证对垃圾收集器的调用,无法确定调用时间和能否真正进行垃圾回收,但是一般情况下,垃圾回收应该是自动进行的,无需手动触发,除非在一些特殊情况下,例如性能测试前可以调用System.gc()
System.runFinalization(); 会强制调用失去引用对象的finalize() 方法
内存溢出和内存泄漏
内存溢出:没有空闲的内存,并且垃圾回收器也无法提供更多内存(回收以后,依然不够)
随着GC的发展,一般情况下不太容易出现内存溢出,除非应用程序占用内存的速度超过垃圾回收的速度,
大多数情况下,会对不同的年龄段分段进行回收,但在特殊情况下,也会就像独占式的Full GC,一次回收大量内存
产生内存溢出的原因:
-
堆的内存过小
-
代码中有大量的大对象,并且长时间不能被回收
-
另外关于方法区的内存溢出,因为方法区的垃圾回收是很不积极的,所以在1.7及以前采用虚拟机内存的永久代更容易内存溢出,1.8及以后,采用本地内存的元空间,就不太容易出现元空间的内存溢出了
在抛出oom之前,通常垃圾收集器会被触发,尽可能去清理出空间,当然也不是任何情况下都会触发垃圾回收器,例如有一个超大对象超过了堆的最大值,就直接报oom了
内存泄漏:严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们,就叫做内存泄漏
而实际情况中,如果存在生命周期很长的对象(例如静态的类变量)导致oom,也可以叫做宽泛意义上的"内存泄漏"
内存泄漏会导致可用的内存越来越少,最终由可能会导致omm,使程序崩溃
例如:指针忘记断开,导致一部分对象仍然可达,无法回收
需要注意的是,java没有使用引用计数算法,所以不会产生循环引用导致的内存泄漏
java可能产生内存泄漏的场景:
- 单例模式:单例的生命周期和应用程序是一样长的,如果单例对象引用了外部对象,就会导致这个外部对象变成不可回收的
- 一些未关闭的资源,例如:数据库连接、网络连接、流等
STW
stop the world,简称STW,在GC过程中,整个应用程序线程都会被暂停,产生的程序停顿,没有任何响应,有点像卡死的感觉
- 因为可达性算法中枚举根节点的分析工作必须在能保证一致性的快照中进行,如果不能满足的话,分析的结果也会无法保证,所以停顿是不可避免的
- 一致性是指整个分析期间整个执行系统看起来像被冻结在某个时间点上
- 如果分析过程中对象引用关系还在不断变化,分析结果的准确性是无法保证的
stw 中断的应用程序线程会在完成GC后恢复,但是频繁的中断用户体验会很差,所以需要尽量减少stw的发生
stw和哪个GC无关,所有的GC都有这个事件,只是随着GC的发展,会尽可能的缩短暂停事件
stw是jvm后台自动发起和自动完成的,在用户不可见的情况下,把用户正常工作的线程全部停掉
所以开发中,一般不要调用System.gc();会导致垃圾回收,继而导致stw
垃圾回收的并行和并发
程序中的并行、并发:
并发:一个时间段内同时发生
并行:一个时间点上同时发生
垃圾回收的并行、并发:
并行(多个垃圾回收线程同时执行)、并发(一个时间段内,用户线程和垃圾回收线程同时执行)、串行(单线程)
安全点、安全区域
并不是在任何时间点上,应用程序都可以停下来GC,需要在特定的时间点上才能停下来GC,这些时间点,就被称为安全点(safepoint)
safe point 选择很重要,太少会导致GC等待时间太长,太频繁又会导致运行时性能有问题,一般来说,大部分指令的执行时间很短,是否具有让程序长时间执行,是选择安全点的标准,例如方法调用,循环跳转等
如何在GC发生时,如何检查所有线程都跑到最近的安全点停顿下来:
- 抢先式中断:先中断所有线程,如果线程不在安全点,就恢复线程,让线程跑到安全点,目前已经没有虚拟机采用了
- 主动式中断:设置一个中断标志,各个线程运行到安全点的时候主动轮询这个标志,如果中断标志为真,将自己进行中断挂起
但是对于已经挂起或者阻塞的线程,就没办法要求线程执行到安全点,所以就划分了安全区域
安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中任何位置开始GC都是安全的,可以看做是安全点的扩展
- 当线程执行到安全区时,会首先表示已近进入安全区域了,如果在这段时间发生GC,JVM会忽略标识为安全区状态的线程(因为暂停用户线程的目的就是保证对象的引用不会发生变化)
- 当线程离开安全区域时,会检查jvm是否完成了GC,如果完成了,就继续运行,否则线程必须等待接收到可以离开安全区的信号为止
java中不同类型的引用
有这样一类对象,当内存空间足够的时候,可以保留在内存中,如果内存空间不足在进行垃圾回收后依然很紧张,就可以抛弃这些对象
强引用、软引用、弱引用、虚引用的区别,以及分别的使用场景
从jdk 1.2 以后,java对引用的概念进行了扩充,将引用分为了强引用、软引用、弱引用、虚引用四种,四种引用的强度依次衰弱,除了强引用以外,其他三种引用都继承了抽象类 Reference
Reference 的四个实现类,就是软、弱、虚以及终结器引用
以下几种情况,都是在引用还在,对象可达的情况下,不可达的对象是肯定要回收的
- 强引用:最传统意义上的 “引用” 的定义,无论任何情况下(哪怕出现oom),只要强引用的关系还存在,垃圾回收器就永远不会回收掉被引用的对象
- 软引用:在系统将要发生内存溢出之前,将会把这些对象收入回收范围之中进行第二次回收,如果这次回收后还没有足够的内存,才会抛出内存溢出异常
- 弱引用:被弱引用关联的对象只能生存到下一次垃圾回收之前,当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被引用关联的对象
- 虚引用:一个对象是否有虚引用的存在,完全不会对其生存 时间构成影响,也无法通过虚引用来获得一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收器回收时收到一个通知
需要注意的同一个对象可能被多个对象引用,也就是说,可能同时被强、软、弱、虚引用
强引用
最常见的对象,正常创建出来的对象都是强引用,只要强引用对象可触及就不能被垃圾回收掉,所以强引用是造成java内存溢出的主要原因之一
软引用
软引用是来描述一些还有用,但是非必须的对象,只被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收没有足够的内存,才会抛出内存溢出异常,所以软引用是不会造成内存溢出的(内存溢出和强引用以外的引用无关)
软引用通常用来实现内存敏感的缓存,比如高速缓存就用到了软引用,如果还有空闲空间就暂时保留,如果没有就清理掉,这样就保证了使用了缓存的同时,不会耗尽内存
内存足够,不会回收软可达对象,只有内存不够时才会回收软可达对象
创建一个软引用对象,下面两种情况是相等的
SoftReference softReference = new SoftReference(new String("123"));
String s = new String("123");
SoftReference so = new SoftReference(s);
s = null; //取消强引用
弱引用
弱引用:也是用来描述非必须对象,只被弱引用关联的对象,只能生存到下一次垃圾回收,只要发现弱引用,不管空间是否充足都会回收,但是因为垃圾回收器的线程优先级通常情况下很低,因此弱引用也有可能存在较长时间
弱引用也可以用来做缓存,弱引用和软引用的区别在于,当GC进行回收时,软引用需要判断是否回收,而弱引用是直接回收,所以弱引用对象更容易、更块的被GC回收掉
WeakReference weakReference =new WeakReference(new String("123"));
例如:WeakHashMap,
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V> {
虚引用
也称为“幽灵引用”或者“幻影引用”,是所有引用类型中最弱的一个
一个对象是否有虚引用,完全不会决定对象的生命周期,如果一个对象只有虚引用,那么几乎和没有引用是一样的,随时都能被垃圾回收器回收掉,唯一的作用就是跟踪垃圾回收的过程,能在对象被收集器回收时起到一个系统通知
//虚引用必须要创建一个引用队列
ReferenceQueue referenceQueue =new ReferenceQueue();
PhantomReference phantomReference =new PhantomReference(new String("123"),referenceQueue);
终结器引用
用以实现finalize()方法,无需手动编码,配合引用队列使用,在GC时,终结器引用进入队列,由finalize线程通过终结器引用找到被引用对象并调用finalize()方法,第二次GC时才能被回收