随便理解一下JVM

来源:互联网 发布:淘宝内衣买家秀店铺 编辑:程序博客网 时间:2024/05/29 12:20

什么是JVM

JVM即Java Virtual Machine,意为Java虚拟机
一种能够运行Java bytecode(字节码)的虚拟机,是Java平台的一部分,能够运行以Java语言写作的软件程序。

为什么叫做虚拟机?

在C/C++中,使用编译器编译,生成可执行程序 or lib库,操作系统进行调度,分配内存,交给CPU去执行

JVM有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统
JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。通过对CPU所执行的软件实现,实现能执行编译过的Java程序码
JVM的特性使得其之上的高级语言具有跨平台的特性,即”一次编写,处处执行”

JVM是多语言平台

JVM并不是Java专属,而是一个平台,只要提供把代码转换成符合当前JVM规范的,可识别的字节码,都可以在JVM上跑
除了Java之外,有很多语言都可以在JVM上试运行
以下为原生就在JVM上实现运行的语言:
BBj
BeanShell
Ceylon
Clojure
Fantom
Kotlin
Groovy
MIDletPascal
Scala
Xtend
以下为实现了相应的JVM编译器的语言及其编译器:
Erlang:Erjang
JavaScript:Rhino
Pascal:Free Pascal
PHP:Quercus
Python:Jython
REXX:NetRexx
Ruby:JRuby
Scheme:Kawa
Tcl:Jacl
其实这些语言我大多都没听说过

HotSpot

HotSpot的正式发布名称为”Java HotSpot Performance Engine”,是Java虚拟机的一个实现,从Java1.3.1版本开始使用,包含了服务器版和桌面应用程序版,现时由Oracle维护并发布。它利用JIT及自适应优化技术(自动查找性能热点并进行动态优化,这也是HotSpot名字的由来)来提高性能

Hotspot JVM有两种类型,分别是server和client。它们的区别是Server VM的初始堆空间会大一些,默认使用的时并行垃圾回收器。Client VM相对来讲会保守一些,初始堆空间会小一些,使用串行的垃圾回收器,它的目标是为了让JVM的启动速度更快。
JVM在启动的时候会根据硬件和操作系统会自动选择使用Server还是Client类型的JVM
在32位Windows系统上,不论硬件配置如何,都默认使用Client类型的JVM
在其他32位操作系统上,如果机器配置有2GB集群以上的内存同时有2个以上的CPU,则默认会使用Server类型的JVM
64位机器上只有Server类型的JVM。也就是说Client类型的JVM只在32位机器上提供
你也可以使用-server和-client选项来指定JVM的类型,不过只在32位的机器上有效

命令行查看Java版本
上图为win10 64bit环境下的java -version命令

Hotspot JVM提供以下三大类选项:
1.标准选项:这类选项的功能是很稳定的,在后续版本中也不太会发生变化。运行java或者java -help可以看到所有的标准选项。所有的标准选项都是以-开头,比如-version, -server等。
这里写图片描述

2.X选项:比如-Xms。这类选项都是以-X开头,可能由于这个原因它们被称为X选项。运行java -X命令可以看到所有的X选项。这类选项的功能还是很稳定,但官方的说法是它们的行为可能会在后续版本中改变,也有可能不在后续版本中提供了。
这里写图片描述

3.XX选项:这类选项是属于实验性,主要是给JVM开发者用于开发和调试JVM的,在后续的版本中行为有可能会变化。

JVM内存模型和GC

内存模型

Java无法像C/C++一样实际的操作内存,只可以操作JVM中的内存,这也是虚拟机的特性之一
通常理解内存只区别了堆内存和栈内存,JVM提供的内存模型远不止这两个区域

下图简单介绍了Java的内存模型:
Java内存模型

使用-Xmx命令设置JVM可使用的最大内存:-Xmx4096m

STACK(栈)
栈是一种先进后出的数据结构,用于记录方法调用再适合不过了,这一部分通常空间占得比较小
栈包括两个部分,一个是虚拟机栈,一个是本地方法栈
这两个模型都可以顾名思义,虚拟机栈,即记录java代码的方法调用,本地方法栈,即记录本地方法的调用
在虚拟机栈中,对于每个单元,被称为栈帧
对于每个栈帧,包含如下内容:
1.局部变量表
2.操作数栈
3.动态连接
4.方法返回地址
5.附加信息
http://blog.csdn.net/xtayfjpk/article/details/41924283

PC(程序计数器)
只有一个字节,该空间只是JVM模型上规定的空间,实际上会被优化到别的空间地方,取决于虚拟机的实现
简单的来说,PC记录的是当前执行的字节码的地址,或时要返回的地址
如果正在执行一个native方法,那么此时PC寄存器的值为”undefined”

STACK和PC都是线程私有的,每个线程的创建,都会创建自己线程独享的Stack和PC,并且相互独立
使用-Xss可以来设定每个线程的栈大小,Java5以后默认是1m

METHOD AREA(方法区)
方法区,只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。
在Hotspot中,方法区由堆内存实现,称为堆内存的一部分,称为PermGen(永久带)
使用XX:PermSize=128m可以指定方法区的初始空间大小,XX:MaxPermSize可以永久带的最大空间大小

最新的Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——Metaspace(元空间)
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

Heap(堆)
堆也是一种常见的数据结构,常说的堆其实就是二叉堆,二叉堆是一个完全二叉树,所谓堆内存就是使用堆这种数据结构管理的内存.
所有的对象,数组,都在堆内存中分配
使用Xmx 堆空间最大空间大小
Xms 堆空间初始空间大小
xmn 来设定堆新生代的大小(稍后讲解)

OOM & SOF
顺便提一下Java的异常处理机制
大家都知道Java的异常是Exception,Exception还有一个父类,叫Throwable,顾名思义,可被抛出的
直接继承Throwable的,除了Exception,还有一个类叫Error
在Java内存模型中,有两个内存溢出的错误,它们不是异常,在JVM内存已满,无法继续分配内存的情况下会抛出,一个是OutOfMemoryError(简称OOM),一个是StackOverflowError(简称SOF)

OutOfMemoryError:为堆内存溢出,当堆内存空间不足时会抛出该错误,在介绍方法区的时候提到过,Java1.7以前,HotSpot虚拟机的方法区是放在堆内存中实现的,被称为PermGen(永久带),所以当方法区内存空间不足的时候,会抛出OutOfMemoryError:PermGen .在Java1.8之后,HostSpot移除了永久带,加入了Metaspace,Metaspace默认大小是没有限制的,当然如果对其进行大小限制,并且发生了空间不足的情况,这时也会抛出OutOfMemoryError:Metaspace

以下代码可以实现堆内存溢出:

public class OutOfMemoryTest {    public static void main(String[] args) {        List<byte[]> list = new LinkedList<>();        int count = 0;        for (; ; ) {            list.add(new byte[1048 * 1024]);            System.out.println("count:" + count++);        }    }}

可以看到,用一个LinkedList,无限的存入大小为1M的byte[]数组
结果如图:
这里写图片描述

StackOverflowError:意为栈溢出,有一段简单的代码可以实现虚拟机栈溢出

public class StackOverFlowTest {    public static int stackOverFlow(int count) {        System.out.println(count++);        return stackOverFlow(count);    }    public static void main(String[] args) {        stackOverFlow(0);    }}

该代码使用递归无限的执行,栈内存会不停地压入栈帧,直到栈内存溢出

这里写图片描述

GC(垃圾收集器)

什么是GC

在C/C++中,开发者需要关心内存的分配和释放,只分配不释放会造成内存泄露,有可能让整个程序崩溃
而对于内存主动释放无疑是一件繁琐复杂且容易出问题的地方,对此,Java提供了自动的垃圾回收机制,称为:Garbage Collection

垃圾判定算法

引用计数(reference counting)
此对象有一个引用,则+1,删除一个引用,则-1,只用收集计数为0的对象。
缺点:直接上代码:

public class Object {    Object field = null;    public static void main(String[] args) {        Thread thread = new Thread(new Runnable() {            public void run() {                Object objectA = new Object();                Object objectB = new Object();                objectA.field = objectB;                objectB.field = objectA;                //to do something                objectA = null;                objectB = null;            }        });        thread.start();        while (true);    }}

这种情况下,objectA new的时候,reference+1,此时计数为1,objectB.field = objectA的时候,objectA 的引用增加,reference+1,此时计数为2,objectB同理;接下来释放了objectA,objectB,各自reference - 1,此时计数为1,用此种算法时GC并不会去释放

可达性分析算法
这个算法的基本思路就是通过一系列的称谓GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径为引用链,当一个对象到GC Roots没有任何引用链项链时,则证明此对象时不可用的,下面看一下例子:
这里写图片描述
对象object5、object6、object7虽然互相没有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象
理解此种算法,主要是理解GC root,以下几种对象可作为GC root
1.虚拟机栈(栈帧中的本地变量表)中引用的对象;
2.方法区中类静态属性引用的对象;
3.方法区中常量引用的对象;
4.本地方法栈中JNI(即一般说的Native方法)引用的对象;

垃圾收集算法

标记-清除算法
分标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
缺点:1.效率问题,标记和清除两个过程的效率都不高;2.空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多后导致以后程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前出发一次垃圾收集动作

标记-整理算法
标记可达的对象,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
优点:内存连续
缺点:非常的慢
标记整理

复制算法
把内存空间划分为2个相等的区域,每次只使用一个区域。垃圾回收时,遍历当前使用区域,把正在使用的对象复制到另外一个区域
复制算法
优点:每次对整个半区进行回收,内存分配时不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效;
不足:提高效率的代价是将内存缩小到原来的一半,执行GC的时候Stop the world

分代算法
把堆分成几个部分,不同的部分用不同的GC算法
这是目前广泛使用的方法
HotSpot虚拟机,将堆分成了Young(新生代)和Old(老年代)
其中Young又被分成三个区域,EdenSurvive0Survive1(简称S0,S1)
这里写图片描述

不同代的内存分配与GC策略:
1.新的对象都会在Eden区域分配空间,除非这个对象非常大,那么直接进入Old。当Eden区满,进行GC,没有被GC释放的内存将进入Survive区,超过Survive区大小的对象会进入Old区

2.在不断创建对象的过程中,Eden区会满,而Young空间的第一次GC就是找出Eden区中,幸存活着的对象,然后将这些对象,放到S0,或S1区中的其中一个, 假设第一次选择了S0,它会逐步将活着的对象拷贝到S0区域,但是如果S0区域满了,剩下活着的对象只能放old区域了,接下来要做的是,将Eden区域清空,此时S1区域也是空的。
当第二次Eden区域满的时候,就将Eden区域中活着的对象+S0区域中活着的对象,迁移到S1中,如果S1放不下,就会将剩下的部门,放到Old区域中,只是这次对象来源区域增加了S0,最后会将Eden区+S0区域,清空
第三次和第四次依次类推,始终保证S0和S1有一个是空的,用来存储临时对象,用于交换空间的目的,反反复复多次没有被淘汰的对象,将会放入old区域中,默认是15次。具体的交换过程就和上图中的信息相似。

Young GC和 Full GC
1.发生在Young的GC被称为Young GC,或是Minor GC,Hotspot采用复制算法来回收新生代
但新生代中的对象一般98%是朝生夕死,无需按照1:1比例来划分内存空间,而是将内存分为1块较大的Eden(伊甸园)空间和2块较小的Survivor(幸存者)空间,每次使用Eden和其中1块Survivor。
回收时,将Eden和Survivor中还存活的对象一次性复制到另外一个Survivor空间中,最后清理掉Eden和刚才用过的Survivor空间。
HotSpot VM默认Eden和Survivor的比例是8:1:1,即只浪费10%的内存。
98%的对象可回收只是一般场景下的数据,无法保证每次回收都只有不多于10%的对象存活,所以当Survivor空间不足时,需要依赖其他内存(老年代)进行分配担保,让对象进入老年代。

2.发生在Old的GC被称为Full GC,或是Major GC,Hotspot采用标记整理算法来回收老年代
为什么不用速度快的复制算法回收老年代?
虽然复制算法效率较高,但是在对象存活率较高时复制操作较多,效率会变低。更关键的是,如果不想浪费一倍的空间,就需要有额外的空间进行分配担保,以对应被使用内存中的所有对象全部存活的极端情况,所以老年代一般不直接选用这种算法。
根据老年代的特点:对象存活率高、没有额外空间对其进行分配担保,需要内存连续以便分配大对象,采用标记-整理算法来进行回收。

垃圾收集器

以上介绍的都只是算法,接下来介绍几种已经实现的垃圾收集器:

新生代收集器:

1.Serial收集器
Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的唯一选择。
这个收集器是一个单线程的收集器,是使用复制算法的收集器,但它的单线程的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
使用-XX:+UseSerialGC 来指定使用Serial收集器
应用场景:
Serial收集器是虚拟机运行在Client模式下的默认新生代收集器。
优点:仔细想想,简单粗暴的东西就是适合简单粗暴,假如我就限定这个Java程序只能单核心CPU跑,此时用Serial收集器是个非常好的选择

2.ParNew收集器
其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。
使用-XX:+UseParNewGC来指定使用Serial收集器
-XX:ParallelGCThreads 限制线程数量
应用场景: ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。

3.Parallel Scavenge收集器
它也是使用复制算法的收集器,又是并行的多线程收集器
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用
应用场景:CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
GC自适应的调节策略:
Parallel Scavenge收集器有一个参数-XX:+UseAdaptiveSizePolicy。当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器。

老年代收集器:
Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法

应用场景:
Client模式
Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用。
Server模式
如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程标记-整理算法

应用场景:
在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。

这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器外别无选择(Parallel Scavenge收集器无法与CMS收集器配合工作)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合。

CMS(ConcMarkSweep)收集器
老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记-清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。
使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,
优先使用ParNew+CMS,当用户线程内存不足时,采用备用方案Serial Old收集。

CMS收集的执行过程:
(1) 初始标记 (Initial Mark) (Stop the World Event,所有应用线程暂停) 在老年代中的对象, 如果从新生代中能访问到, 则被 “标记,marked” 为可达的(reachable).对象在旧一代“标志”可以包括这些对象可能可以从新生一代。暂停时间一般持续时间较短,相对小的收集暂停时间.
(2) 并发标记 (Concurrent Marking) 在Java应用程序线程运行的同时遍历老年代的可达对象图。扫描从被标记的对象开始,直到遍历完从root可达的所有对象. 调整器(mutators)在并发阶段的2、3、5阶段执行,在这些阶段中新分配的所有对象(包括被提升的对象)都立刻标记为存活状态.
(3) 再次标记(Remark) (Stop the World Event, 所有应用线程暂停) 查找在并发标记阶段漏过的对象,这些对象是在并发收集器完成对象跟踪之后由应用线程更新的.
(4) 并发清理(Concurrent Sweep) 回收在标记阶段(marking phases)确定为不可及的对象. 死对象的回收将此对象占用的空间增加到一个空闲列表(free list),供以后的分配使用。死对象的合并可能在此时发生. 请注意,存活的对象并没有被移动.
(5) 重置(Resetting) 清理数据结构,为下一个并发收集做准备.

在CMS清理过程中,只有初始标记和重新标记需要短暂停顿,并发标记和并发清除都不需要暂停用户线程,因此效率很高,很适合高交互的场合。
CMS也有缺点,它需要消耗额外的CPU和内存资源,在CPU和内存资源紧张,CPU较少时,会加重系统负担(CMS默认启动线程数为(CPU数量+3)/4)。

在并发收集过程中,用户线程仍然在运行,仍然产生内存垃圾,所以可能产生“浮动垃圾”,本次无法清理,只能下一次Full GC才清理,因此在GC期间,需要预留足够的内存给用户线程使用。
使用CMS的收集器并不是老年代满了才触发Full GC,而是在使用了一大半(默认68%,即2/3,使用-XX:CMSInitiatingOccupancyFraction来设置)的时候就要进行Full GC,如果用户线程消耗内存不是特别大,可以适当调高-XX:CMSInitiatingOccupancyFraction以降低GC次数,提高性能,如果预留的用户线程内存不够,则会触发Concurrent Mode Failure,此时,将触发备用方案:使用Serial Old 收集器进行收集,但这样停顿时间就长了,因此-XX:CMSInitiatingOccupancyFraction不宜设的过大。
CMS采用的是标记清除算法,会导致内存碎片的产生,可以使用-XX:+UseCMSCompactAtFullCollection来设置是否在Full GC之后进行碎片整理,用-XX:CMSFullGCsBeforeCompaction来设置在执行多少次不压缩的Full GC之后,来一次带压缩的Full GC。

G1收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,通过-XX:+UseG1GC参数来启用,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征. ,G1收集器是Java1.7的新特性

首先,使用G1收集器,堆内存划分都会产生变化:
G1会讲堆划分为固定大小的多个区域,依然存在eden,S0,S1,old这些概念,不同的是是采用逻辑区分,而不是物理区分.每个heap区(Region)的大小在JVM启动时就确定了. JVM 通常生成 2000 个左右的heap区, 根据堆内存的总大小,一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,范围允许为 1Mb 到 32Mb,且是2的指数倍

这里写图片描述

在上图中,我们注意到还有一些Region标明了H,它代表Humongous,这表示这些Region存储的是巨大对象(humongous object,H-obj),即大小大于等于region一半的对象。H-obj有如下几个特征:
1.H-obj直接分配到了old gen,防止了反复拷贝移动。
2.H-obj在global concurrent marking阶段的cleanup 和 full GC阶段回收。
3.在分配H-obj之前先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 full GC。
为了减少连续H-objs分配对GC的影响,需要把大对象变为普通的对象,建议增大Region size。

Pause Prediction Model
Pause Prediction Model 即停顿预测模型。它在G1中的作用是:
G1 GC是一个响应时间优先的GC算法,它与CMS最大的不同是,用户可以设定整个GC过程的期望停顿时间,参数-XX:MaxGCPauseMillis指定一个G1收集过程目标停顿时间,默认值200ms,不过它不是硬性条件,只是期望值。那么G1怎么满足用户的期望呢?就需要这个停顿预测模型了。G1根据这个模型统计计算出来的历史数据来预测本次收集需要选择的Region数量,从而尽量满足用户设定的目标停顿时间。
停顿预测模型是以衰减标准偏差为理论基础实现的.

G1GC过程:
G1提供了两种GC模式,Young GC和Mixed GC,两种都是完全Stop The World的。
Young GC:选定所有新生代里的Region。通过控制新生代的region个数,即新生代内存大小,来控制young GC的时间开销。

Mixed GC:选定所有新生代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
由上面的描述可知,Mixed GC不是full GC,它只能回收部分老年代的Region,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的。

上文中,多次提到了global concurrent marking,它的执行过程类似CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为四个步骤:

初始标记(initial mark,STW)。它标记了从GC Root开始直接可达的对象。
并发标记(Concurrent Marking)。这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且收集各个Region的存活对象信息。
最终标记(Remark,STW)。标记那些在并发标记阶段发生变化的对象,将被回收。
清除垃圾(Cleanup)。清除空Region(没有存活对象的),加入到free list。
第一阶段initial mark是共用了Young GC的暂停,这是因为他们可以复用root scan操作,所以可以说global concurrent marking是伴随Young GC而发生的。第四阶段Cleanup只是回收了没有存活对象的Region,所以它并不需要STW。

Young GC发生的时机大家都知道,那什么时候发生Mixed GC呢?其实是由一些参数控制着的,另外也控制着哪些老年代Region会被选入CSet。

G1HeapWastePercent:在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
G1MixedGCLiveThresholdPercent:old generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet。
G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数。
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old generation region数量。

G1GC的参数及含义
-XX:G1HeapRegionSize=n 设置Region大小,并非最终值
-XX:MaxGCPauseMillis 设置G1收集过程目标时间,默认值200ms,不是硬性条件
-XX:G1NewSizePercent 新生代最小值,默认值5%
-XX:G1MaxNewSizePercent 新生代最大值,默认值60%
-XX:ParallelGCThreads STW期间,并行GC线程数
-XX:ConcGCThreads=n 并发标记阶段,并行执行的线程数
-XX:InitiatingHeapOccupancyPercent 设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous

G1的长期目标是取代CMS(Concurrent Mark-Sweep Collector, 并发标记-清除). 因为特性的不同使G1成为比CMS更好的解决方案. 一个区别是,G1是一款压缩型的收集器.G1通过有效的压缩完全避免了对细微空闲内存空间的分配,不用依赖于regions,这不仅大大简化了收集器,而且还消除了潜在的内存碎片问题。除压缩以外,G1的垃圾收集停顿也比CMS容易估计,也允许用户自定义所希望的停顿参数(pause targets)

原创粉丝点击