System.gc()
默认情况下,通过System.gc()捉着Runtime,getRuntime().gc()的调用,会显示触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存
然而System,gc()附带一个免责声明,无法保证对垃圾收集器的调用(提醒jvm的垃圾回收器执行gc,但不确定是否马上执行)
JVM实现者可以通过System.gc()调用来决定JVM的GC行为。而一般情况下,垃圾回收是自动进行的,无需手动触发,否则过于麻烦。再一些特殊情况下,如我们正在编写一个性能基准,我们可以再运行之间掉哦那个System.gc()
内存溢出(OOM)
内存溢出相对于内存泄漏来说,尽管更容易被理解,但是同样的内存溢出也是引发程序崩溃的罪魁祸首之一。
由于GC一直再发展,又有一般情况下,除非应用程序占用的内存增长速度非常快,早晨垃圾回收已经跟不上内存消耗的速度,否则不太容易出现oom情况
大多数情况下,gc会根据各种年龄段的垃圾税收,是在不行就来一次独占式的Full GC操作,这时候会回收大量的内存,供应用程序继续使用
javadoc中对OutOfMeoryError的解释是:没有空闲内存,并且垃圾收集器也无法提供更多内存
原因有二
虚拟机堆内存设置不够
比如:可能存在内存泄露问题,也可能是堆大小不合适,比如我们要处理比较客观的数据量,但是没有显示指定JVM堆大小或者指定数值偏小,可以通过jvm参数进行调整
代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)
对于老版本jdk因为永久代大小有限,并且jvm堆永久代垃圾回收非常不积极,所以当我们不断添加新类型的时候永久代出现outofmemoryError也非常多见,尤其是再运行时存在大量动态类型生成场合,类似intern字符串缓存占用太多空间,也会导致oom问题,对应的异常信息会标记出来和永久代相关:java.lang.outofmemoryError:PermGen space
随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的oom有所改观,出现oom,异常信息变成了:java.lang.OutOfMemoryError:Metaspace。直接内存不足,也会导致oom
内存泄漏
也成为“存储渗漏”,严格来说,只有对象不会被程序用到了,但是gc又不能回收他们的情况才叫内存泄漏。
但实际情况很多时候一些不太好的时间会导致对象的生命周期变得很长甚至导致oom,也可以叫做宽泛意义上的内存泄漏
尽管内存泄漏不会立刻导致程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步禅师,直至耗尽所有内存,最终出现OutOfMemory异常导致程序兵溃
注意,这里的内存空间并不是指物理内存,而是虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小
常见情况
单例模式
单例生命周期和应用程序时一样长的,所以单例程序中,如果持有堆外部对象的引用的话这个外部对象时不能被回收的,会导致内存泄漏
一些提供close的资源未关闭导致内存泄漏
一些io连接必须手动close,否则不能被回收
Stop The World
Stop-the-World,检查stw,指的是gc时间发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉
可达性分析算法中枚举根节点会导致所有java执行线程停顿:
分析工作必须再一个能确保一致性的快照中进行
一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
被stw终端的应用程序线程会在完成gc之后回复,频繁终端会让用户感觉像是网速不快早晨电影卡带一样,所以我们要减少stw的发生。
stw时间和采用那款gc无关,与哦有gc都有这个事件
哪怕是g1也不能完全避免stw发生,只能说垃圾回收器越来越优秀,效率越来越高,进坑你缩短暂停时间
stw是jvm后台自动发起和自动完成的。再用户不可见的情况下,把用户正常的工作线程全部停掉
开发中不要用system.gc(),会导致stw的发生
安全点和安全区域
程序执行时并非在所有地方都能够停顿下来开始gc,只有在他认定的位置才能停顿下来开始gc,这些位置称为“安全点”(safe piint)
safe point的选择很重要,如果太少可能导致gc等待的事件太惨,如果太频繁可能导致运行时的性能问题。 大部分指令的执行事件都非常短暂,通常根据是否具有让程序长时间执行的特征为标准。比如:选择一些执行时间较长的指令作为safe point,如方法调用、循环跳转和异常跳转
如何在gc发生时,检查所有线程都跑到最近的安全点停顿下来呢
抢先式中断(目前没有虚拟机采用了)
首先终端所有线程,如果还有线程不在安全点,就回复线程,让线程报道安全点
主动式中断
设置一个中断标志,各个线程云效到安全点的时候主动轮询这个标志,如果中断标志为真,则将自己挂起
安全区域
safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入gc的safepoint。但是程序不执行的时候呢?例如线程处于sleep状态或者blocked状态,这时候线程无法相应jvm的中断请求,走到安全点区中断挂起,jvm也不太可能等待线程被唤醒。对于这种情况,就需要安全区域来解决
安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始gc都是安全的。我们也可以把safe region看作是被扩展了的safepoint
实际执行时:
当线程运行到safe region的代码时,首先表示已经进入了safe region,如果这段时间发生gc,jvm会忽略标识为safe region状态的线程
当线程即将离开safe region时,会坚持jvm是否已经完成gc,如果完成了,则继续云效,否则线程必须等待直到收到可以安全离开safe region的信号为止
引用
引用分为强引用(Strong Reference)、软引用(soft reference)、弱引用(Weak Reference)、虚引用(phantom reference)强度一次逐渐减弱
除强引用外,其他引用均可以在java.lang.ref包中找到他们
强引用
代码中普遍存在的引用,类似 Object object = new Object()这种关系。无论任何情况下,只要强引用关系还在就永远不会回收
虽然str对这个字符串的引用被取消了,但是str1对这个字符串的引用没有取消,所以还存在强引用,该字符串无法被回收,所以还可以输出这个内容
软引用
系统发生溢出之前,会把这些对象列入回收范围进行二次回收,如果这次回收后没有足够的内存才会报内存溢出
Object obj = new Object();//声明强引用
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null//销毁强引用
如果内存空间足够,发生gc也不会回收软引用对象
弱引用
弱引用关联对象只能生存道下一次垃圾收集之前。当垃圾收集器工作时,无论空间是否足够都会回收掉弱引用关联的对象。
但是由于垃圾线程优先级较低,因此并不能很快发现弱引用对象,这种情况下弱引用对象也能存在较长时间。
Object obj = new Object();//声明强引用
WeakReference<Object> sf = new WeakReference<Object>(obj);
obj = null//销毁强引用
弱引用和软引用的区别是:软引用时空间不足才回收,弱引用被发现就回收
WeakHashMap就是基于弱引用的
虚引用
一个对象时候存在虚引用,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。
虚引用必须和引用队列一起使用。虚引用在创建时必须提供一个引用队列作为参数。当垃圾回收器准备回收一个对象时。如果发现他还有虚引用,就会在回收对象之后将这个虚引用加入引用队列,以通知应用程序对象的回收情况
Object obj = new Object();//声明强引用
ReferenceQueue pq = new ReferenceQueue ();
PhantomReference<Object> sf = new PhantomReference<Object>(obj,pq);
obj = null//销毁强引用