在 JVM
中程序寄存器、Java 虚拟机栈、本地办法栈,这三个区是随着线程的创立而创立,随着线程完结而销毁。
其实就是这三个的生命周期和线程的生命周期一样。都是每个线程公有的。
每次办法的调用就会向栈里入栈一个栈帧,办法调用完结,跟着就出栈。
对象也是有生命周期的,所以对于不须要的对象要进行必要的分明,否则长此以往,咱们的内存就被一点一点的耗费完。
明天来学习,如何判断对象是否曾经能够被回收?以及回收有哪些算法?
如何判断对象已死?
援用计数法
给对象增加一个援用计数器,每当一个中央援用它 object 时技术加 1,援用失去当前就减 1,计数为 0 阐明不再援用。
- 长处:实现简略,断定效率高;
- 毛病:无奈解决对象互相循环援用的问题,对象 A 中援用了对象 B,对象 B 中援用对象 A。
public class A {public B b;}
public class B {public C c;}
public class C {public A a;}
public class Test{private void test(){A a = new A();
B b = new B();
C c = new C();
a.b=b;
b.c=c;
c.a=a;
}
}
可达性剖析算法
当一个对象到 GC Roots
没有援用链相连,即就是 GC Roots
到这个对象不可达时,证实对象不可用。
GC Roots
品种:
Java 线程中,以后所有正在被调用的办法的援用类型参数、局部变量、长期值等。也就是与咱们栈帧相干的各种援用。
所有以后被加载的 Java 类。
Java 类的援用类型动态变量。
运行时常量池里的援用类型常量(String 或 Class 类型)。
JVM 外部数据结构的一些援用,比方 sun.jvm.hotspot.memory.Universe 类。
用于同步的监控对象,比方调用了对象的 wait() 办法。
public class Test{private void test(C c){A a = new A();
B b = new B();
a.b=b;
// 这里的 a /b/ c 都是 GC Root;}
}
对象的援用类型
强援用:
User user=new User();
咱们开发中应用最多的对象援用形式。
特点:咱们平时典型编码 Object obj = new Object()中的 obj 就是强援用。
通过关键字 new 创立的对象所关联的援用就是强援用。
当 JVM 内存空间有余,JVM 宁愿抛出 OutOfMemoryError 运行时谬误(OOM),使程序异样终止,也不会靠随便回收具备强援用的“存活”对象来解决内存不足的问题。
对于一个一般的对象,如果没有其余的援用关系,只有超过了援用的作用域或者显式地将相应(强)援用赋值为 null,就是能够被垃圾收集的了,具体回收机会还是要看垃圾收集策略。
软援用:
SoftReference object=new SoftReference(new Object());
特点:软援用通过 SoftReference 类实现。软援用的生命周期比强援用短一些。
只有当 JVM 认为内存不足时,才会去试图回收软援用指向的对象:即 JVM 会确保在抛出 OutOfMemoryError 之前,清理软援用指向的对象。
软援用能够和一个援用队列(ReferenceQueue)联结应用,如果软援用所援用的对象被垃圾回收器回收,Java 虚拟机就会把这个软援用退出到与之关联的援用队列中。
后续,咱们能够调用 ReferenceQueue 的 poll()办法来查看是否有它所关怀的对象被回收。如果队列为空,将返回一个 null, 否则该办法返回队列中后面的一个 Reference 对象。
利用场景:软援用通常用来实现内存敏感的缓存。如果还有闲暇内存,就能够临时保留缓存,当内存不足时清理掉,这样就保障了应用缓存的同时,不会耗尽内存。
弱援用:
WeakReference object=new WeakReference (new Object();
ThreadLocal 中有应用。
弱援用通过 WeakReference 类实现。弱援用的生命周期比软援用短。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具备弱援用的对象,不论以后内存空间足够与否,都会回收它的内存。因为垃圾回收器是一个优先级很低的线程,因而不肯定会很快回收弱援用的对象。
弱援用能够和一个援用队列(ReferenceQueue)联结应用,如果弱援用所援用的对象被垃圾回收,Java 虚拟机就会把这个弱援用退出到与之关联的援用队列中。利用场景:弱利用同样可用于内存敏感的缓存。
虚援用:
简直没见过应用,ReferenceQueue、PhantomReference。
finalize 办法
这个办法就有点相似于“某个人被判了死刑,然而不肯定会死”的情景。
即便在可达性剖析算法中不可达的对象,也并非肯定是“非死不可”的,这时候他们临时处于“缓刑”阶段,真正宣告一个对象死亡至多要经验两个阶段:
1、如果对象在可达性剖析算法中不可达,那么它会被第一次标记并进行一次刷选,刷选的条件是是否须要执行 finalize()办法(当对象没有笼罩 finalize()或者 finalize()办法曾经执行过了(对象的此办法只会执行一次)),虚拟机将这两种状况都会视为没有必要执行)。
2、如果这个对象有必要执行 finalize()办法会将其放入 F -Queue 队列中,稍后 GC 将对 F -Queue 队列进行第二次标记,如果在重写 finalize()办法中将对象本人赋值给某个类变量或者对象的成员变量,那么第二次标记时候就会将它移出“行将回收”的汇合。
办法区的回收
在堆中,尤其是在新生代中,一次垃圾回收个别能够回收 70% ~ 95% 的空间,而办法区的垃圾收集效率远低于此。
办法区垃圾回收次要两局部内容:废除的常量和无用的类。
垃圾回收算法
标记 - 革除
第一步:就是找出沉闷的对象。咱们反复强调 GC 过程是逆向的,依据 GC Roots 遍历所有的可达对象,这个过程,就叫作标记。
第二步:除了下面标记进去的对象以外,其余的都革除掉。
- 毛病:标记和革除效率不高,标记和革除之后会产生大量不间断的内存碎片
复制
新生代应用,新生代分中Eden:S0:S1
= 8:1:1,其中前面的 1:1 就是用来复制的。
当其中一块内存应用完了,就将还存活的对象复制到另外一块下面,而后把曾经应用过的内存空间一次革除掉。
个别对象调配都是进入新生代的 eden 区,如果 Minor GC
还存活则进入 S0
区,S0
和 S1
一直对象进行复制。对象存活年龄最大默认是 15,大对象进来可能因为新生代不存在间断空间,所以会间接接入老年代。任何应用都有新生代的 10% 是空着的。
- 毛病:对象存活率高时,复制效率会较低,节约内存。
标记整顿
它的次要思路,就是挪动所有存活的对象,且依照内存地址程序顺次排列,而后将末端内存地址当前的内存全副回收。然而须要留神,这只是一个现实状态。对象的援用关系个别都是非常复杂的,咱们这里不对具体的算法进行形容。咱们只须要理解,从效率上来说,个别整顿算法是要低于复制算法的。这个算法是躲避了内存碎片和内存节约。
让所有存活的对象都向一端挪动,而后间接清理掉端边界以外的内存。
从下面的三个算法来看,其实没有相对最好的回收算法,只有最适宜的算法。
STW
STW
=Stop The world,字面翻译过去就是整个世界都进行了。
在 JVM 中也有这么个说法,就是 STW,是指 JVM 垃圾收集器在收集垃圾对象的时候,其余所有线程都被挂起(除了垃圾收集器之外),JVM 中一种全局暂停景象。
—- 全局进展,想想就很可怕,所有的 Java 代码进行执行,native 代码能够执行,然而不能与 JVM 进行交互,这些基本上都是因为 GC 引起的。
然而也还有另外的几种场景也能够导致 STW:
1.Garbage collection pauses
2.Code deoptimization
3.Flushing code cacheClass redefinition (e.g. hot swap or instrumentation)
4.Biased lock revocation
5.Various debug operation (e.g. deadlock check or stacktrace dump)