Jvm内存模型
java垃圾回收的主要区域就是堆,Java中堆(heap)一般也被称为GC堆。在jvm内存模型中,虚拟机栈、本地方法栈、程序计数器的生命周期和线程相同,并且,这三个部分的内存,在类结构确定下来时,基本就确定了,大体上可以认为是编译期可知的,随着方法调用结束,线程执行结束,分配的内存空间自然跟着被收回。不存在垃圾回收的问题,而方法区和堆则不一样,一个方法中的多个分支需要的内存可能不一样,我们只有在程序运行期间才能知道会创建那些对象,这部分的内存分配和回收都是动态的,所以是垃圾收集器所处理的部分。
回收前确定对象是否可以回收
- 引用计数法算法:引用计数法的实现很简单,判断效率也高,它的算法是这样的,给对象添加一个计数器,此对象被引用一次,计数器就+1,引用失效的时候,计数器就-1,任何时刻,计数器值为0的对象就是不可能再被使用的,这样的方式来判断对象是否存活的一个问题就是,不能解决对象之间循环依赖的问题。
//这样的循环依赖,引用计数器的值永远都不能为0
ObjectA.instance = ObjectB;
ObjectB.instance = ObjectA;
- 可达性分析算法:
java引用的分类
从Jdk1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
- 强引用:指在程序代码中普遍存在的,类似于"Object object = new Object()"这类的引用,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。
- 软引用:用来描述一些还有用但非必需的对象。对于软引用关联的对象,在系统将要发生内存泄漏的异常之前,就会把这些对象列进回收范围,进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存泄漏异常。jdk1.2以后,提供了SoftReference类来实现软引用。
public class SoftReference<T>
extends Reference<T>软参考对象,由垃圾收集器根据内存需求自行决定清除。 软引用通常用于实现对内存敏感的缓存。
构造方法
SoftReference(T referent) 创建引用给定对象的新软引用。
SoftReference(T referent, ReferenceQueue<? super T> q) 创建一个引用给定对象并在给定队列中注册的新软引用。
方法
T get() 返回此引用对象的引用对象。
- 弱引用:也是用来描述非必需的对象,但是它的强度比软引用更弱,被弱引用关联的对象只能存活到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,jdk1.2以后提供了WeakReference类来实现弱引用。
public class WeakReference<T>
extends Reference<T>弱引用对象,它们不会阻止它们的指示物被最终化,最终化,然后回收。 弱引用最常用于实现规范化映射。
构造器
WeakReference(T referent) 创建一个引用给定对象的新弱引用。
WeakReference(T referent, ReferenceQueue<? super T> q) 创建一个引用给定对象并在给定队列中注册的新弱引用。
- 虚引用:最弱的一种引用,一个对象是否有虚引用的存在,完全不会对此对象的生存时间构成影响,也无法通过虚引用来取得一个对象的实例。为对象设置虚引用关联的唯一目的在这个对象被收集器回收的时收到一个系统通知。jdk1.2以后提供了PhantomReference类来实现虚引用。
public class PhantomReference<T>
extends Reference<T>幻像引用对象,在收集器之后排队,确定它们的对象可以被回收。 幻影参考通常用于安排事后清理操作。
构造器 描述
PhantomReference(T referent, ReferenceQueue<? super T> q)
创建一个新的幻像引用,该引用引用给定对象并在给定队列中注册。
方法
T get() 返回此引用对象的引用对象。
即使在对象可达性分析算法中的不可达对象,也并非真的会被回收,真正宣告一个对象死亡的,至少要经历两次标记过程,如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个F-Queue的队列之中,并在稍后由一个虚拟机自动建立的,低优先级的Finalizer线程去执行它。此时,在F-Queue队列中的对象如果重新与Gc Roots引用链上的任何一个对象关联上,那么在第二次标记时,这个对象将被移除即将回收的集合,如果此时被回收的对象依然没有逃脱,那基本上它就真的会被回收了。任何一个对象的finalize()方法只会被虚拟机调用一次。
- 回收方法区:方法区(永久代)垃圾收集性价比低,堆中收集一次大概可以回收70%~95%的空间,而永久代的收集远低于这个比例。永久代的垃圾收集主要是两部分:废弃常量和无用的类,废弃常量和无用的对象类似,假如常量池中"abc"的字符串在任何地方都没有引用这个字面量,如果发生内存空间回收的话,这个字符串就会被回收。常量池中的其他类(接口)、方法、字段的符号引用也是类似。判定一个类是否是无用类,条件要苛刻的多,必须满足三点,
- 该类的所有实例对象都已经被回收了,也就是Java堆中不存在该类的任何实例。
- 加载该类的classloader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述3个条件的无用类进行回收,hotspot虚拟机提供了-Xnoclassgc参数来控制。在大量使用反射、动态代理、CGLIB等ByteCode框架、动态生成JSP以及OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。
垃圾收集算法
- 标记-清除算法:
分为标记和清除两个阶段:首先标记出所有需要被回收的对象,在标记完成后统一回收所有被标记的对象。不足之处在于,标记-清除这两个过程的效率都不高,另一个是空间问题,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致以后程序运行时需要分配较大对象时,无法找到足够的连续内存而不得不提前出发一次垃圾收集动作。
- 标记-整理算法:
标记-整理算法过程和标记-清除算法一样,但是后续步骤不同,不是直接对可回收的对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
- 复制算法:
为了解决效率问题,人们设计了一种称为复制的垃圾收集算法,它可以将内存按容量分为大小相等的两块,每次只是用其中的一块。当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也不需考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效,缺点是,这种算法把内存缩小为原来的一半,未免太高了。现在的商业虚拟机都采用复制收集算法,只是不是按照1:1这样的比例来划分内存空间,而是将内存空间分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor空间。当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor中,然后把Eden和Survivor清理掉。hotspot虚拟机默认的Eden和Survivor比为8:1。也就是每次新生代中可用内存空间为整个新生代容量的90%,只有10%的内存会被"浪费",当然我们也不能保证每次回收时存活的对象都不超过10%,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保。survivor内存不够时,这些对象会直接通过分配担保机制进入老年代。
- 分代收集算法:根据对象存活周期的不同将内存划分为几块。一般把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。例如,在新生代中,每次垃圾收集时都发现大批对象死去,只有少量存活,那么就可以采用复制算法,而老年代中的对象存活率高,没有额外的空间对它进行担保,那么就必须采用标记-清除或者标记-整理算法来进行回收。
垃圾收集器
- Serial收集器:
-这是一个单线程收集器,使用标记-整理算法。它工作时只会使用一个CPU或者一条收集线程区完成垃圾收集工作,更重的是,它在垃圾回收时,必须暂停其他所有的工作线程,直到它收集结束。"Stop The World"就是表示,虚拟机在后台自动发起和自动完成的,在用户看不见的情况下把用户正常工作的线程全部停掉,然后就会出现卡顿和失去响应。从jdk1.7以后,虚拟机开发团队一直在努力减少这方面的影响。
- ParNew收集器:
这个是Serial收集器的多线程版本,对象分配策略、回收策略和Serial是一样的。
- Parallel Scavenge收集器:新生代的收集器,使用复制算法,并行多线程收集器。
- Serial Old收集器:Serial收集器的老年代版本,单线程收集器,使用标记-整理算法。这个收集器主要在Client模式下的虚拟机使用的。
- Parallel OLd收集器:Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。jkd1.6以后才开始使用。
- CMS收集器:该收集器是一种以获取最短回收停顿时间为目标的收集器,目前很大一部分的Java应用集中在互联网或者B/S系统的服务端上,使用标记-清除算法实现。
- G1收集器:Garbage-First
查看当前jdk使用的那种垃圾收集器:java -XX:+PrintCommandLineFlags -version