深入JVM内核---JVM内存模型
在上一篇博客中讲GC算法的时候提到了一些关于JVM内存模型.JVM中用heap堆来存储运行时的数据,所有类实例和数组由堆分配内存,JVM启动时创建堆(heap memory),在堆以外的内存叫非堆(non-heap memory)
1.堆(Heap space)
在JVM的堆内存空间中,从大的层面划分,可以分为新生代(Young)和老年代空间(Old).其中Young空间又被分为两个部分和3个板块,分别为1个Egen区和2个survivor区.
Minor GC
从年轻代空间(包括Egen和survivor区域)回收内存被称为Minor GC.每次Minor GC会清理年轻代的内存.
Eden区域:
是用来存放使用new或者newInstance等方式创建的对象,默认都是存放在Eden区,除非这个对象太大,或者超过了设定的阈值-XX:PretenureSizeThresold,这样的对象会被直接分配到Old区域。
Survivor(幸存)区
一般称S0,S1,理论上他们是一样大的,解释一下,他们是如何工作的: 在不断创建对象的过程中,Eden区会满,这时候会开始做Young G也叫Minor GC,而Young空间的第一次GC就是找出Eden区中,幸存活着的对象,然后将这些对象,放到S0,或S1区中的其中一个, 假设第一次选择了S0,它会逐步将活着的对象拷贝到S0区域,但是如果S0区域满了,剩下活着的对象只能放old区域了,接下来要做的是,将Eden区域 清空,此时时候S1区域也是空的。
当第二次Eden区域满的时候,就将Eden区域中活着的对象+S0区域中活着的对象,迁移到S1中,如果S1放不下,就会将剩下的部门,放到Old区域中,只是这次对象来源区域增加了S0,最后会将Eden区+S0区域,清空
第三次和第四次依次类推,始终保证S0和S1有一个是空的,用来存储临时对象,用于交换空间的目的,反反复复多次没有被淘汰的对象,将会放入old区域中,默认是15次,计数器会在对象的头部记录它交换的次数。
老年代(Old)
用于存放经过多次新生代GC仍然存活的对象。新建的对象也可能直接进入老年代,比如大对象(可通过参数指定)和大的数组对象。
永恒代(Perment)
存放类的Meta信息。
Major GC
是清理老年代.
Full GC
是清理整个堆空间---包括年轻代和老年代.
发生在老年代的GC,当老年代没有足够的空间时即发生Full GC.在这里可能会发生Stop-The-World,这是java中一种全局暂停的现象,所有java代码停止,native代码可以执行,但不能和JVM交互.多半由于GC引起:Dump线程;死锁检查;堆Dump.
2.方法区:
jvm方法区(Method Area)
当jvm使用类装载器装在某个类时,它首先要定位到对应的class文件,然后读入这个class文件,最后提取该文件的内容信息,并将这些信息存储到方法去,最后返回一个class实例。
方法区是系统分配的一个内存逻辑区域,是一块所有线程共享的内存区域,用来存储类型信息(类型信息可以理解为类的描述信息(类的全限定名,访问修饰符,字段,方法等)),方法区的大小决定了系统可以包含多少个类,如果系统类太多,方法区内存不够会导致方法区溢出,虚拟机同样会抛出内存溢出信息。方法去特点:
1.方法区是线程安全的,由于所有的线程都共享方法区,所以方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入jvm,那么只允许一个线程去装在它,而其他线程必须等待。
2.方法去的大小不必是固定的,jvm可根据应用需要动态调整,同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是jvm自己的堆)中自由分配。
3.方法区也可被垃圾收集,当某个类不在被使用时,jvm将卸载这个类,进行垃圾收集。
方法区存放内容:
1.类的全限定名(类的全路径名)。
2.类的直接超类的权全限定名(如果这个类是Object,则它没有超类)。
3.类的类型(类或接口)。
4.类的访问修饰符,public,abstract,final等。
5.类的直接接口全限定名的有序列表。
6.常量池(字段,方法信息,静态变量,类型引用(class))等
HotSpot虚拟机处理方法区的时候选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。可是其他的虚拟机并不会这样处理.
有三个概念需要清楚:
-
常量池(Constant Pool):常量池数据编译期被确定,是Class文件中的一部分。存储了类、方法、接口等中的常量,当然也包括字符串常量。
-
字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存储编译期类中产生的字符串类型数据。
- 运行时常量池(Runtime Constant Pool):方法区的一部分,所有线程共享。虚拟机加载Class后把常量池中的数据放入到运行时常量池。
-
- 1.常量池在每个VM中只有一份,存放的是字符串常量的引用值。
- 2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
- 3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。
Java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。这里所说的常量包括:基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和String(也可以通过String.intern()方法可以强制将String放入常量池)
两种浮点数类型的包装类Float,Double并没有实现常量池技术
-
常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目资源关联最多的数据类型。
-
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。
-
字面量:文本字符串、声明为final的常量值等;
-
符号引用:类和接口的完全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符
JDK1.6之前字符串常量池位于方法区之中。
JDK1.7字符串常量池已经被挪到堆之中。
3.Java虚拟机栈
也叫栈区。是由一个个栈帧组成的后进先出的栈式结构,栈帧中存放方法运行时的局部变量、方法出口等信息。当调用一个方法时,就会在虚拟机栈中创建一个栈帧用于存放这些数据,方法调用完时栈帧消失。若方法中又调用了其他方法,则继续在栈顶创建新的栈.
4.本地方法栈
与虚拟机栈类似,区别是本地方法栈是为Native方法服务的,而java虚拟机栈是为java方法服务的.
5.程序计数器
最小的一块内存,通过改变计数器的值来选取下一条需要执行的字节码指令.程序计数器用来为线程独立拥有,线互补互补影响,保证线程切换后能准确恢复到执行位置,线程执行指令的跳转、循环、分支都要依赖计数器来完成.
1. 何为GC
转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/51892567
Java与C语言相比的一个优势是,可以通过自己的JVM自动分配和回收内存空间。
垃圾回收机制是由垃圾收集器Garbage Collection来实现的,GC是后台一个低优先级的守护进程。在内存中低到一定限度时才会自动运行,因此垃圾回收的时间是不确定的。
为何要这样设计:因为GC也要消耗CPU等资源,如果GC执行过于频繁会对Java的程序的执行产生较大的影响,因此实行不定期的GC。
与GC有关的是:JVM运行时数据区中的堆(对象实例会存储在这里)和 gabagecollector方法。
垃圾回收GC只能回收通过new关键字申请的内存(在堆上),但是堆上的内存并不完全是通过new申请分配的。还有一些本地方法,这些内存如果不手动释放,就会导致内存泄露,所以需要在finalize中用本地方法(nativemethod)如free操作等,再使用gc方法。
- System.gc();
2. 何为垃圾
Java中那些不可达的对象就会变成垃圾。对象之间的引用可以抽象成树形结构,通过树根(GC Roots)作为起点,从这些树根往下搜索,搜索走过的链称为引用链。
当一个对象到GC Roots没有任何引用链相连时,则证明这个对象为可回收的对象。
可以作为GC Roots的主要有以下几种:
(1)栈帧中的本地变量表所引用的对象。
(2)方法区中类静态属性和常量引用的对象。
(3)本地方法栈中JNI(Native方法)引用的对象。
JVM(3)对象A和B循环引用,最后会不会不被GC回收?
①首先说一下,GC里边在JVM当中是使用的ROOT算法,ROOT算法,什么称作为ROOT呢,就是说类的静态成员,静态成员就是static修饰的那种,是“根”的一个,根还包括方法中的成员变量,只有成员或对象不挂在根上,GC的时候就可能把他们搞掉,这里提到的循环引用,就看这个循环引用是否挂在根上,如果挂在根上,如果这个根还被JVM的Java代码所执行的话,就不会GC掉,如果说这个根已经被释放掉了,这个对象不挂在跟上了,那个这个对象就会被GC掉。
②说一下根搜索算法,ROOTS,这个算法,那些在Java里会被认为是根呢,在我印象里一般是static修饰的类成员,比如说静态字段,这种字段引用的对象被称为根,只要类在POOL区里不被卸载,一直在堆里,类对象只要没被回收掉,他引用的对象就不会被GC。
③再说另一种情况,方法中的栈,栈中有他的栈成员 Integer a = XXX,当方法没有被释放,没有出栈的时候,方法没有被弹出的时候,那Integer a 所引用的对象也是不会被回收的,在什么情况下回收呢,就是这个对象没有挂在根上,就会被回收。
④我们回到标题的问题,这个循环引用是否被回收,就看这个循环引用是否挂在根上,A引用B,B引用A,A和B并没有挂在某个内存元和根上,当他们的生命周期结束的时候,这两个对象都有可能被回收。
- //垃圾产生的情况举例:
- //1.改变对象的引用,如置为null或者指向其他对象
- Object obj1 = new Object();
- Object obj2 = new Object();
- obj1 = obj2; //obj1成为垃圾
- obj1 = obj2 = null ; //obj2成为垃圾
- //2.引用类型
- //第2句在内存不足的情况下会将String对象判定为可回收对象,第3句无论什么情况下String对象都会被判定为可回收对象
- String str = new String("hello");
- SoftReference<String> sr = new SoftReference<String>(new String("java"));
- WeakReference<String> wr = new WeakReference<String>(new String("world"));
- //3.循环每执行完一次,生成的Object对象都会成为可回收的对象
- for(int i=0;i<10;i++) {
- Object obj = new Object();
- System.out.println(obj.getClass());
- }
- //4.类嵌套
- class A{
- A a;
- }
- A x = new A();//分配了一个空间
- x.a = new A();//又分配了一个空间
- x = null;//产生两个垃圾
- //5.线程中的垃圾
- calss A implements Runnable{
- void run(){
- //....
- }
- }
- //main
- A x = new A();
- x.start();
- x=null; //线程执行完成后x对象才被认定为垃圾
3. 四种引用类型
3.1 强引用
Object obj = new Object();
这里的obj引用便是一个强引用,强引用不会被GC回收。即使抛出OutOfMemoryError错误,使程序异常终止。
3.2 软引用
如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.3 弱引用
弱引用与软引用的区别在于:垃圾回收器一旦发现了弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
3.4 虚引用
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器发现一个对象有虚引用时,首先执行所引用对象的finalize()方法,在回收内存之前,把这个虚引用对象加入到引用队列中,你可以通过判断引用队列中是否有该虚引用对象,来了解这个对象是否将要被垃圾回收。然后就可以利用虚引用机制完成对象回收前的一些工作。
这里特别需要注意:当JVM将虚引用插入到引用队列的时候,虚引用执行的对象内存还是存在的。但是PhantomReference并没有暴露API返回对象。所以如果我想做清理工作,需要继承PhantomReference类,以便访问它指向的对象。
4. 典型的垃圾回收算法
在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。
下面讨论几种常见的垃圾收集算法。
4.1 Mark-Sweep(标记-清除)算法
标记-清除算法分为两个阶段:标记阶段和清除阶段。
标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发GC。
4.2 Copying(复制)算法
Copying算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把第一块内存上的空间一次清理掉,这样就不容易出现内存碎片的问题,并且运行高效。
但是该算法导致能够使用的内存缩减到原来的一半。而且,该算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。(这也是为什么后面提到的新生代采用Copying算法)
4.2 Mark-Compact(标记-整理)算法
为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。
该算法标记阶段标记出所有需要被回收的对象,但是在完成标记之后不是直接清理可回收对象,而是将存活的对象都移向一端,然后清理掉端边界以外的所有内存(只留下存活对象)。
4.4 Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。
它的核心思想是将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以在不同代的采取不同的最适合的收集算法。
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,该算法效率在新生代也较高。但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间(比例8:1:1),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和A空间。在进行了第一次GC之后,使用的便是Eden space和B空间了,下次GC时会将存活对象复制到A空间,如此反复循环。
当对象在Survivor区躲过一次GC的话,其对象年龄便会加1,默认情况下,对象年龄达到15时,就会移动到老年代中。一般来说,大对象会被直接分配到老年代,所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组,比如:byte[] data = newbyte[4*1024*1024]。
当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和JVM的相关参数。这些搬运工作都是GC完成的,GC不仅负责在Heap中搬运实例,同时负责回收存储空间。
最后,因为每次回收都只回收少量对象,所以老年代一般使用的是标记整理算法。
方法区也是会被回收的
!但是方法区的回收条件非常苛刻,只有同时满足以下三个条件才会被回收!
1、所有实例被回收
2、加载该类的ClassLoader被回收
3、Class对象无法通过任何途径访问(包括反射)
注意,在方法区中有一个永久代(Permanet Generation),它用来存储class文件、静态对象、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
Minor GC是新生代Copying算法。MinorGC触发条件:
(1)当Eden区满时,触发Minor GC。
Full GC的老年代,采取的Mark-Compact。Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行。
(2)老年代空间不足。
(3)方法区空间不足。
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存。
有关查看垃圾回收信息的JVM常见配置方式:
- -XX:+PrintGCDetails
最后介绍一下有关堆的JVM常见配置方式:
- -Xss //选置栈内存的大小
- -Xms: //初始堆大小
- -Xmx: //最大堆大小
- -XX:NewSize=n: //设置年轻代大小
- -XX:NewRatio=n: //设置年轻代和年老代的比值。比如设置为3,表示年轻代与年老代比值为1:3
- -XX:SurvivorRatio=n: //年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。比如设置为3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5。
- -XX:MaxPermSize=n: //设置持久代大小
5. 典型的垃圾回收器
目前的收集器主要有三种:串行收集器、并行收集器、并发收集器 。
- 串行收集器
使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量(100M 左右)情况下的多处理器机器上。可以使用-XX:+UseSerialGC 打开。 - 并行收集器
- 对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用-XX:+UseParallelGC .打开。并行收集器在J2SE5.0第六6更新上引入,在Java SE6.0中进行了增强--可以堆年老代进行并行收集。如果年老代不使用并发收集的话,是使用单线程进行垃圾回收 ,因此会制约扩展能力。使用-XX:+UseParallelOldGC 打开。
- 使用-XX:ParallelGCThreads=<N> 设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等 。
- 此收集器可以进行如下配置:
- 最大垃圾回收暂停: 指定垃圾回收时的最长暂停时间,通过-XX:MaxGCPauseMillis=<N> 指定。<N>为毫秒.如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值 。设定此值可能会减少应用的吞吐量。
- 吞吐量: 吞吐量为垃圾回收时间与非垃圾回收时间的比值 ,通过-XX:GCTimeRatio=<N> 来设定,公式为1/(1+N) 。例如,-XX:GCTimeRatio=19时,表示5%的时间用于垃圾回收。默认情况为99,即1%的时间用于垃圾回收。
- 并发收集器
可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC 打开。- 并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。在每个年老代垃圾回收周期 中,在收集初期并发收集器会对整个应用进行简短的暂停,在收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程中多个线程同时进行垃圾回收工作。
- 并发收集器使用处理器换来短暂的停顿时间 。在一个N个处理器的系统上,并发收集部分使用K/N 个可用处理器进行回收,一般情况下1<=K<=N/4 。
- 在只有一个处理器的主机上使用并发收集器 ,设置为incremental mode 模式也可获得较短的停顿时间。
- 浮动垃圾 :由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“Floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要20% 的预留空间用于这些浮动垃圾。
- Concurrent Mode Failure :并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生“并发模式失败”,此时整个应用将会暂停,进行垃圾回收。
- 启动并发收集器 :因为并发收集在应用运行时进行收集,所以必须保证收集完成之前有足够的内存空间供程序使用,否则会出现“Concurrent Mode Failure”。通过设置-XX:CMSInitiatingOccupancyFraction=<N> 指定还有多少剩余堆时开始执行并发收集
- 小结
- 串行处理器:
--适用情况:数据量比较小(100M左右);单处理器下并且对响应时间无要求的应用。
--缺点:只能用于小型应用 - 并行处理器:
--适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。
--缺点:应用响应时间可能较长 - 并发处理器:
--适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境。
- 串行处理器:
在介绍各种收集器之前,先确认一下并行和并发的概念:(下面是指垃圾收集器的语境中)
l 并行(Parallel):多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
l 并发(Concurrent):用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替进行的),用户程序继续运行,而垃圾收集程序运行于另一个CPU上。
1.Serial收集器(新生代 – 串行GC)
最基本、最悠久的收集器,他是一个单线程的收集器,而单线程是指当他在进行垃圾收集的时候,必须暂停其他所有的工作线程,被称为”Stop The World”。
优点:简单而高效(与其他收集器的单线程相比),在限于单个CPU的环境,没有额外的线程交互的开销,所以能获得最高效率。
适用:运行在Client模式下的虚拟机。
2.ParNew收集器(新生代 – 并行GC)
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余的和Serial所有控制参数一样。在配置为CMS GC的情况下默认的新生代收集器,可以用-XX:+UseParNewGC强制指定。
优点:在多CPU的环境下可以发挥更高而效率,并且是唯一一个可以和CMS收集器搭配工作的新生代并行GC。
适用:运行在server模式下的虚拟机首选的新生代收集器。
3.Parallel Scavenge收集器(新生代 – 并行回收GC)
看上去和ParNew没什么区别,但是Parallel Scavenge最大的特点就是他的关注点在于CPU的吞吐量,吞吐量=运行代码时间/(运行代码时间+垃圾收集时间)。较高的吞吐量可以最好的利用CPU的效率。-XX:MaxGCPauseMillis配置最大垃圾收集停顿时间,-XX:GCTimeRatio配置吞吐量大小。
优点:被称为”吞吐量优先”收集器,有一个自适应调节参数(-XX:+UseAdaptiveSizePolicy),当这个参数打开后,无需手动指定新生代大小(-Xmn)、Eden和Survivor比例(-XX:SurvivorRatio)、晋升老年代年龄限制(-XX:PretenureSizeThreshold)等细节参数,虚拟机会动态调节这些参数来提供最适合的停顿时间或最大吞吐量。
适用:本身是Server级别多CPU机器上的默认GC方式,也可以通过-XX:+UseParallelGC来指定,并且可以采用-XX:ParallelGCThread来指定线程数。
4.Serial Old收集器(老年代 – 串行GC)
Serial Old是Serial收集器的年老代版本,同样是一个单线程收集器,使用”标记-整理”算法。
适用:Client模式下虚拟机使用;在Server模式有两大用途:与Parallel Scavenge收集器搭配使用,作为CMS收集器的后备预案。
5.Parallel Old收集器(老年代 – 并行GC)
Parallel Old是Parallel Scavenge收集器的老年代版本,为了配合Parallel Scavenge的面向吞吐量的特性而开发的对应组合。
适用:在注重吞吐量以及CPU资源敏感的场合采用。
6.CMS收集器(老年代 – 并发GC)
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。基于”标记-清除”算法,整个过程分为4个步骤:初始标记(CMS initial mark)、并发标记(CMS concurrent mark)、重新标记(CMS remark)和并发清除(CMS concurrent sweep)。
其中初始标记、重新标记仍然是”Stop The World”,初始标记仅仅是标记一下GC Roots能直接关联的对象,并发标记进行GC Roots Tracing的过程,重新标记为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那部分对象,这个阶段停顿时间比初始标记阶段稍长一些,但比并发标记时间短。
优点:并发收集、低停顿。
适用:重视服务的响应速度、系统停顿时间和用户体验的互联网网站或者B/S系统。
7.G1收集器
G1(Garbage First)收集器是当前收集器技术最前沿成果,与之前的CMS相比有两个显著改进:基于”标记-整理”算法实现收集器和精确控制停顿。能够在基本不牺牲吞吐量的前提下完成低停顿的内存回收。
附:
默认GC组合:
可选的GC组合: