垃圾回收的相关概念
System.gc()的理解
- 在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显示触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存
- 然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用
- JVM实现者可以通过System.gc()调用来决定JVM的GC行为,而一般情况下,垃圾回收应该是自动机型的,无需手动触发,否则就太过于麻烦了。
System.gc() 与 System.runFinalization()结合使用可以强制调用使用引用的对象finalization方法
public class LocalVarGC{
public void localvarGC1(){
byte[] buffer = new byte[10*1024*1024];
System.gc();
//没有回收
}
public void localvarGC2(){
byte[] buffer = new byte[10*1024*1024];
buffer = null;
System.gc();
//被回收
}
public void localvarGC3(){
{
byte[] buffer = new byte[10*1024*1024];
}
System.gc();
//不会被回收
//buffer占用位置1,没有进行垃圾回收
}
public void localvarGC3(){
{
byte[] buffer = new byte[10*1024*1024];
}
int value = 10;
System.gc();
//会被回收
}
}
内存溢出与内存泄漏
内存溢出
- 内存溢出相对于内存泄漏来说,尽管更容易被理解,但是同样的,内存溢出也是引发程序崩溃的罪魁祸首之一。
- 由于GC一直在发展,所有一般情况下,除非应用程序占用内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现OOM的情况
- 大多数情况下,GC会进行各种年龄段的垃圾回收,实在不行了就进行一次Full GC,这时候会回收大量的内存,共应用程序继续使用
- javadoc中对OutOfMemoryError的解释是没有空闲内存,并且垃圾收集器也无法提供更多内存。
这里面隐含着一层意思是,在抛出OutOfMemoryError之前,通常垃圾收集器会被触发,尽其所能去清理出空间
- 例如,在引用机制分析中,设计JVM会去尝试回收软引用指向的对象等。
- 在Java.nio.BIts.reserveMemory()方法中,我们能清除的看到,System.GC()会被调用,以清理空间。
当然,也不是在任何情况下垃圾收集器都会被触发,比如,我们去分配一个超大对象,类似一个超大数组超过堆的最大值,JVM可以判断出垃圾收集并不能解决这个问题,所以直接抛出OutOfMemoryError。
内存泄漏
只有对象不会在被程序用到了,但是GC由不能回收他们的情况,才叫做内存泄漏
举例
- 单例模式
单例的生命周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部引用时不能被回收的,则会导致内存泄漏的产生。
- 一些提供close的资源未关闭导致内存泄漏。
Stop the World
-
Stop the World ,简称STW,指的是GC时间发生过程中会产生程序的停顿,停顿产生时整个应用程序线程都会被暂停,没有任何相应,有点像卡死的感觉,这个停顿称为STW。
- 可达性分析算法中枚举根节点会导致所有Java执行线程停顿。
- 一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
- 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
-
被STW中断的应用程序线程会在完成GC之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样,所以我们需要减少STW的发生。
-
STW事件和采用那款GC无关,所有的GC都有这个事件。
-
哪怕是G1也不能完全避免Stop-the-world情况的发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能的缩短了暂停时间。
-
STW时JVM在后台自动发起和自动完成的
-
开发中不要使用System.gc();会导致Stop-the-world的发生。
垃圾回收的并行与并发
并发和并行,在讨论垃圾收集器的上下文语境中,他们可以解释如下:
-
并行,指多条垃圾收集线程并行工作,但是此时用户线程仍处于等待状态。
- 如ParNew、Parallel Scavenge、Parallel Old;
-
串行(Serial)
- 相较于并行的概念,单线程执行。
- 如果内存不够,则程序暂停,启动JVM垃圾回收器进行垃圾回收。回收完,在启动程序的线程。
-
并发:指用户线程与垃圾收集线程同时执行,垃圾回收线程在执行时不会停顿用户线程的运行。
- 用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;
- 如CMS、G1
安全点与安全区域
程序执行时并非在所有地方都能停顿起来开始GC,只有在特定的位置才能停顿下来开始GC,这些位置称为“安全点”。
Safe Point的选择很重要,如果太少可能导致GC等待时间太长,如果太频繁可能呆滞运行时的性能问题。大部分指令的执行时间都非常短暂,通常会根据是否具有让程序长时间执行的特征未标准。比如:选择一些执行时间较长的指令作为Safe Point,如方法调用、循环跳转和异常跳转。
安全区域
引用
强引用、软引用、弱引用、虚引用有什么区别?
强引用
public class StringReferenceTest{
public static void main(Stirng[] args){
StringBuffer str = new StringBuffer("Hello,尚硅谷");
StringBuffer str1 = str;
str = null;
System.gc();
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(str1);
//str1不会回收
}
}
软引用-内存不足即回收
软引用用于描述一些还有用但是不是必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常
使用场景
软引用通常用来实现内存敏感的缓存。比如:告诉缓存就有用到软引用。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
public static void main(String[] args){
User u1 = new User(1,"songkh");
SoftReference<User> userSoftRef = new SoftReference<User>(u1);
u1 = null;
System.out.println(userSoftRef.get());
System.gc();
System.out.println("After:GC");
System.out.println(userSoftRef.get());
byte[] b = new byte[1024*1024*7];
System.out.println(userSoftRef.get());//,堆内存不足回收,软引用null
}
弱引用-发现即回收
虚引用-对象回收跟踪