垃圾收集器关注的是Java堆和方法区这部份内存。java
GC大概须要关注的事情有:算法
哪些内存须要回收
何时回收
怎样回收
复制代码
给一个对象添加一个引用计数器,每当有个地方引用时,计数器加1;引用失效时,计数器减1;引用计数器为0的对象不可能再被使用。缓存
实现简单,判断效率高。
复制代码
难解决对象之间存在循环引用的场景。
复制代码
主流的Java虚拟机没有用引用计数器服务器
以GC Roots对象做为起始点,从这些节点开始向下搜索,搜索走过的路径为引用链。从GC Roots到这个对象不可达时,说明此对象不可用,会被判断为可回收的对象。多线程
虚拟机栈中引用的对象【栈帧中的本地变量表】
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中引用的对象
复制代码
JDK1.2以后Java对引用作了进一步的细分。由强到弱依次分为:强引用、软引用、弱引用、虚引用。并发
广泛存在的,相似于ide
Object obj = new Object();
复制代码
只要强引用还存在,垃圾收集器就不会回收被引用的对象性能
咱们能够将对象的引用显示地置为null:o=null; 【能够帮助垃圾收集器回收此对象】this
描述一些可能用到的对象可是非必需的。对于这种引用,在内存充足的时候垃圾回收器不会回收他,在内存不足的时候会回收。spa
软引用很是适合于建立缓存。当系统内存不足的时候,缓存中的内容是能够被释放的。
在Java中用java.lang.ref.SoftReference类来表示。
// 获取对象并缓存
Object object = new Object();
SoftReference softRef = new SoftReference(object);
// 从软引用中获取对象
Object object = (Object) softRef.get();
if (object == null){
// 当软引用被回收后从新获取对象
object = new Object();
}
复制代码
弱引用用来描述非必需对象,其强度比软引用更弱。被弱引用关联的对象只能活到下次垃圾收集器回收以前,无论内存是否充足。
若是这个对象是偶尔的使用,而且但愿在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。 能够解决内存泄露问题。
例如对象池、缓存中的过时对象都有可能引起内存泄露的问题。用 WeakHashMap 来做为缓存的容器能够有效解决这一问题。WeakHashMap 和 HashMap 几乎同样,惟一的区别就是它的键使用弱引用。当 WeakHashMap 的键标记为过时时,这个键对应的条目就会自动被移除。这就避免了内存泄漏问题。
虚引用也称为幻影引用,是最弱的一种引用方式。
一个对象是都有虚引用的存在都不会对生存时间都构成影响,也没法经过虚引用来获取对一个对象的真实引用。
惟一的用处:能在对象被GC时收到系统通知,JAVA中用PhantomReference来实现虚引用。
String temp = "hello world";
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> phReference = new PhantomReference<String>(temp, queue);
System.out.println(phReference.get());//get返回null
复制代码
不一样分类的引用,让内存管理更容易,不一样的对象实例有不一样的回收管理方式
对对象进行可达性分析发现他到GC ROOTS没有可达的引用链,就会做为GC回收的对象,若是到GC Roots可达,那么就还没死,不会回收。
可是即便到GC Roots对象不可达,对象也还有自我救赎的机会,也并不是死亡。
若是重写了finalize方法,而且从新指向该对象,该对象仍是存活,不会死亡。若是这个自我救赎的机会也错失,那么通常都会被回收掉。
public class FinalizeTest {
public static FinalizeTest testFinalize = null;
@Override
public void finalize() throws Throwable {
super.finalize();
System.out.println("正在执行finalize方法~!");
//自救
testFinalize = this;
}
public static void main(String[] args) throws InterruptedException {
testFinalize = new FinalizeTest();
//对象第一次成功拯救本身
testFinalize = null;
System.gc();
//由于finalize()方法优先级很低,因此暂停1S等待它
Thread.sleep(1000);
//finalize()方法确实被GC触发了,可是收集前成功逃脱了。
if (testFinalize != null) {
System.out.println("alive~!");
} else {
System.out.println("dead~!");
}
//对象第二次成功拯救本身未遂,由于任何一个对象的finalize()只会被系统调用一次。
testFinalize = null;
System.gc();
//由于finalize()方法优先级很低,因此暂停1S等待它
Thread.sleep(1000);
if (testFinalize != null) {
System.out.println("alive~!");
} else {
System.out.println("dead~!");
}
}
}
复制代码
finalize方法实际中通常不会使用,运行代价大,不肯定性大,能够用try-finally更好的关闭外部资源。
永久代主要可回收的两部分分别是:
废弃的常量
无用的类
复制代码
无用类要同时知足3个条件:
该类的全部实例都被回收掉
加载该类的ClassLoader被回收
对应的Class对象没有在任何地方被引用,没法在任何地方经过反射访问该类方法
复制代码
HotSpot虚拟机提供了-Xnoclassgc参数进行控制是否回收无用。
还可使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看类的加载和卸载信息。
一、标记阶段。从根集合开始扫描,标记存活的对象
二、清除阶段。扫描整个内存空间,回收未被标记的对象,使用free-list记录被释放的区域
实现简单
不须要额外的空间
复制代码
效率问题,标记、清除两个阶段扫描性能不高
会产生内存碎片。分配大对象时,没法找到匹配的内存,会致使另外一次垃圾收集的触发
复制代码
针对老年代的CMS收集器;
复制代码
从根集合开始扫描,从一块内存中找到存活的对象,复制到另外一块空闲的内存中,而后回收第一块内存中的对象。下次这两块内存交换身份。
实现简单,运行高效,没有标记
没有内存碎片
复制代码
内存使用缩小为原来的一半
复制代码
如今商业JVM都采用这种算法=来回收新生代;
Serial收集器、ParNew收集器、Parallel Scavenge收集器、G1;
复制代码
一、标记阶段。和标记清除算法的同样
二、整理阶段。让全部存活的对象向一端移动,而后直接清理掉端外界边的内存
没有内存碎片
复制代码
须要移动对象,增长成本
复制代码
不少垃圾收集器采用这种算法来回收老年代;
如Serial Old收集器、G1;
复制代码
当前商业虚拟机基本上都是采用分代垃圾回收算法来回收垃圾,思想也很简单,就是根据对象的生命周期将内存划分,而后进行分区管理,根据各个年代的特色采用最合适的收集算法。
通常把Java堆分为新生代和老年代;
每次垃圾收集都有大批对象死去,只有少许存活;
因此可采用复制算法;
复制代码
对象存活率高,没有额外的空间能够分配担保;
使用"标记-清理"或"标记-整理"算法;
复制代码
根据各个年代的特色采用最适当的收集算法
复制代码
仍然不能控制每次垃圾收集的时间;
复制代码
目前几乎全部商业虚拟机的垃圾收集器都采用分代收集算法;
如HotSpot虚拟机中所有垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
复制代码
JVM在进行GC时,并不是每次都对新生代、旧生代、永久代一块儿回收的,大部分时候回收的都是指新生代。所以GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC),它们所针对的区域以下。
普通GC(minor GC):只针对新生代区域的GC。
全局GC(major GC or Full GC):针对年老代的GC,偶尔伴随对新生代的GC以及对永久代的GC。
复制代码
因为年老代与永久代相对来讲GC效果很差,并且两者的内存使用增加速度也慢,所以通常状况下,须要通过好几回普通GC,才会触发一次全局GC。
单线程的收集器,进行垃圾收集时,必须暂停其余的工做线程,也就是“Stop The World”。
使用串行回收;复制算法
单CPU、新生代小、对暂停时间要求不高的应用
Client模式下的默认新生代收集器
复制代码
-XX:+UseSerialGC 串行收集器
复制代码
ParNew收集器就是Serial收集器的多线程版本。经过多线程扫描并压缩堆。
新生代并行,老年代串行;新生代复制算法、老年代标记-整理
Server模式下虚拟机中首选的新生代收集器
复制代码
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量
复制代码
新生代收集器,使用复制算法,多个线程来经过扫描并压缩堆。
Parallel Scavenge收集器的目标是达到一个可控制的吞吐量,也称吞吐量优先的收集器。
吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集器时间)
复制代码
高吞吐量能够高效的利用CPU,尽快完成程序任务,适合在后台运算而不须要太多交互任务。
多CPU、对暂停时间要求比较短的应用
Server模式上的默认选择
复制代码
-XX:MaxGCPauseMillis 最大垃圾收集停顿时间
-XX:GCTimeRatio 设置吞吐量大小
-XX:+UseAdaptiveSizePolicy GC自适应的调节策略,把内存管理的调优任务交给虚拟机去完成
复制代码
Serial Old收集器是Serial收集器的老年代版本,使用标记-整理算法
这个收集器主要在于给Client模式下的虚拟机使用。
若是在Server中,主要用途是:1,在JDK1.5前和Parallel Scavenge搭配使用。2,做为Concurrent Mode Failure时候使用。
复制代码
Parallel Scanvenge收集器的老年队收集器,使用标记-整理方式。
在这个方式没有产生以前,Parallel Scavenge只能选择Serial Old。
因为被拖了后腿,那么Parallel Scavenge并不能在总体上获取吞吐量最大化的效果。甚至比不上CMS+ParNew的吞吐量。
并发标记-清除算法。 以获取最短回收停顿时间为目标的收集器
初始化标记-stop the world【简单标记下GC Roots能直接关联到的对象】
并发标记-耗时【进行GC Roots Tracing 】
从新标记-stop the world【修正并发标记期间用户程序继续运行而致使标记发生变更那一部分对 象标记记录】
并发清除-耗时
复制代码
没法处理浮动垃圾
对CPU资源敏感
会产生大量的空间碎片
复制代码
重视服务器响应速度的应用
复制代码
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引发停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几回Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(通常状况约等于可用CPU数量)
复制代码
G1收集器时面向服务端应用的垃圾收集器。
在G1中,堆被划分红 许多个连续的区域(region)。采用G1算法进行回收,吸取了CMS收集器特色。
支持很大的堆,高吞吐量
可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收
并行和并发
分代收集
空间整合【总体上:标记-整理算法,局部上:复制算法】
可预测停顿【可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收】
复制代码
初始标记;【标记GC Roots直接关联的对象,stw】
并发标记;【进行GC Roots Tracing 】
最终标记;【再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾】
筛选回收【stw】
复制代码
须要大堆空间、限制的垃圾回收延迟的应用
复制代码
–XX:+UseG1GC 使用G1垃圾回收器
复制代码