APP下载

经典技术干货分享:JVM内存模型和垃圾回收机制

消息来源:baojiabao.com 作者: 发布时间:2024-05-21

报价宝综合消息经典技术干货分享:JVM内存模型和垃圾回收机制

来源:全栈工程师小辉

JVM内存模型

根据Java虚拟机器规范,Java资料区域分为五大资料区域。

其中方法区和堆是所有执行绪共享的,虚拟机器栈、本地方法栈和程式计数器则为执行绪私有的。

有的部落格称方法区是永久代,那是因为前者是JVM的规范,而后者则是JVM规范的一种实现,并且只有HotSpot才有永久代,

JDK8中已经彻底移除了方法区,JDK8中引入了一个新的内存区域叫metaspace(元空间),后边详细介绍。

栈区

栈分为虚拟机器栈和本地方法栈。

虚拟机器栈

每个方法执行都会建立一个栈帧,用于存放区域性变量表,操作栈,动态连结,方法出口等。 每个方法从被呼叫,直到被执行完。 对应着一个栈帧在虚拟机器中从入栈到出栈的过程。

通常说的栈就是指区域性变量表部分,存放编译期间可知的8种基本资料型别及物件引用和指令地址。

区域性变量表是在编译期间完成分配,当进入一个方法时,这个栈中的区域性变数分配内存大小是确定的。

常见的的两种异常StackOverFlowError和OutOfMemoneyError。 当执行绪请求栈深度大于虚拟机器所允许的深度就会丢掷StackOverFlowError错误; 虚拟机器栈动态扩充套件,当扩充套件无法申请到足够的内存空间时候,丢掷OutOfMemoneyError。

本地方法栈

本地方法栈与虚拟机器栈的作用十分类似,不过本地方法是为native方法服务的。 部分虚拟机器(比如Sun HotSpot虚拟机器)直接将本地方法栈与虚拟机器栈合二为一。 与虚拟机器栈一样,本地方法栈也会丢掷StactOverflowError与OutOfMemoryError异常。

程式计数器

当前执行绪所执行的行号指示器。 通过改变计数器的值来确定下一条指令,比如循环,分支,跳转,异常处理,执行绪恢复等都是依赖计数器来完成。

Java虚拟机器多执行绪是通过执行绪轮流切换并分配处理器执行时间的方式实现的。 为了执行绪切换能恢复到正确的位置,每条执行绪都需要一个独立的程式计数器,所以它是执行绪私有的。

唯一一块Java虚拟机器没有规定任何OutofMemoryError的区块。

方法区

方法区/永久代是被所有执行绪共享区域,用于存放已被虚拟机器载入的类资讯、常量、静态变数等资料。 永久代的垃圾回收和老年代的垃圾回收是系结的,一旦其中一个区域被占满,这两个区都要进行垃圾回收。

在JDK1.7之前执行时常量池逻辑包含字串常量池存放在方法区,此时hotspot虚拟机器对方法区的实现为永久代

在JDK1.7字串常量池被从方法区拿到了堆中,这里没有提到执行时常量池,也就是说字串常量池被单独拿到堆,执行时常量池剩下的东西还在方法区,也就是hotspot中的永久代

在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之,这时候字串常量池还在堆, 执行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间(Metaspace)

移除永久代的影响

永久代在JDK8中被删除,被一个叫做元空间的区域所替代了。 这项改动是很有必要的,因为对永久代进行调优是很困难的。 永久代中的元资料可能会随着每一次Full GC发生而进行移动。 并且为永久代设定空间大小也是很难确定的,因为这其中有很多影响因素,比如类的总数,常量池的大小和方法数量等。

预设情况下,元空间的最大可分配空间就是系统可用内存空间。 因此,我们就不会遇到永久代存在时的内存溢位错误,也不会出现泄漏的资料移到交换区这样的事情。 终端使用者可以为元空间设定一个可用空间最大值,如果不进行设定,JVM会自动根据类的元资料大小动态增加元空间的容量。

注意: 永久代的移除并不代表自定义的类载入器泄露问题就解决了。 还必须监控内存消耗情况,因为一旦发生泄漏,会占用大量的本地内存,

堆区

堆被所有执行绪共享区域,在虚拟机器启动时建立,唯一目的是存放物件例项。

堆区是垃圾回收的主要区域,通常情况下分为两个区块年轻代和老年代。 年轻代又分为Eden区(存放新建立物件),From survivor区和To survivor区(两个survivor区储存gc后幸存下的物件)。 预设情况下各自占比 8:1:1。

java虚拟机器规范对这块的描述是: 所有物件例项及阵列都要在堆上分配内存,但随着逃逸分析技术的成熟,这个说法也不是那么绝对,但是大多数情况都是这样的。

逃逸分析: 通过逃逸分析来决定某些例项或者变数是否要在堆中进行分配,如果开启了逃逸分析,即可将这些变数直接在栈上进行分配,而非堆上进行分配。 这些变数的指标可以被全域性所引用,或者被其它执行绪所引用。

在JVM执行时,可以通过配置以下引数改变整个JVM堆的配置比例

JVM执行时堆的大小-Xms,堆的最小值-Xmx,堆空间的最大值新生代堆空间大小调整-XX:NewSize新生代的最小值-XX:MaxNewSize,新生代的最大值-XX:NewRatio,设定新生代与老年代在堆空间的大小-XX:SurvivorRatio,新生代中Eden所占区域的大小永久代大小调整-XX:MaxPermSize 4.其他-XX:MaxTenuringThreshold,设定将新生代物件转到老年代时需要经过多少次垃圾回收,但是仍然没有被回收OutOfMemoryError报错及解决方法

java.lang.OutOfMemoryError:java heap space这种是java堆内存不够,一个原因是内存真不够,另一个原因是程式中有死循环。如果是java堆内存不够的话,可以通过调整JVM下面的配置来解决: -Xms 、 -Xmxjava.lang.OutOfMemoryError:GC overhead limit exceeded这是JDK6新增错误型别,当GC为释放很小空间占用大量时间时丢掷;一般是因为堆太小,导致异常的原因,没有足够的内存。解决方案:检视系统是否有使用大内存的程式码或死循环;通过新增JVM配置,来限制使用内存:-XX:-UseGCOverheadLimitjava.lang.OutOfMemoryError: PermGen space这一部分用于存放Class和Meta的资讯,Class在被Load的时候被放入PermGen space区域。所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。这种是永久代内存不够,可通过调整JVM的配置: -XX:MaxPermSize 、 -XXermSizejava.lang.OutOfMemoryError: Direct buffer memory可能原因是本身资源不够或者申请的太多内存。如果不是内存泄漏的话,可以使用引数 -XX:MaxDirectMemorySize 引数,或者 -XX:MaxDirectMemorySizejava.lang.OutOfMemoryError: unable to create new native thread可能原因是系统内存耗尽,无法为新执行绪分配内存或者建立执行绪数超过了操作系统的限制。通过两个途径解决:排查应用是否建立了过多的执行绪。通过jstack确定应用建立了多少执行绪调整操作系统执行绪数阈值。操作系统会限制程序允许建立的执行绪数,使用ulimit -u命令检视限制。某些服务器上此阈值设定的过小,比如1024。一旦应用建立超过1024个执行绪,就会遇到java.lang.OutOfMemoryError: unable to create new native thread问题。如果是这种情况,可以调大操作系统执行绪数阈值。增加机器内存。如果上述两项未能排除问题,可能是正常增长的业务确实需要更多内存来建立更多执行绪。如果是这种情况,增加机器内存。减小堆内存。一个老司机也经常忽略的非常重要的知识点:执行绪不在堆内存上建立,执行绪在堆内存之外的内存上建立。所以如果分配了堆内存之后只剩下很少的可用内存,依然可能遇到java.lang.OutOfMemoryError: unable to create new native thread。考虑如下场景:系统总内存6G,堆内存分配了5G,永久代512M。在这种情况下,JVM占用了5.5G内存,系统程序、其他使用者程序和执行绪将共用剩下的0.5G内存,很有可能没有足够的可用内存建立新的执行绪。如果是这种情况,考虑减小堆内存。减小执行绪栈大小。执行绪会占用内存,如果每个执行绪都占用更多内存,整体上将消耗更多的内存。每个执行绪预设占用内存大小取决于JVM实现。可以利用-Xss引数限制执行绪内存大小,降低总内存消耗。例如,JVM预设每个执行绪占用1M内存,应用有500个执行绪,那么将消耗500M内存空间。如果实际上256K内存足够执行绪正常执行,配置-Xss256k,那么500个执行绪将只需要消耗125M内存。(注意,如果-Xss设定的过低,将会产生java.lang.StackOverflowError错误)。java.lang.StackOverflowError这也内存溢位错误的一种,即执行绪栈的溢位,要么是方法呼叫层次过多(比如存在无限递回呼叫),要么是执行绪栈太小。可以通过优化程式设计,减少方法呼叫层次;调整 -Xss 引数增加执行绪栈大小。垃圾回收算法

新生代采用复制算法。 老年代采用标记/清除算法或标记/整理算法。 由于老年代存活率高,没有额外空间给他做担保,必须使用这两种算法。

标记-清除(Mark Sweep)算法

算法分为2个阶段:

标记处需要回收的物件回收被标记的物件标记算法分为两种:

引用计数算法(Reference Counting)可达性分析算法(Reachability Analysis)由于引用技术算法无法解决循环引用的问题,所以这里使用的标记算法均为可达性分析算法。下文将介绍两种标记算法。

如图所示,当进行过标记清除算法之后,出现了大量的非连续内存。 当java堆需要分配一段连续的内存给一个新物件时,发现虽然内存清理出了很多的空闲,但是仍然需要继续清理以满足“连续空间”的要求。 所以说,这种方法比较基础,效率也比较低下。

复制(Copying)算法

为了解决效率与内存碎片问题,复制(Copying)算法出现了,它将内存划分为两块相等的大小,每次使用一块,当这一块用完了,就将还存活的物件复制到另外一块内存区域中,然后将当前内存空间一次性清理掉。 这样的对整个半区进行回收,分配时按照顺序从内存顶端依次分配,这种实现简单,执行高效。 不过这种算法将原有的内存空间减少为实际的一半,代价比较高。

从图中可以看出,整理后的内存十分规整,但是白白浪费一般的内存成本太高。 然而这其实是很重要的一个收集算法,因为现在的商业虚拟机器都采用这种算法来回收新生代。 IBM公司的专门研究表明,新生代中的物件98%都是“朝生夕死”的,所以不需要按照1:1的比例来划分内存。 HotSpot虚拟机器将Java堆划分为年轻代(Young Generation)、老年代(Tenured Generation),其中年轻代又分为一块Eden和两块Survivor。

所有的新建物件都放在年轻代中,年轻代使用的GC算法就是复制算法。 其中Eden与Survivor的内存大小比例为8:2,其中Eden由1大块组成,Survivor由2小块组成。 每次使用内存为1Eden+1Survivor,即90%的内存。 由于年轻代中的物件生命周期往往很短,所以当需要进行GC的时候就将当前90%中存活的物件复制到另外一块Survivor中,原来的Eden与Survivor将被清空。 但是这就有一个问题,我们无法保证每次年轻代GC后存活的物件都不高于10%。 所以在当活下来的物件高于10%的时候,这部分物件将由Tenured进行担保,即无法复制到Survivor中的物件将移动到老年代。

标记-整理算法

复制算法在极端情况下(存活物件较多)效率变得很低,并且需要有额外的空间进行分配担保。 所以在老年代中这种情况一般是不适合的。

所以就出现了标记-整理(Mark-Compact)算法。 与标记清除算法一样,首先是标记物件,然而第二步是将存货的物件向内存一段移动,整理出一块较大的连续内存空间。

垃圾回收的几种形式

Minor GC

在年轻代(包括Eden区和Survivor区)中的垃圾回收称之为 Minor GC。 Minor GC当年轻代中eden区分配满的时候触发,只会清理年轻代。 经过这次GC后,Eden区和From区已经被清空。 这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。 不管怎样,都会保证名为To的Survivor区域是空的。

Full GC

full gc是收集整个堆,包括young gen、old gen、perm gen(如果存在的话)、元空间(1.8及以上)等所有部分的模式。

手动呼叫System.gc()方法 [增加了full GC频率,不建议使用而是让jvm自己管理内存,可以设定-XX:+ DisableExplicitGC来禁止RMI呼叫System.gc]发现perm gen(如果存在永久代的话)需分配空间但已经没有足够空间老年代空间不足,比如说新生代的大物件大阵列晋升到老年代就可能导致老年代空间不足。mixed GC(G1特有)

混合GC 收集整个young gen以及部分old gen的GC。 只有G1有这个模式。

垃圾回收的两种判定方法

1. 引用计数算法

在JDK1.2之前,使用的是引用计数器算法,即当这个类被载入到内存之后,就会产生方法区,堆叠、程式计数器等一系列资讯,当建立物件的时候,为这个物件在堆叠空间中分配物件,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用时,引用计数器继续+1,而当其中一个引用销毁时,引用计数器-1,当引用计数器减为0的时候,标志着这个物件已经没有引用了,可以回收了! 但是这样会有一个问题: 当我们的程式码出现这样的情况时:

ObjA.obj=ObjB

ObjB.obj=ObjA

这样的程式码会产生如下引用情形ObjA指向ObjB,而ObjB又指向objA,这样当其他所有的引用都消失了之后,ObjA和ObjB还有一个相互的引用,也就是说两个物件的引用计数器各为1,而实际上这两个物件都已经没有额外的引用,已经是垃圾了。

2.可达性分析算法

可达性分析算法是从离散数学中的图论引入的,程式把所有的引用关系看做一张图,从一个节点GC Root开始,寻找对应的引用节点,找到这个节点之后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

目前Java中可作为GC Root的物件有:

虚拟机器栈中引用的物件(本地变量表)方法区中静态属性引用的物件方法区中常量引用的物件(final的常量值)本地方法栈中引用的物件(Native物件)。java中存在的四种引用

强引用:只要引用存在,垃圾回收器永远不会回收。软引用:非必须引用,内存溢位之前进行回收。程式码示例:Object obj=new Object();

SoftReference sf=new SoftRerence(obj);

obj=null;

sf.get();//有时会返回null

这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个物件,当然这个物件被标记为需要回收的物件时,则返回null;

软引用主要用于使用者实现类似快取的功能,在内存不足的情况下直接通过软引用取值,无需从繁忙的真实来源查询资料,提升速度; 当内存不足时,自动删除这部分快取资料,从真实的来源查询这些资料。

弱引用: 弱引用也是用来描述非必需物件的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的物件。 可以通过如下程式码实现Object obj=new Object();

WeakReference wf=new WeakReference(obj);

obj=null;

wf.get();//有时会返回null

wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的资料,可以取到,当执行过第二次垃圾回收时,将返回null。 弱引用主要用于监控物件是否已经被标记为即将回收的垃圾,可以通过弱引用的isEnQueues方法返回物件是否被垃圾回收器标记。

虚引用: 虚引用和前面的软引用、弱引用不同,它并不影响物件的生命周期。 在java中用java.lang.ref.PhantomReference类表示。 如果一个物件与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。要注意的是,虚引用必须和引用伫列关联使用,当垃圾回收器准备回收一个物件时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用伫列中。 程式可以通过判断引用伫列中是否已经加入了虚引用,来了解被引用的物件是否将要被垃圾回收。 如果程式发现某个虚引用已经被加入到引用伫列,那么就可以在所引用的物件的内存被回收之前采取必要的行动。 可以通过如下程式码实现

import java.lang.ref.ReferenceQueue;

public class Main {

public static void main(String[] args) {

ReferenceQueue queue = new ReferenceQueue();

PhantomReference pr = new PhantomReference(new String("hello"), queue);

System.out.println(pr.get());

}

}

垃圾收集器

如果说垃圾回收算法是内存回收的方法论,那么垃圾收集器就是具体实现。 jvm会结合针对不同的场景及使用者的配置使用不同的收集器。

年轻代收集器:

Serial、ParNew、Parallel Scavenge

老年代收集器:

Serial Old、Parallel Old、CMS收集器

特殊收集器:

G1收集器(新型,不在年轻、老年代范畴内)

年轻代收集器

Serial

最基本、发展最久的收集器,在jdk3以前是gc收集器的唯一选择,Serial是单执行绪收集器,Serial收集器只能使用一条执行绪进行收集工作,在收集的时候必须得停掉其它执行绪,等待收集工作完成其它执行绪才可以继续工作。

虽然Serial看起来很坑,需停掉别的执行绪以完成自己的gc工作,但是也不是完全没用的,比如说Serial在执行在Client模式下优于其它收集器(简单高效,不过一般都是用Server模式,64bit的jvm甚至没Client模式)

序列收集器组合 Serial + Serial Old

优点: 对于Client模式下的jvm来说是个好的选择。 适用于单核CPU(现在基本都是多核了) 缺点: 收集时要暂停其它执行绪,有点浪费资源,多核下显得。

ParNew收集器

可以认为是Serial的升级版,因为它支援多执行绪[GC执行绪],而且收集算法、Stop The World、回收策略和Serial一样,就是可以有多个GC执行绪并发执行,它是HotSpot第一个真正意义实现并发的收集器。 预设开启执行绪数和当前cpu数量相同,如果cpu核数很多不想用那么多,可以通过-XX:ParallelGCThreads来控制垃圾收集执行绪的数量。

优点:

支援多执行绪,多核CPU下可以充分的利用CPU资源执行在Server模式下新生代首选的收集器【重点是因为新生代的这几个收集器只有它和Serial可以配合CMS收集器一起使用】缺点: 在单核下表现不会比Serial好,由于在单核能利用多核的优势,线上程收集过程中可能会出现频繁上下文切换,导致额外的开销。

Parallel Scavenge

采用复制算法的收集器,和ParNew一样支援多执行绪。 但是该收集器重点关心的是吞吐量(吞吐量 = 程式码执行时间 / (程式码执行时间 + 垃圾收集时间) 如果程式码执行100min垃圾收集1min,则为99%) 对于使用者界面,适合使用GC停顿时间短,不然因为卡顿导致互动界面卡顿将很影响使用者体验。 对于后台高吞吐量可以高效率的利用cpu尽快完成程式运算任务,适合后台运算

并行收集器组合 Parallel Scavenge + Parallel Old

Parallel Scavenge注重吞吐量,所以也成为"吞吐量优先"收集器。

JDK7和8中,作为年轻代预设的收集器

老年代收集器

Serial Old

和新生代的Serial一样为单执行绪,Serial的老年代版本,不过它采用"标记-整理算法",这个模式主要是给Client模式下的JVM使用。 如果是Server模式有两大用途:

jdk5前和Parallel Scavenge搭配使用,jdk5前也只有这个老年代收集器可以和它搭配。作为CMS收集器的后备。Parallel Old

支援多执行绪,Parallel Scavenge的老年版本,jdk6开始出现,采用"标记-整理算法"。 在jdk6以前,新生代的Parallel Scavenge只能和Serial Old配合使用,而且Serial Old为单执行绪Server模式下无法充分利用多核cpu,这种结合并不能让应用的吞吐量最大化。

Parallel Old的出现结合Parallel Scavenge,真正的形成“吞吐量优先”的收集器组合。 JDK7和8中,作为老年代预设的收集器

CMS收集器

CMS收集器(Concurrent Mark Sweep)是以一种获取最短回收停顿时间为目标的收集器。 (重视响应,可以带来好的使用者体验,被sun称为并发低停顿收集器)启用CMS: -XX:+UseConcMarkSweepGC

正如其名,CMS采用的是"标记-清除"(Mark Sweep)算法,而且是支援并发的。

它的运作分为4个阶段:

初始标记(initial mark): 标记一下GC Roots能直接关联到的物件并发标记(concurrent mark): 并发标记就需要标记出GC roots 关联到的物件的引用物件有哪些。 比如说 A -> B (A引用B,假设A是GC Roots关联到的物件),那么这个阶段就是标记出B物件,A物件会在初始标记中标记出来。 这个过程是可以和使用者执行绪并发执行的。 所谓的并发的实现,可以有几种方式,比如说,标记了100个物件,那么就停一停,让使用者执行绪跑一会; 再比如说,标记了10ms,再停一停,之类的实现。重新标记(remark): 为了修正因并发标记期间使用者程式运作而产生变动的那一部分物件的标记记录,会有些许停顿,时间上一般 初始标记 并发清除(sweep): 将前面标记物件的内存回收,这个阶段GC执行绪与使用者执行绪并发执行。以上初始标记和重新标记需要停掉其它执行java执行绪。 之所以说CMS的使用者体验好,是因为CMS收集器的内存回收工作是可以和使用者执行绪一起并发执行。 总体上CMS是款优秀的收集器,但是它也有缺点:

cms对cpu特别敏感,cms执行执行绪和应用程序并发执行需要多核cpu,如果cpu核数多的话可以发挥它并发执行的优势,但是cms预设配置启动的时候垃圾执行绪数为 (cpu数量+3)/4,它的效能很容易受cpu核数影响,当cpu的数目少的时候比如说为为2核,如果这个时候cpu运算压力比较大,还要分一半给cms运作,这可能会很大程度的影响到计算机效能。cms无法处理浮动垃圾,可能导致Concurrent Mode Failure(并发模式故障)而触发full GC由于cms是采用"标记-清除“算法,因此就会存在垃圾碎片的问题,为了解决这个问题cms提供了-XX:+UseCMSCompactAtFullCollection选项,这个选项相当于一个开关(预设开启),用于CMS要进行full GC时开启内存碎片合并,内存整理的过程是无法并发的,且开启这个选项会影响效能(比如停顿时间变长)Concurrent mode failure: 如果CMS回收过程还没有执行完,老年代的剩余空间就用完了,或者,当前老年代空间不能满足一次内存分配请求(可能物件较大),那么此时将触发担保机制,停顿所有使用者执行绪,序列老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间;

浮动垃圾: 由于cms支援执行的时候使用者执行绪也在执行,程式执行的时候会产生新的垃圾,这里产生的垃圾就是浮动垃圾,cms无法当次处理,得等下次才可以。

假如有一个物件GC执行绪没有标记(使用者执行绪之前没在用),然后轮到了使用者执行绪,使用者执行绪说,这个物件我重新又要用了,不要把这个物件GC掉,这个时候怎么办? 假如这个时候处理不了,还是GC了,那么程式就直接报错了,这个是不允许的,解决办法可以百度搜索“cms 三色标记法”获取答案

G1收集器

G1(garbage first)收集器是当前最为前沿的收集器之一(1.7以后才开始有),同cms一样也是关注降低延迟,是用于替代cms功能更为强大的新型收集器,因为它解决了cms产生空间碎片等一系列缺陷。

当G1确定有必要进行垃圾回收时,它会先收集存活资料最少的区域(垃圾优先) g1的特别之处在于它强化了分割槽,弱化了分代的概念,是区域化、增量式的收集器,它不属于新生代也不属于老年代收集器。用到的算法为标记-清理、复制算法

G1是区域化的,它将java堆内存划分为若干个大小相同的区域"region“,JVM可以设定每个region的大小(1-32m,大小得看堆内存大小,必须是2的幂),它会根据当前的堆内存分配合理的region大小。

g1通过并发(并行)标记阶段查询老年代存活物件,通过并行复制压缩存活物件(这样可以省出连续空间供大物件使用)。g1将一组或多组区域中存活物件以增量并行的方式复制到不同区域进行压缩,从而减少堆碎片,目标是尽可能多回收堆空间,且尽可能不超出暂停目标以达到低延迟的目的。g1提供三种垃圾回收模式 young gc、mixed gc 和 full gc,不像其它的收集器,根据区域而不是分代,新生代老年代的物件它都能回收。几个重要的预设值,更多的检视官方文件oracle官方g1中文文件 g1是自适应的回收器,提供了若干个预设值,无需修改就可高效运作

-XX:G1HeapRegionSize=n 设定g1 region大小,不设定的话自己会根据堆大小算,目标是根据最小堆内存划分2048个区域-XX:MaxGCPauseMillis=200 最大停顿时间 预设200毫秒JDK9中,G1作为预设的收集器

JDK7/8,预设关闭的,开启选项 -XX:+UseG1GC

end:如果你觉得本文对你有帮助的话,记得关注点赞转发,你的支援就是我更新动力。

2019-09-26 08:53:00

相关文章