计算机系统,包括内存最小的寻址单元是字节;说白了,虚拟机理论上最大内存就是硬件内存,硬件内存是有限的,你占用了,我就用不了了;因此对象不用的时候,回收其占用内存空间,以提升虚拟机资源利用率!让虚拟机有更高的产出!html
程序计数器,栈区,本地方法栈区的生命周期都是和线程绑定的;线程消失,其占用的内存也就释放;java
因此,垃圾回收做用的区域是 堆内存(对象),方法区(常量);python
基本类型在栈区,自动回收!git
String str=”abc”;在方法区常量池中会添加“abc”,后期若是有其余字符串值为”abc”,都会指向常量池中惟一的“abc”;github
当没有String指向常量池中的“abc”时,就回收它了!算法
方法区回收性价比不高!编程
类须要同时知足下面3个条件才能算是“无用的类”:数组
1)该类全部的实例都已经被回收,也就是Java堆中不存在该类的任何实例。浏览器
2)加载该类的ClassLoader已经被回收。缓存
3)该类对应的java.lang.Class 对象没有在任何地方被引用,没法在任何地方经过反射访问该类的方法
虚拟机能够对知足上述3个条件的无用类进行回收,这里说的仅仅是“能够”,而并非和对象同样,不使用了就必然会回收。
在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都须要虚拟机具有类卸载的功能,以保证永久代不会溢出。
在对象中,好比对象头中添加引用计数器,有一个地方引用它就+1;不然-1;计算引用计算器的值来肯定对象是否被引用。
缺点:不能解决循环引用问题
Java JVM是否实现:否,python中实现了。
从垃圾回收的根对象GC Root触发,搜索根对象持有的全部成员变量对象Objs;再从全部成员变量对象Objs出发,搜索Objs持有的全部成员变量对象Objs2;搜索不可到达的对象就是能够垃圾回收的对象!
Stop-the-world,是说GC停顿,在执行算法时要求整个程序暂停,目的是对象引用链不能改变。
由于任何垃圾回收算法判断对象是否存活都使用可达性算法来分析,因此,任何垃圾回收算法,标记阶段都必须Stop-the-world。
1)虚拟机栈中引用的对象,(栈帧中本地变量表中引用的对象)
就是对象A的普通成员变量是对象B;A和B都在栈帧中,均可作GC Root对象
2)方法区中类静态属性引用的对象;
就是对象A的静态成员是对象static B,而 static B在方法区中,B中引用了对象的成员变量,那么B也做为GC Root对象
3)方法区中常量引用的对象
就是对象A的成员对象是final B,final修饰的B默认就是final static B;B中引用了对象的成员变量,那么B也做为GC Root对象
1) 本地方法栈中JNI(native方法中)引用的对象
http://blog.csdn.net/mark2when/article/details/59162810
http://blog.csdn.net/w605283073/article/details/72757684
1)从GC Roots搜索全部不可到达对象
2)准备执行不可到达对象的finalize方法
若是finalize方法执行过,直接垃圾回收
若是finalize方法每执行过,执行它,而后垃圾回收
若是执行finalize方法的过程当中,该对象被其余可到达对象引用了,则该对象逃脱!
/**
* 此代码演示了两点:
* 1.对象能够在被GC时自我拯救。
* 2.这种自救的机会只有一次,由于一个对象的finalize()方法最多只会被系统自动调用一次
* @author zzm
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes, i am still alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize mehtod executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC();
//对象第一次成功拯救本身
SAVE_HOOK = null;
System.gc();
// 由于Finalizer方法优先级很低,暂停0.5秒,以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
// 下面这段代码与上面的彻底相同,可是此次自救却失败了
SAVE_HOOK = null;
System.gc();
// 由于Finalizer方法优先级很低,暂停0.5秒,以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
}
}
运行结果:
finalize mehtod executed!
yes, i am still alive :)
no, i am dead :(
SAVE_HOOK对象的finalize()方法确实被GC收集器触发过,而且在被收集前成功逃脱了。
另一个值得注意的地方是,代码中有两段彻底同样的代码片断,执行结果倒是一次逃脱成功,一次失败,这是由于任何一个对象的finalize()方法都只会被系统自动调用一次,若是对象面临下一次回收,它的finalize()方法不会被再次执行,所以第二段代码的自救行动失败了。
并发标记中,就是用户程序和标记算法同时执行的过程,使用三色标记算法!
它是描述追踪式回收器的一种有用的方法,利用它能够推演回收器的正确性。 首先,咱们将对象分红三种类型的。
黑色:根对象,或者该对象与它的子对象都被扫描
灰色:对象自己被扫描,但还没扫描完该对象中的子对象
白色:未被扫描对象,扫描完成全部对象以后,最终为白色的为不可达对象,即垃圾对象
当GC开始扫描对象时,按照以下图步骤进行对象的扫描:
根对象被置为黑色,子对象被置为灰色,没有引用的对象被置为白色
继续由灰色遍历,将已扫描了子对象的对象置为黑色。
遍历了全部可达的对象后,全部可达的对象都变成了黑色。不可达的对象即为白色,须要被清理
问题来了:并发标记时用户程序更改对象引用关系了怎么办?
程序代码更改对象引用有2种方式:
1)增长对象引用,建立对象Object o=new Object(); 或者o1=new Object()
2)删除对象引用,o=null;
因此并发标记保证应用程序在运行的时候,GC标记的对象不丢失,有以下2中可行的方式:
1)在新增对象时,记录对象的reference关系到可到达的对象结构中;
2)在删除的时候,从可到达对象结构中,删除对象的reference
恰好这对应CMS和G1的2种不一样实现方式:
1)在CMS中,记录新增,不记录删除,采用的是增量更新(Incremental update),只要在写屏障(write barrier)里发现要有一个白对象的引用被赋值到一个黑对象 的字段里,那就把这个白对象变成灰色的。即插入的时候记录下来。
2)在G1中,记录删除,不记录新增,使用的是STAB(snapshot-at-the-beginning)的方式,删除的时候记录全部的对象,它有3个步骤:
第1,在开始标记的时候生成一个快照图标记存活对象
第2,在并发标记的时候全部被改变的对象入队(在write barrier里把全部旧的引用所指向的对象都变成非白的)
第3,可能存在游离的垃圾,将在下次被收集
https://stackoverflow.com/questions/19154607/how-actually-card-table-and-writer-barrier-works
Write Barrier是并发标记时,对用户程序更改对象引用关系的一种监听机制,会把用户程序对对象-引用关系的更改记录到remember set log中,并在后期处理remember set log时告知Garbage Collector;方便垃圾回收器GC。
不一样的是,你记录新增仍是删除。CMS就记录新增,G1就记录删除!
write barrier - a piece of code executed whenever a member variable (of a reference type) is assigned/written to. If the new reference points to a young object and it's stored in an old object, the write barrier records that fact for the garbage collect. The difference lies in how it's recorded.
具体能够看我以前的回答整理
https://www.zhihu.com/question/37028283
GC的本质就是,从GC Roots触发搜索全部不可到达对象,而后执行这些对象的finalize()方法,而后再垃圾回收这些对象!
因此执行finalize()时,若是要回收的对象被可到达对象引用,则该对象逃脱GC。
Reference引用存储的是堆内存的地址
1)强引用Strong reference
2)软引用Soft reference
什么是软引用类型?
软引用类型就是有用但非必须的对象
3)弱引用Weak reference
4)虚引用Phantom reference
引入引用类型的级别,是为了根据是否能够回收内存,把对象进行分类;哪些能够回收,哪些不能回收,这样让JVM的垃圾回收的目的更具体,更高效!
强引用类型就是这种,Object obj=new Object()
当内存空间不足,抛出OutOfMemoryError错误
除非obj=null;不然只要强引用还在,垃圾回收器就不会回收引用的对象!
分2种状况
public void test(){
Object o=new Object();
// 省略其余操做
}
此时,执行test()方法时,test方法进入栈帧,对象o的reference引用在线程栈区建立,而reference指向的对象在堆区;test()方法执行完毕,test方法退出栈帧,对象o的引用自动随之清除,堆内存的Object对象也会被垃圾回收!
结论:不用管,自动清除!
成员变量Object obj=new Object();
在成员变量使命达成后,将来再也不使用,则手动设置obj=null,则对象将被垃圾回收!
一句话:除非你把我置为null,不然JVM是不会回收强引用类型的!(方法内除外)。
你能够保留个人位置,这样就不用从新分配内存了!
强引用在实际中有很是重要的用处,举个ArrayList的实现源代码:
private transient Object[] elementData;
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
注意ArrayList私有成员变量elementData,在clear()执行时,把elementData数组中每一个成员置为null;而不是把elementData置为null;这样数组中的对象都将被垃圾回收;而elementData数组由于是强引用,它的栈reference,在栈内存,数组对应(数组对象)空间在堆内存不变,避免后续调用add()或其余方法时 还得从新分配数组对象空间!
软引用类型修饰的对象是 有用但非必须的对象!什么意思?缺了软引用对象,程序也照常运行!
JVM内存不足时,会清除软引用类型修饰的对象!
生命周期:建立到JVM内存不足,内存充足的时候也可能进行垃圾回收!
String str=new String("abc"); // 强引用
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
虚拟机会在内存溢出以前,回收掉软引用类型的对象str!内存不足时软引用类型对象自动被垃圾回收!
至关于,内存不足,JVM 自动把str置为null,而后等到垃圾回收str。
If(JVM.内存不足()) {
str = null; // 转换为软引用
System.gc(); // 垃圾回收器进行回收
}
能够用来实现内存敏感的高速缓存!
一句话:有内存,我存在,没内存,我奉献个人内存!你能够在你须要时使用我,不须要我时清除我!
虚引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是从新进行请求仍是从缓存中取出呢?这就要看具体的实现策略了。
(1)若是一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,须要从新请求;
(2)若是将浏览过的网页存储到内存中会形成内存的大量浪费,甚至会形成内存溢出
这时候就可使用软引用
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 尚未被回收器回收,直接获取
}else{
prev = new Browser(); // 因为内存吃紧,因此对软引用的对象回收了
sr = new SoftReference(prev); // 从新构建
}
软引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。?
弱引用类型的对象是更没必要须的对象!
软引用类型对象会在jvm内存不足时被垃圾回收!
弱引用类型对象会在垃圾回收线程发现它时就被回收,不论jvm内存是否不足!
不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象。
生命周期:建立到第一次垃圾回收!
自动回收str,不论内存是否不足
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
String abc = abcWeakRef.get(); //一句代码就把str转成强引用类型了
1)偶尔使用,随用随取的对象
若是这个对象是偶尔的使用,而且但愿在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。
2)引用一个对象又不想改变它的生命周期时,使用弱引用类型
public class ReferenceTest {
private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();
public static void checkQueue() {
Reference<? extends VeryBig> ref = null;
while ((ref = rq.poll()) != null) {
if (ref != null) {
System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).id);
}
}
}
public static void main(String args[]) {
int size = 3;
LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>();
for (int i = 0; i < size; i++) {
weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq));
System.out.println("Just created weak: " + weakList.getLast());
}
System.gc();
try { // 下面休息几分钟,让上面的垃圾回收线程运行完成
Thread.currentThread().sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
checkQueue();
}
}
class VeryBig {
public String id;
// 占用空间,让线程进行回收
byte[] b = new byte[2 * 1024];
public VeryBig(String id) {
this.id = id;
}
protected void finalize() {
System.out.println("Finalizing VeryBig " + id);
}
}
class VeryBigWeakReference extends WeakReference<VeryBig> {
public String id;
public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) {
super(big, rq);
this.id = big.id;
}
protected void finalize() {
System.out.println("Finalizing VeryBigWeakReference " + id);
}
}
最后的输出结果为:
Just created weak: com.javabase.reference.VeryBigWeakReference@1641c0
Just created weak: com.javabase.reference.VeryBigWeakReference@136ab79
Just created weak: com.javabase.reference.VeryBigWeakReference@33c1aa
Finalizing VeryBig Weak 2
Finalizing VeryBig Weak 1
Finalizing VeryBig Weak 0
In queue: Weak 1
In queue: Weak 2
In queue: Weak 0
与软引用,弱引用不一样,虚引用指向的对象十分脆弱,咱们不能够经过get方法来获得其指向的对象。
虚引用类型必须和引用队列一块儿使用!PhantomReference类实现虚引用!
惟一做用就是当其指向的对象被回收以后,本身被加入到引用队列,用做记录该引用指向的对象已被销毁。
1)虚引用类型可让你知道它指向的对象何时从内存中移除
设置了虚引用的对象在被垃圾回收时会接到系统发送的通知!
而实际上这是Java中惟一的方式。这一点尤为表如今处理相似图片的大文件的状况。当你肯定一个图片数据对象应该被回收,你能够利用虚引用来判断这个对象回收以后在继续加载下一张图片。这样能够尽量地避免可怕的内存溢出错误。
2)避免析构问题
参考https://droidyue.com/blog/2014/10/12/understanding-weakreference-in-java/
引用类型 |
被垃圾回收时间 |
用途 |
生存时间 |
强引用 |
历来不会 |
对象的通常状态 |
JVM中止运行时终止 |
软引用 |
在内存不足时 |
对象缓存 |
内存不足时终止 |
弱引用 |
在垃圾回收时 |
对象缓存 |
gc运行后终止 |
虚引用 |
Unknown |
Unknown |
Unknown |
Java 7之基础 - 强引用、弱引用、软引用、虚引用
http://blog.csdn.net/mazhimazh/article/details/19752475
译文:理解Java中的弱引用- 技术小黑屋
https://droidyue.com/blog/2014/10/12/understanding-weakreference-in-java/
最基础的算法,其余算法都是它的改进版!
1)标记全部没有引用的对象
2)统一回收全部被标记的对象
无,理论算法
1)标记和清除的效率都不高,为何?
2)产生大量的内存碎片,重点
标记-复制;
1) 将内存分为等大小2块区域A和B,
2) 只使用一块区域A,
3) 等A区域满了,对A进行标记,把全部存活对象集中复制到B区域
4) 清理A区域
年轻代 ,对象存活率较低,复制的开销低!
若是使用在年老代,对象存活率高,复制的开销也高!
优势:解决内存缝隙问题
缺点:内存使用率低
商业虚拟机大部分使用这种算法,Hotspot虚拟机年轻代Eden和2个Suivivor比例为
8:1:1就是为了不内存碎片问题
老年代,对象存活率较高,不用复制算法
1) 遍历GC Roots,对全部存活对象标记
2) 把全部存活对象,集中复制到内存某一端(最前或者最后)连续区域A
3) 清理除了A外全部区域
优势:连续空间
缺点:效率不高,标记全部存活对象,并记录全部对象引用地址
通过大量实际观察得知,在面向对象编程语言中,绝大多数对象的生命周期都很是短。分代收集的基本思想是,将堆划分为两个或多个称为 代(generation) 的空间。新建立的对象存放在称为 新生代(young generation) 中(通常来讲,新生代的大小会比 老年代 小不少),随着垃圾回收的重复执行,生命周期较长的对象会被 提高(promotion) 到老年代中。所以,新生代垃圾回收和老年代垃圾回收两种不一样的垃圾回收方式应运而生,分别用于对各自空间中的对象执行垃圾回收。新生代垃圾回收的速度很是快,比老年代快几个数量级,即便新生代垃圾回收的频率更高,执行效率也仍然比老年代垃圾回收强,这是由于大多数对象的生命周期都很短,根本无需提高到老年代。
把Java堆分为年轻代(Eden:Survior1:Survivor2=8:1:1),老年代,再加上方法区的永久代;一共3个区域;
按照区域的特色,分别有适合垃圾回收算法!
对象存活率低,复制算法;
对象存活率高,标记清理,或者标记整理
GC Roots主要存在于常量池全局变量,线程栈本地变量表的引用;但如今应用方法区有上百兆,如何高效查找到全部GC Root?
GC停顿后,并不是遍历全部线程栈和方法区查找GC Roots;而是在类加载的过程当中虚拟机就把对象什么偏移量上是什么类型变量计算出来,放在OopMap数据结构中,JIT编译过程也会记录栈和寄存器中哪些位置是引用,这样JVM遍历OopMap就知道哪些是GC Roots了。
https://stackoverflow.com/questions/26029764/what-does-oop-maps-means-in-hotspot-vm-exactly
OopMap是记录对象索引在Java栈的哪些位置的数据结构,OopMap的首要做用是查找GC Roots;当对象从堆中删除时,其索引也被删除了,因此OopMap要在须要的时候更新reference纪律!
问题来了:何时更新OopMap?
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html
查找OopMap就知道GC Roots了,问题是程序一直在运行,能够致使OopMap变化的指令不少,何时记录(更新)OopMap?
你不知道何时应该GC,若是时刻记录OopMap,那么内存成本很高!
因此这个问题能够转化成-JVM应该何时GC?
由于GC的时候才要查找GC Roots,查找GC Roots才须要OopMap!
那何时才应该GC呢?
答案:safepoint的时候!
http://www.sczyh30.com/posts/Java/jvm-gc-safepoint-condition/
https://stackoverflow.com/questions/19201332/java-gc-safepoint
safepoint的实现机制取决于JVM的实现机制!
HotSpot中safepoint指的是: JVM在GC以前暂停程序来进行垃圾回收的机制!
Safepoint期间,全部java线程中止,native若是不和JVM交互则不用中止,全部GC Roots是肯定的,引用和对象的关系链也是肯定的,在这个期间垃圾回收程序回收java堆上无引用的对象!
若是要触发一次GC,那么JVM中全部Java线程都必须到达GC Safepoint。
JVM只会在特定位置放置safepoint,好比:
1)内存分配的地方(allocation,即new一个新对象的时候)
2)长时间执行区块结束的时刻(如方法调用,循环跳转,异常跳转等)
Safepoint的时刻就是GC的时刻!GC的时刻就是Stop-the-world时刻!
因此safepoint时刻就是STW时刻!
这样,JVM只在程序运行到1),2)状况时才记录/更新OopMap;
问题来了,如何让全部线程都到达本身最近Safepoint呢?
两种方式,抢占式和主动式是从线程的角度说的!
1)抢占式中断-理论方法没有JVM实现
GC发生时,首先把全部线程所有中断,遍历检查全部线程,若是发现有线程不在safepoint,则恢复该线程,知道它跑到安全点上!直到全部线程到safepoint上,进行垃圾回收。
2)主动式中断-大部分JVM实现
GC发生时,JVM不中断全部线程;全部线程检查本身是否在safepoint上,若是在则在本身线程上作一个标志,而且线程自动暂停;全部线程依自身状况前后主动暂停本身;
而后进行垃圾回收!
Garbage collection pauses垃圾回收暂停
Code deoptimization代码优化
Flushing code cache刷新代码缓存
Class redefinition (e.g. hot swap or instrumentation)类别从新定义(例如热插拔或仪器)
Biased lock revocation有偏见的锁定撤销
Various debug operation (e.g. deadlock check or stacktrace dump)
各类debug操做,如死锁检查,堆栈dump
主动式中断线程的方式Safepoint被大部分JVM实现,主动式中断要求一点:线程是清醒的执行的状态,若是线程处于阻塞状态或者等待状态怎么办?
JVM中程序不等人啊,程序要求一致运行!怎么办?
用Safe Region安全区域解决!
Safe region指一段代码区域,这个区域中不会更改引用-对象的关系,因此在这个区域中任意位置均可以开始GC。
线程阻塞或者等待状态就不会更改引用对象的关系!
1)在GC前,全部线程标注本身是否进入safepoint状态,是否进入safe region状态!
2)JVM不会去管进入safe region状态的线程;只需等待其余线程进入safepoint状态便可开始GC回收;
3)若是进入safe region状态线程要离开safe region状态,JVM先查看是否完成GC任务,完成则能够离开,不然不能离开!
图片来自https://crowhawk.github.io/2017/08/15/jvm_3/
http://zqhxuyuan.github.io/2016/07/26/JVM/ 多图
在垃圾回收中;
并行,指垃圾回收器并行多个线程同时执行;但垃圾回收器工做期间,程序STW;
并发,指用户程序与垃圾回收器并发执行;不必定是并行的,可能交替执行!
用户程序与垃圾回收器能并行同时执行吗?
不能全程同时执行;某些时刻t,须要用户程序STW,
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值!
吞吐量=cpu运行用户代码时间/(cpu运行代码时间+cpu垃圾回收时间)
虚拟机运行100分钟,垃圾回收1分钟,吞吐量就是99%
https://www.zhihu.com/question/41922036
https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc
针对HotSpot的GC,分为两大类:
1)Partial GC:并不收集整个堆的模式
Young GC
只收集young代的GC
Old GC
只收集old代的GC,只有CMS是这个模式,其余说收集old的都会带着收集young
Mixed GC
收集整个young代+部分old代的GC,只有G1是这个模式
2)Full GC,收集整个堆,young代,old代,perm代(jdk8把perm移到了堆区)
通常Major GC指Full GC,也有人认为Major GC指Old GC,因此要问清楚他时候的究竟是哪一个!
清理年轻代(Eden,2Survivor)的Young GC又叫作Minor GC
当Eden区满了,不能分配内存给new的对象时;
1.Minor GC频繁发生
Eden常常满,对象常常死亡,因此Minor GC频繁发生
2.Minor GC后没有内存碎片
年轻代Minor GC采用复制算法,通过标记后,Eden区的存活对象被复制到2个(0,1)Survivor区的1个;
对于Eden区,内存指针能够从0开始,没有内存碎片;
对于Survivor区,2个区域是没有前后顺序的,一个使用,另一个就用来复制;因此Survivor的1区域内存指针也老是从0开始的,1个Survivor区没有内存碎片;
3.Minor GC清理年轻代,老年代也不会被清理。
Minor GC采用复制算法,标记-复制的标记阶段中,从年老代指向年轻代的引用被认为是有效的引用,而从年轻代指向年老代的引用则认为是无效的引用!
4.Minor GC的 STW时间和Eden中垃圾对象的多少有关
Major GC和Full GC是非官方的说法。
Major GC说的就是Full GC
Full GC是清理young+old+perm(若是属于java 堆)的GC;
https://stackoverflow.com/questions/24766118/when-is-a-full-gc-triggered
1)准备触发Minor GC时,发现Old区剩余的空间不够,若是不使用CMS,则触发Full GC。
由于只有CMS是单独收集Old区的!其余收集Old去的都会收集整个堆!
2)若是堆中有Perm代(jdk8),当Perm区不够时也会触发Full GC
3)System.gc( )触发Full GC
4)Heap dump带的GC也是Full GC
把Heap中数据生成dump文件来分析JVM故障。
5)调节young,old区域size时也触发Full GC;
Parallel Scavenge(-XX:+UseParallelGC)框架下,默认是在要触发full GC前先执行一次young GC,而且两次GC之间能让应用程序稍微运行一小下,以期下降full GC的暂停时间(由于young GC会尽可能清理了young gen的死对象,减小了full GC的工做量)。控制这个行为的VM参数是-XX:+ScavengeBeforeFullGC。
年轻代serial,parNew,Parallel Scavenge使用复制算法;
老年代Serial old,Parallel old使用标记-整理算法,CMS使用标记-删除算法;
不一样厂商,不一样JVM,实现的垃圾收集器也不一样!
用户也会根据应用特色组合各年代所使用的的垃圾收集器!
有连线,说明能够组合使用!
不然,不能组合! Tenured gen老年代
注重吞吐量以及CPU资源敏感的场合,能够优先考虑Parallel Scavenge+Parallel Old收集器
Serial是一个使用复制算法的单线程垃圾回收器,只使用单cpu,单线程执行GC,并且执行GC的过程必须暂停程序。
1)JVM client模式下默认垃圾回收器!新生代收集器!
何时使用Serial垃圾回收期?
2)小应用,占用java堆内存少200M之内,停顿时间能够控制在100毫秒之内;这样不影响客户端体验
3)单核cpu或2核cpu;serial收集器没有线程交互的开销,专心作垃圾收集对于少核cpu来讲效率较高!
4)与CMS配合使用
1)垃圾回收前STW,单线程对新生代Eden使用复制算法收集;
2)垃圾回收前STW,单线程对老年代Old使用标记整理算法收集;
ParNew就是Serial收集器的多线程版本!暂停程序!也是复制算法
1)新生代收集器
何时使用ParNew垃圾回收器?
JVM Server模式下默认垃圾回收器!
2)与CMS配合使用
CMS老年代收集器只能和Serial,ParNew收集器使用!
为何使用ParNew回收器?
1)垃圾回收前STW,多线程对新生代Eden使用复制算法收集;
2)垃圾回收前STW,单线程对老年代Old使用标记整理算法收集;
它默认开启的收集线程数与CPU的数量相同,在CPU很是多的状况下可以使用-XX:ParallerGCThreads参数设置
1)使用-XX:+UseConcMarkSweepGC选项设置使用CMS进行老年代收集后,新生代默认就使用ParNew回收器!
2)强制指定使用ParNew回收器
-XX:+UseParNewGC选项
Scavenge是清除的意思
Parallel是一个使用复制算法的新生代垃圾回收器;并行多线程收集;
吞吐量优先
Parallel Scavenge收集器的目标是可控的吞吐量!
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值!
吞吐量=cpu运行用户代码时间/(cpu运行代码时间+cpu垃圾回收时间)
虚拟机运行100分钟,垃圾回收1分钟,吞吐量就是99%
停顿时间越短,越适合须要与用户交互的程序!须要高吞吐量!!
新生代收集器
何时使用Parallel Scavenge垃圾回收器?
为何使用Parallel Scavenge垃圾回收器?
Parallel Scavenge垃圾回收期如何工做?
Parallel Scavenge提供了2个参数,用于控制吞吐量,分别是:
1)最大垃圾收集停顿时间:
-XX:MaxGCPauseMillis 数值>0
收集器尽可能保证回收时间不超过该值,具体停顿时间由实际状况而定!
正常状况下,垃圾回收的停顿时间是不会改变的,除非你调小新生代内存的大小!如收集500M和收集300M相比,确定后者时间少,可是后者确定收集的更频繁!原来10S收集一次,每次停顿100毫秒;如今每5秒收集一次,每次停顿70毫秒;停顿时间将下来,但总体吞吐量可能也降下来了!
2)吞吐量大小,直接设置吞吐量
-XX:GCTimeRatio 数值大于0小于100,默认是99
吞吐量=代码时间 /(代码时间+停顿时间)
3)Parallel Scavenge有自适应调节策略GC Ergonomics
-XX:+UseAdaptiveSizePolicy 是一个开关参数
设置后,虚拟机按照实际状况自动调节新生代Eden与Survivor的比例,自动调节此生老年代对象的年龄等参数!
动态调整这些参数来得到最大的吞吐量!
具备自适应调节策略!
Serial收集器的老年代版本!使用复制-整理算法!
1)Client模式的 老年代回收器!
2)Server模式下,做为CMS的备案。当CMS发生concurrent mode Failure使用!
Parallel Scavenge收集器的老年代版本,使用标记-整理算法
吞吐量优先
注重吞吐量以及CPU资源敏感的场合,均可以优先考虑Parallel Scavenge+Parallel Old收集器组合!
如何使用Parallel Old收集器?
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html
Concurrent Mark Sweep清扫,CMS使用并发的标记-清除算法
最小化GC停顿时间。
如何作到的?--这是寻找信息时给别人的回答
http://www.cnblogs.com/littleLord/p/5380624.html
首先,CMS垃圾回收器的设计目标 是 最小化 STW的时间,也就是最小化用户程序的暂停时间!
而后,要让程序运行,又要垃圾回收,想一个这种的办法就是让程序和垃圾回收同时运行!
可是,垃圾回收的可达性分析阶段,对不可到达对象的标记时必需要STW(由于对动态的reference-对象关系标记是没有用的);
问题来了:如何减小STW呢?
CMS的作法:
第一,初始标记,初始标记不是递归标记全部不可到达对象;而是从GC Roots出发,只标记第一级关联对象;须要STW,可是时间很短,由于第一级对象对于全部对象来讲数量不多
第二,并发标记,让CMS垃圾回收程序和用户程序同时运行;因此没有STW;此次标记确定是从第一级关联对象出发;搜索全部不能到达的对象;
这是一个很消耗时间的过程,可是由于用户程序在运行,因此没影响;
第三,从新标记,由于第二阶段用户程序在运行,可能产生新的对象;这一阶段须要把这些新产生的对象找出来;因此须要STW;
若是是我设计这个阶段的搜索,我必定会记录第二阶段什么时间点,用户新增了对象;这样查找新增的对象时间也不会好久;
第四,并发清理;直接删除第三阶段肯定的全部不可到达对象;产生了内存碎片;
工做流程分为4步:
1)初始标记CMS initial mark -STW
标记GC Roots能直接关联到的全部一级关联对象,会STW,时间短,由于GC Roots数量和总体对象数量相比仍是小!
暂停,时间短
2)并发标记CMS concurrent mark
从GC Roots的一级关联对象出发,递归标记全部能标记的对象;
时间长,不暂停
3)从新并发标记CMS remark -STW
从新标记可到达对象;
为何要从新标记?为何还要STW从新标记?
由于2)中并发标记时程序运行,可能改变reference-对象的关系;因此若是想要一个肯定的reference-对象的关系,必须STW。
时间长,暂停
4)并发清除CMS concurrent sweep
并发清除全部垃圾对象
1)使用CMS收集Old
-XX:+UseConcMarkSweepGC选项,则老年代使用CMS,而后默认年轻代使用ParNew收集器。
2)设置Old内存占满多少触发CMS垃圾回收
能够用-XX:CMSInitiatingOccupancyFraction值来设定老年代内存达到多少百分比来触发CMS垃圾回收!
可是,该值设置多少合适呢?
该值设置过低,CMS垃圾回收太频繁;
设置过高,预留给程序内存不足,CMS回收失败,触发Serial Old回收,停顿时间更长
3)解决CMS碎片问题
使用-XX:+UseCMSCompactAtFullCollection(默认开启);用于CMS顶不住要进行FullGC时开启内存碎片的合并整理过程,空间碎片没有了,可是STW停顿时间变长了。
4)设置执行多少次不压缩的FullGC后压缩
使用-XX:CMSFullFCsBeforeCompaction,默认值为0,每次进入Full GC都碎片整理。
CMS默认启动回收线程数 tnum=( Cpu数量+3 ) / 4
也就是说,即使不+3, CMS也要占用1/4的Cpu资源。
优势:
1)用户程序STW停顿时间少;
缺点:
提高CMS的吞吐量(提高程序代码运行时间)是以牺牲程序的性能为前提的
是因为GC和程序同时运行致使。
1)CMS收集器对Cpu资源敏感,并发收集阶段会发生于用户程序抢Cpu资源状况。
并发收集垃圾过程通常占用1/4 cpu资源!
2)CMS失败Concurrent Mode Failuer用户程序也暂停,可能致使另外一次Full GC的产生!
Why CMS失败致使Full GC?
3)CMS使用并发标记-清除算法,产生大量内存碎片。
1)并发收集阶段,因为用户程序也要运行,因此,须要给程序预留足够内存空间;
2)所以,CMS收集器不能等老年代填满再收集,由于老年代也须要预留空间给程序。
JDK1.5默认设置下,当老年代使用68%空间则CMS就会被激活;68%的设置能够用
-XX:CMSInitiatingOccupancyFraction值来设定。
JDK1.6,默认92%老年代使用激活CMS回收;这时若是CMS运行期间程序所需老年代内存>8%,则会发生Concurrent Mode Failure失败,
这时,JVM启动备案,临时启用Serial Old收集器来从新进行老年代垃圾回收!
CMS并发清理回收垃圾的阶段,程序是运行的;运行就可能产生新的reference-对象;
因此,浮动垃圾,就是CMS并发清理过程,由用户程序新生成的对象!
CMS没法处理浮动垃圾Floating Garbage,只能下次CMS时回收!
在并发标记,清理的时候让GC线程,用户程序交替运行,尽可能减小GC线程独占资源的时间!
后果:整个GC过程会更长,可是对用户程序影响就显得少一些,速度降低没有那么快!
使用效果也不太好!
官方设置为deprecated,再也不提倡用户使用!
Garbage First Collector,简称G1 Collector;是Hotspot1.7之后面向大内存(Heap区n~10G)、多核系统的收集器。
那到底什么是G1收集器?
G1是一种能够操做全堆的,并发、并行、部分Stop The World、使用Copying算法收集region的分代的增量式收集器!
G1在JDK1.9中成为默认收集器!
Region?啥是Region?
Region是区域的意思,就是内存的一段地址区间;G1算法将Heap区域分红独立等大的Region,G1兼收代的概念,每一个Region属于一个代(可能不一样部分),如E表明Eden的region,O表明Old代的region等等。
操做全堆?
能收集young和old,而不是像CMS只能收集old。
并发?
未
并行?
部分STW?
使用Copying算法?
G1将全堆内存分割成等大小独立的Region,在GC阶段,A的region存活对象被Copying到B Region。
问题来了?
Region是内存,JVM如何记录对象内存地址的变化的?
分代?
G1中兼收Young代(Eden,Survior),Old代的概念;不过G1分割过的堆,代的地址空间不是连续的了!极可能一个region是Eden的,右边相邻的region是Old代的!
增量收集?
G1的设计目标/关注点是最小化STW;为了实现这一目标,G1把Heap堆切分为不少Region,而后根据Region回收的价值(如最多垃圾对象最早回收)回收相应Region来解决内存不足的问题,而不是以往老套算法整代区域都要GC。
Garbage First,First是啥意思?
这里的First是指回收价值最高;
那怎么叫回收价值最高呢?
确定是垃圾对象占比越高的Region回收价值越高;
每次G1收集时会判断各Region的活性(存活对象的占比),垃圾对象占比越多回收价值越高,而后G1会参考按照以前回收某些Region消耗的时间,来估算此次回收哪些Region。用最小时间获取最大收益!因此叫Garbage First!
以什么为单位回收什么呢?
Region;
合起来就是:对回收价值最高的Region进行回收!
为何要分region呢?
这个思想来源是分治思想,仍是要回归到G1的设计目标:最小化STW!
先不说标记,光说回收!收集一整代时间长?仍是收集一整代的某一部分时间长?
确定是后者时间短,因此就分region了!
这种将Heap区划分红多块的理念源于:当并发后台线程寻找可回收的对象时、有些区块包含可回收的对象要比其余区块多不少。虽然在清理这些区块时G1仍然须要暂停应用线程、但能够用相对较少的时间优先回收包含垃圾最多区块。这也是为何G1命名为Garbage First的缘由:第一时间处理垃圾最多的区块。
Hotspot以前已经携带了Serial, Paralel, CMS等收集器,为何还须要研发一个新的G1呢?垃圾收集的三个性能指标: footprint, max pause time, throughput彷佛像CAP同样不能同时知足。
在服务端更注重的是短停顿时间,也就是stop-the-world的时间,另一段时间内的总停顿时间也是一个衡量指标。
Mark-Sweep, Mark-Compact均须要和清理区域大小成比例的工做量,而Copying算法则须要通常是一半的空间用于存放每次copy的活对象。CMS的Initial Marking和Remarking两个STW阶段在Heap区愈来愈大的状况下须要的时间越长,而且因为内存碎片,须要压缩的话也会形成较长停顿时间。因此须要一种高吞吐量的短暂停时间的收集器,而无论堆内存多大。
标记:从某一时刻t的对象图快照开始标记;
而后标记程序和用户程序同时执行;须要记录t时刻用户程序新增了哪些对象,并发标记过程当中这些对象都被认为是存活对象,不会对它们进行标记
避免长暂停时间,能够考虑将堆分红多个部分,一次收集其中一部分,这样的方式又叫作增量收集(incremental collection), 分代收集也能够当作一种特殊的增量收集。
G1收集器将堆内存划分为一系列大小相等的Region区域,Region大小在1MB到32MB在启动时肯定,G1一样也使用分代收集策略,将堆分为Eden, Survivior, Old等,只不过是按照逻辑划分的,每一个Region逻辑上属于一个分代区域,而且在物理上不连续,当一个Old的Region收集完成后会变成新可用Region并可能成为下一个Eden Region。当申请的对象大于Region大小的一半时,会被认为是巨型对象,巨型对象默认会被放在Old区,但若是巨型对象知识短时间存在,则会被放入一个Humongous Region(巨型区域)中。当一个Region中是空的时,称为可用Region或新Region。
JVM指标:
1)JVM的吞吐量,
2)GC暂停时间,
3)垃圾对象回收时间;
4)程序占用内存;
G1须要在这4个JVM指标中取舍!
为何须要G1时说明白一点,须要一种无论堆内存多大状况下都可以高吞吐量,短暂停时间的垃圾回收器!
因此G1选择了吞吐量,暂停时间;舍弃了垃圾对象回收时间;程序占用内存;
高吞吐量怎么得到?提高年轻代大小,从程序开始运行到垃圾回收的的时间就变长了;
年轻代变大了,回收年轻代时间也变久了,STW时间也变大了!怎么办?
分治,把堆内存分为等大小的内存区域Region;而后按照Region垃圾对象占比多少选择回收价值最高的region来进行GC;对于一次GC,不会回收整代内存(不连续也不必,由于涉及目标是缩短暂停时间),而是只回收某些Region,回收的内存足以支撑程序运行便可;
这样以回收某些region,回收屡次的方式,这样维持了一个较高JVM的吞吐量,但减少了单次GC暂停时间;
大部分GC Roots对象都是Old对象吗?
是
是否要扫描Old代的所有region?
老年代的全部对象都是根么?这样扫描下来会耗费大量的时间
G1引进了RSet的概念。它的全称是Remembered Set,做用是跟踪指向某个区域内的对象引用。这样就能够用RS以Region为单位GC,而不用扫描整个堆GC。
通常状况下,这个RSet实际上是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。
HashTable结构:
RememberSet数据结构:region起始地址(0~11)-region的CartTable
由于1个region对应1个CartTable,因此hashTable中每一个key只有1个item,item是CartTable,CartTable是字节数组,字节数组记录了被外部region引用的Cart
RS主要存放:1)old到young的引用;2)old到old的引用
在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅须要扫描这一块区域,而不须要扫描整个老年代。
其余的region中对象引用我本身region中的对象,本身region中对象属于哪一个卡表,记录哪些卡表的索引
从region角度,有2种信息,分别是:我引用了谁?point-out,谁引用了我point-in?
从回收region的角度,确定是谁引用了我更有价值!!
因此在G1中,并无使用point-out,这是因为G1分区过小,分区数量太多,若是是用point-out的话,会形成大量的扫描浪费,有些根本不须要GC的分区引用也扫描了。因而G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当作根来扫描就避免了无效的扫描。因为新生代有多个,那么咱们须要在新生代之间记录引用吗?这是没必要要的,缘由在于每次GC时,全部新生代都会被扫描,因此只须要记录老年代到新生代之间的引用便可。
RSet for Refuib2中每一个红格结构: region开始地址-该region的cardTable
须要注意的是,若是引用的对象不少,赋值器须要对每一个引用作处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每一个区域称之为卡。卡一般较小,介于128到512字节之间,用来存放对象?应该是。Card Table一般为字节数组,由Card的索引(即数组下标)来标识每一个分区的空间地址。默认状况下,每一个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。通常状况下,这个RSet实际上是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。
经过Index(0~某数字)应该不能直接找到Card的内存地址,应该还有其余映射关系。
GC和用户程序并发执行时,用户线程何时修改跨region的引用??
维护remembered set须要mutator(用户程序)线程在可能修改跨Region的引用的时候通知collector, 这种方式一般叫作write barrier(和GC中的Memory Barrier不一样), 每一个线程都会有本身的remembered set log,至关于各自的修改的card的缓冲buffer,除此以外还有全局的buffer, mutator本身的remember set buffer满了以后会放入到全局buffer中,而后建立一个新的buffer。
Snaphot-At-The-Beginning简称SATB
对象的分配策略。它分为3个阶段:
1)TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
2)Eden区中分配
3)Humongous区分配
TLAB为线程本地分配缓冲区,它的目的为了使对象尽量快的分配出来。若是对象在一个共享的空间中分配,咱们须要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每个线程都有一个固定的分区用于分配对象,即一个TLAB。分配对象时,线程之间再也不须要进行任何的同步。
对TLAB空间中没法分配的对象,JVM会尝试在Eden空间中进行分配。若是Eden空间没法容纳该对象,就只能在老年代中进行分配空间。
阶段目标
G1收集器的标记阶段负责标记处存活的对象、而且计算各个Region的活跃度等。
标记算法
G1使用了一种Snaphot-At-The-Beginning简称SATB的标记算法, 记录标记开始时的对象图的快照,以后并发收集过程当中的新申请的对象都认为是存活对象。
快照标记,和CMS同样没法解决浮动垃圾问题!
什么时候标记
当堆使用比例超过InitiatingHeapOccupancyPercent后开始marking阶段,使用SATB记录marking开始阶段的对象图快照。
如何标记
G1使用bitmap标记哪些位置已经完成标记了,一个bitmap的bit表示8bytes, 咱们使用两个marking bitmap,一个previous、一个next,
previous marking bitmap表示已经完成标记的部分,标记完成后会交换previous和next
标记阶段分为一下几个步骤:
标记周期的最开始是清除next marking bitmap,是并发执行的。而后开始initial marking phase, 会暂停全部线程,标记出全部能够直接从GC roots能够直接到达的对象,这是在Young GC的暂停收集阶段顺带进行的。
找出全部的GC Roots的Region, 而后从这些Region开始标记可到达的对象,是一个并发阶段。
这个阶段G1经过tracing找出整个堆全部的可到达的对象。这个阶段是并发执行的;
用户程序若是修改对象引用关系,则记录修改到Remember set log中Rslog;
Final mark是一个STW阶段,G1将全部的SATB buffer处理完成。就是处理Rslog,并到更新可到达对象关系Remember Set中;
marking的最后一个阶段,G1统计各个Region的活跃性,彻底没有存活对象的Region直接放入空闲可用Region列表中,而后会找出mixed GC的Region候选列表。
和通常的分代式收集不一样,G1中除了普通的Young GC,还有Mixed GC。
它仍然属于分代收集器。
新生代的垃圾收集依然采用暂停全部应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
老年代也分红不少区域,G1收集器经过将对象从一个区域复制到另一个区域,完成了清理工做。
这就意味着,在正常的处理过程当中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。
收集目标:young gen的Eden区
什么时候触发:
当Eden区域没法申请新的对象时(满了),就会进行Young GC,
收集过程:
Young GC将Eden和Survivor区域的Region(称为Collection Set, CSet)中的活对象Copy到一些新Region中(即新的Survivor),当对象的GC年龄达到阈值后会Copy到Old Region中。因为采起的是Copying算法,因此就避免了内存碎片的问题,再也不须要单独的压缩。
应用程序是否暂停
Young GC过程当中,应用程序暂停。
最终Eden空间的数据为空,GC中止工做,应用线程继续执行。
Young GC 阶段:
阶段1:根扫描
静态和本地对象被扫描
阶段2:更新RS
处理dirty card队列更新RS,point-in的引用更新
阶段3:处理RS
检测从年轻代指向年老代的对象
阶段4:对象拷贝
拷贝存活的对象到survivor/old区域
阶段5:处理引用队列
软引用,弱引用,虚引用处理
GC对象:young+old
什么时候触发
当old区Heap的对象占总Heap的比例超过InitiatingHeapOccupancyPercent以后,就会开始ConcurentMarking, 完成了Concurrent Marking后,G1会从Young GC切换到Mixed GC,
GC步骤
全局并发标记(global concurrent marking)
拷贝存活对象(evacuation)
global concurrent marking的执行过程
在G1 GC中,它主要是为Mixed GC提供标记服务的,并非一次GC过程的一个必须环节。global concurrent marking的执行过程分为五个步骤:
1)初始标记(initial mark,STW)
在此阶段,G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。
根区域扫描(root region scan)
G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,而且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。
2)并发标记(Concurrent Marking)
G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,能够被 STW 年轻代垃圾回收中断
3)最终标记(Remark,STW)
该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。
4)清除垃圾(Cleanup,STW)
在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操做。在统计期间,G1 GC 会识别彻底空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。
和CMS同样,G1的一些收集过程是和应用程序并发执行的,因此可能尚未回收完成,是因为申请内存的速度比回收速度快,新的对象就占满了全部空间,在CMS中叫作Concurrent Mode Failure, 在G1中称为Allocation Failure,也会降级为一个STW的fullgc。
G1使用一种Snapshot-At-The-Begining的方式记录活对象,也就是那一时刻(整个堆concurrent marking开始的时候)的内存的Object graph, 可是在以后这里面的对象可能会变成Garbage, 叫作floating garbage 只能等到下一次收集回收掉。
1.在JDK7及之后,大内存(n~10G),多核系统
我的认为更换GC或者进行调优只能算是系统的锦上添花,并不能做为主要解决系统性能问题的关键,出现内存问题时,应当以修改应用代码为主、编写清晰的GC友好的代码,选择与应用场景合适的收集器能够提升系统的性能。
如今推荐从CMS更换到G1的一些状况以下:
2.Java堆的50%以上都是活对象
3.对象的分配速率变化很大
4.因为old gc或压缩致使不可忍受的长时间的暂停
最小化STW时间
1)可以实现软停顿目标收集??软停顿?,而且具备高吞吐量,具备可预测停顿时间
1)最小化STW时间,GC程序能够和用户程序并发执行;
2)分代收集,总体上看是标记-整理算法,局部(region)上看是复制算法;
3)没有内存碎片
4)STW停顿可预测
CMS:设置了最小GC时间,实际GC时间依据JVM状况而定
G1:能让使用者指定M毫秒的时间片断内,消耗在GC上的时间不超过N毫秒,很厉害!
G1设计就是为了不Full GC,因此G1跟踪region里垃圾状况(回收所得到的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据容许的收集时间,优先回收价值最大的Region,这也就是Garbage-First的由来!
https://liuzhengyang.github.io/2017/06/07/garbage-first-collector/
GC完整过程详解
http://blog.jobbole.com/109170/ 深刻理解java G1收集器 【到】