虽然内存的分配和回收技术已至关成熟,但若是须要排查内存溢出、内存泄露问题,或者要求高并发、高性能时,就须要对垃圾的回收进行监控和调节,以更好优化系统提升性能。html
Java
内存结构中,程序计数器、虚拟机栈、本地方法栈等随着线程而生,随线程而灭,不须要考虑内存回收问题。而 Java
堆和方法区则不一样,它们的内存分配是动态的,只有在运行期间才能知道会建立哪些对象,垃圾回收关注的就是这两部分。java
垃圾回收首先须要判断哪些对象还存活着,主要有引用计数和可达性分析两种算法。算法
它的原理以下:给对象添加一个引用计数器,每当有一个地方引用它时,计时器值就加 1
;当引用失效时,计数器值就减 1
;若是计数器为 0,对象就不可能再被使用。数组
引用计数算法虽然实现简单、断定效率较高。但它很难解决对象之间循环引用的问题。缓存
例如两个对象相互引用,实际上两个对象都不会再访问,但由于相互引用着对方,致使它们的计数器值都不为 0
,因而引用技术算法没法经过 GC
收集器回收它们。并发
它的原理以下:经过一系列称为 GC Roots
的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots
没有任何引用链相连时,则证实对象是不可用的。高并发
Java
中,可做为 GC Roots
的对象包括以下几种:性能
JNI
( Native
方法) 引用的对象。能够看到,对象回收断定算法判断对象是否存活都与引用有关。从 JDK1.2
开始,引用分为四种类型,用来实现不一样的功能,它们的引用强度也依次递减。优化
强引用(Strong Reference)线程
平时使用的引用就是强引用。只要强引用还存在,该对象永远不会被回收。
能够经过将对象设置为 null
,使其被回收。
软引用(Soft Reference)
用于描述一些还有用但并不是必需的对象。当系统内存空间不足时,会回收这些软引用指向的对象。它经过 SoftReference
类来实现软引用。
能够用来实现高速缓存。
弱引用(Weak Reference)
用来描绘非必需对象。被弱引用指向的对象只能生存到下一次垃圾回收以前。只要垃圾收集器运行,弱引用指向的对象就会被回收。它经过 WeakReference
类来实现弱引用。
虚引用(Phantom Reference)
虚引用和没有引用没有任何区别。一个对象是否有虚引用,不会影响其生存时间,也没法经过虚引用获取对象实例。它经过 PhantomReference
来实现虚引用。必须和引用队列 ReferenceQueue
联合使用。
为一个对象设置虚引用的惟一目的是该对象被垃圾收集器回收前会收到一条系统通知。
方法区,或者说 HotSpot
虚拟机中的永久代,进行垃圾回收的效率通常比较低。回收主要包括两部份内容:废弃常量和无用的类。
判断一个常量是不是废弃常量比较简单,与回收 Java
堆中的对象相似。而断定一个类是不是无用的类须要知足三个条件:
ClassLoader
已经被回收;java.lang.Class
对象没有在任何地方被引用,没法在任何地方经过反射访问该类的方法。标记-清除算法分为两个标记和清除阶段:首先标记出全部须要回收的对象,在标记完成后统一回收全部被标记的对象。标记过程也就是对象存活断定算法。
它是最基础的收集算法,主要有两个缺点:
复制算法将可用内存分为大小相等的两块,每次只使用其中的一块。在一块内存用完后,将仍存活的对象赋值到另外一块上面,再把已使用过的内存一次清理掉。
复制算法的优缺点以下:
标记-整理算法分为标记和整理两个阶段,标记阶段和“标记-清除算法”同样,但在整理阶段,不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存。
标记-整理算法的优缺点以下:
分代算法根据对象存活周期将内存划分为几块。通常是将 Java
对分为新生代和老年代,根据各个年代的特色采用适当的收集算法。
新生代中,每次垃圾收集时只有少许对象存活,选择复制算法;老年代中,对象存活率较高、没有额外空间进行分配,使用“标记-清理”或“标记-整理”算法。
为了对不一样生命周期的对象采用不一样的回收算法,因此垃圾收集器都采用分代收集算法,将堆分为新生代和老年代。
新生代主要用来存放新建立的对象,通常占堆 1/3
的空间。因为不少对象生命周期很短,每次 Minor GC
后只有少许对象存活,因此选用复制算法。
新生代又被分为一块较大的 Eden
区和两块较小的大小相等的 Survivor
区,使用 from
和 to
来分别指代两个 Survivor
区。HotSpot
虚拟机默认 Eden
和两块 Survivor
的大小比例为 8:1:1
。每次只会使用 Eden
和其中一块 Survivor
区为对象服务,因此老是有一块 Survivor
区是空闲的,新生代实际可用的内存空间也就为 90%
。
一般,对象会分配在 Eden
区中,当 Eden
区没法在分配对象时,JVM
便会触发一次 Minor GC
,将存活下来的对象复制到 from
指向的 Survivor
区中。
当 from
指向的 Survivor
区也没法分配时,对 Eden
和 from
指向的 Survivor
区执行 Minor GC
,将存活下来的对象复制到 to
指向的 Survivor
区中,而后交换 from
和 to
指针,使 to
指向的 Survivor
区为空,以保证下次 Minor GC
有复制的空闲空间。
老年代用于存放大对象,或年龄超过必定程度的对象。通常占据堆 2/3
的空间。
若是对象须要大量连续的内存空间,例如很长的字符串及数组,这些对象会直接分配在老年代,以免在 Eden
区及两个 Survivor
区之间发生大量的内存复制。
虚拟机为每一个对象定义了一个对象年龄计数器,若是对象分配在 Eden
区,在通过一次 Minor GC
后仍然存活,以后移动到 Survivor
空间中,将其年龄设置为 1
。对象在 Survivor
区中每通过一次 Minor GC
,年龄就增长一次,当它的年龄增长到必定程度(默认为 15
)时,也会被晋升到老年代中。
若是在 Survivor
区中相同年龄全部对象大小的总和大于 Survivor
区的一半,年龄大于或等于该年龄的对象就能够直接进入老年代。
老年代的对象通常都比较稳定,Major GC
不会频繁执行。Major GC
采用标记—清除算法:首先扫描一次全部老年代,标记出存活的对象,而后回收没有标记的对象。MajorGC
的耗时较长,并且会产生内存碎片。
Minor GC(Young GC)
指发生在新生代的垃圾收集动做。当 Eden
区没有足够的空间分配时,就会触发一次 Minor GC
。因为 Java
对象大多生命周期较短,因此 Minor GC
很是频繁,通常回收速度也比较快。
Major GC
指发生在老年代的垃圾收集动做,在进行 Major GC
前,通常都会进行至少一次 Minor GC
。Major GC
的速度通常会比 Minor GC
慢 10
倍以上。
Full GC
指回收整个新生代和老年代的垃圾收集动做。成本较高,对系统性能产生影响。FULL GC
的时候会 STOP THE WORD
。
它的触发条件主要有:
Minor GC
以前,若是老年代最大可用的连续空间小于历次晋升到老生代对象的平均大小,则触发一次 Full GC
。System.gc()
方法时。