JAVA虚拟机详解(JVM堆GC直接内存性能调优)建议收藏
一、JVM(JAVA虚拟机)
JVM(Java虚拟机):是一个抽象的计算模型。如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域。目的是为构建在其上运行的应用程序提供一个运行环境,能够运行java字节码。JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构。
二、JVM内存模型组成元素
Java内存模型主要包含线程私有的程序计数器、java虚拟机栈、本地方法栈和线程共享的堆空间、元数据区、直接内存。
1、Java运行时数据区域
Java虚拟机在执行过程中会将所管理的内存划分为不同的区域,有的随着线程产生和消失,有的随着Java进程产生和消失。
根据JVM规范,JVM运行时区域大致分为程序计数器、虚拟机栈、本地方法栈、堆、方法区(jkd1。8废弃)五个部分。
2、程序计数器(PC寄存器、计数器)
程序计数器就是当前线程所执行的字节码的行号指示器,通过改变计数器的值,来选取下一行指令,通过它主要实现跳转、循环、恢复线程等功能。
在任何时刻,一个处理器内核只能运行一个线程,多线程是通过抢占CPU,分配时间完成的。这时就需要有个标记,来标明线程执行到哪里,程序计数器便拥有这样的功能,所以,每个线程都有自己的程序计数器。
可以理解为一个指针,指向方法区中的方法字节码(用来存储指向下一个指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
倘若执行的是native方法,则程序计数器中为空。
3、Java虚拟机栈(JVMStacks)
虚拟机栈也就是平常所称的栈内存,每个线程对应一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫栈帧的东西,每个方法在执行的同时都会创建一个栈帧,方法被执行时入栈,执行完后出栈。
不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致。
每个栈帧主要包含的内容如下:局部变量表:存储着java基本数据类型(bytebooleancharintlongdoublefloatshort)以及对象的引用。
注意:这里的基本数据类型指的是方法内的局部变量
局部变量表随着栈帧的创建而创建,它的大小在编译时确定,创建时只需分配事先规定的大小即可。在方法运行过程中,局部变量表的大小不会发生改变。操作数栈动态连接方法返回地址
虚拟机栈可能会抛出两种异常:栈溢出(StackOverFlowError):
若Java虚拟机栈的大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度时,抛出StackOverFlowError异常内存溢出(OutOfMemoryError):
若虚拟机栈的容量允许动态扩展,那么当线程请求栈时内存用完了,无法再动态扩展时,抛出OOM异常
3、本地方法栈(NativeMethodStacks)
本地方法栈是为JVM运行Native方法准备的空间,由于很多Native方法都是用C语言实现的,所以它通常又叫C栈。
本地方法栈与虚拟机栈的作用是相似的,都是线程私有的,只不过本地方法栈是描述本地方法运行过程的内存模型。
本地方法被执行时,在本地方法栈也会创建一块栈帧,用于存放该方法的局部变量表、操作数栈、动态链接、方法出口信息等。方法执行结束后,相应的栈帧也会出栈,并释放内存空间。也会抛出StackOverFlowError和OutOfMemoryError异常。
虚拟机栈和本地方法栈的主要区别:虚拟机栈执行的是java方法本地方法栈执行的是native方法
4、Java堆(JavaHeap)
Java堆中是JVM管理的最大一块内存空间。主要存放对象实例。
Java堆是所有线程共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都存放在这里,是垃圾收集器管理的主要区域。
Java堆的分区:在jdk1。8之前,分为新生代、老年代、永久代在jdk1。8及之后,只分为新生代、老年代
永久代在jdk1。8已经被移除,被一个称为元数据区(元空间)的区域所取代
Java堆内存大小:堆内存大小新生代老年代(新生代占堆空间的13、老年代占堆空间23)。既可以是固定大小的,也可以是可扩展的(通过参数Xmx和Xms设定)。如果堆无法扩展或者无法分配内存时报OOM。
主要存储的内容是:对象实例;类初始化生成的对象;基本数据类型的数组也是对象实例;字符串常量池:字符串常量池原本存放在方法区,jdk8开始放置于堆中字符串常量池存储的是string对象的直接引用,而不是直接存放的对象,是一张stringtable静态变量
static修饰的静态变量,jdk8时从方法区迁移至堆中线程分配缓冲区(ThreadLocalAllocationBuffer)线程私有,但是不影响java堆的共性增加线程分配缓冲区是为了提升对象分配时的效率。
堆和栈的区别:管理方式,堆需要GC,栈自动释放大小不同,堆比栈大碎片相关:栈产生的碎片远小于堆,因为GC不是实时的分配方式:栈支持静态分配内存和动态分配,堆只支持动态分配效率:栈的效率比堆高
5、方法区(逻辑上)
方法区是JVM的一个规范,所有虚拟机必须要遵守的。常见的JVM虚拟机有Hotspot、JRockit(Oracle)、J9(IBM)
方法区逻辑上属于堆的一部分,但是为了与堆区分,通常又叫非堆区
各个线程共享,主要用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误。关闭JVM就会释放这个区域的内存。Java8以前是放在JVM内存中的,由堆空间中的永久代实现,受JVM内存大小参数限制Java8移除了永久代和方法区,引入了元空间
拓展:
JDK版本
方法区的实现
运行时常量池所在的位置
JDK6
PermGenspace(永久代)
PermGenspace(永久代)
JDK7
PermGenspace(永久代)
Heap(堆)
JDK8
Metaspace(元空间)
Heap(堆)
6、元空间(元数据区、Metaspace)
元空间是JDK1。8及之后,HotSpot虚拟机对方法区的新实现。
元空间不在虚拟机中,而是直接用物理(本地)内存实现,不再受JVM内存大小参数限制,JVM不会再出现方法区的内存溢出问题,但如果物理内存被占满了,元空间也会报OOM
元空间和方法区不同的地方在于编译期间和类加载完成后的内容有少许不同,不过总的来说分为这两部分:类元信息(Class)
类元信息在类编译期间放入元空间,里面放置了类的基本信息:版本、字段、方法、接口以及常量池表
常量池表:主要存放了类编译期间生成的字面量、符号引用,这些信息在类加载完后会被解析到运行时常量池中运行时常量池(RuntimeConstantPool)
运行时常量池主要存放在类加载后被解析的字面量与符号引用,但不止这些运行时常量池具备动态性,可以添加数据,比较多的使用就是String类的intern()方法
7、直接内存(DirectMemory)
直接内存不是虚拟机运行时数据区的一部分,而是在Java堆外,直接向系统申请的内存区域。
常见于NIO操作时,用于数据缓冲区(比如ByteBuffer使用的就是直接内存)。
分配、回收成本较高,但读写性能高。
直接内存不受JVM内存回收管理(直接内存的分配和释放是Java会通过UnSafe对象来管理的),但是系统内存是有限的,物理内存不足时会报OOM。
三、Java程序内存JVM内存本地内存
1、JVM内存(JVM虚拟机数据区)
Java虚拟机在执行的时候会把管理的内存分配到不同的区域,这些区域称为虚拟机(JVM)内存。
JVM内存受虚拟机内存大小的参数控制,当大小超过参数设置的大小时会报OOM
2、本地内存(元空间直接内存)
对于虚拟机没有直接管理的物理内存,也会有一定的利用,这些被利用但不在虚拟机内存的地方称为本地内存。
本地内存不受虚拟机内存参数的限制,只受物理内存容量的限制。
虽然不受参数的限制,如果所占内存超过物理内存,仍然会报OOM
3、堆外内存
直接内存
直接内存不是虚拟机运行时数据区的一部分,而是在Java堆外,直接向系统申请的内存区域。
可通过XX:MaxDirectMemorySize调整大小,默认和Java堆最大值一样
内存不足时抛出OutOfMemoryError或者OutOfMemoryError:Directbuffermemory;
线程堆栈
可通过Xss调整大小
内存不足时抛出StackOverflowError(如果线程请求的栈深度大于虚拟机所允许的深度)
OutOfMemoryError(如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存)
Socket缓存区
每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB内存,连接多的话这块内存占用也比较可观。
如果无法分配,可能会抛出IOException:Toomanyopenfiles异常
JNI代码
如果代码中使用了JNI调用本地库,那本地库使用的内存也不在堆中,而是占用Java虚拟机的本地方法栈和本地内存
虚拟机和垃圾收集器
虚拟机、垃圾收集器的工作也是要消耗一定数量的内存。
四、JVM堆及各种GC详解
1、结构图(新生代、老年代、永久代)
JVM中的堆,一般分为三大部分:新生代、老年代、永久代(Java8中已经被移除)
2、新生代、MinorGC(YoungGC)
2。1、新生代:
主要是用来存放新生的对象。一般占据堆的13空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。
新生代又分为Eden、S0、S1(SurvivorFrom、SurvivorTo)三个区:Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。SurvivorFrom区:上一次GC的幸存者,作为这一次GC的被扫描者。SurvivorTo区:保留了一次MinorGC过程中的幸存者。
Eden和S0,S1区的比例为8:1:1
幸存者S0,S1区:复制之后发生交换,谁是空的,谁就是SurvivorTo区
JVM每次只会使用eden和其中一块survivor来为对象服务,所以无论什么时候,都会有一块survivor是空的,因此新生代实际可用空间只有90。
当JVM无法为新建对象分配内存空间的时候(Eden满了),MinorGC被触发。因此新生代空间占用率越高,MinorGC越频繁。
2。2、MinorGC
MinorGC的过程(采用复制算法):首先,把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年龄以及达到了老年的标准,一般是15,则赋值到老年代区)同时把这些对象的年龄1(如果ServicorTo不够位置了就放到老年区)然后,清空Eden和ServicorFrom中的对象;最后,ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区。
MinorGC触发机制:
当年轻代满(指的是Eden满,Survivor满不会引发GC)时就会触发MinorGC(通过复制算法回收垃圾)。
对象年龄(Age)计数器
虚拟机给每个对象定义了一个对象年龄(Age)计数器。
如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。
对象在Survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。
对象晋升老年代的年龄阈值,可以通过参数XX:MaxTenuringThreshold(阈值)来设置。
2。3、老年代、MajorGC(OldGC)
年代老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象然后回收没有标记的对象。MajorGC的耗时比较长(速度一般会比MinorGC慢10倍以上,STW的时间更长),因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM(OutofMemory)异常。
2。4、永久代、元数据区(元空间)、常量池
永久代(PermGen)是JDK7及之前,HotSpot虚拟机基于JVM规范对方法区的一个落地实现,其他虚拟机如JRockit(Oracle)、J9(IBM)有方法区,但是没有永久代。在JDK1。8已经被移除,取而代之的是元数据区(元空间)内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域。和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。
元数据区(元空间、Metaspace)元空间的本质和永久代类似,都是对JVM规范中方法区的实现。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:XX:MetaspaceSize(初始空间大小):达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。XX:MaxMetaspaceSize(最大空间)默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与GC相关的属性:XX:MinMetaspaceFreeRatio:在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集;XX:MaxMetaspaceFreeRatio:在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集;
类的元数据放入本地内存中,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由虚拟机的MaxPermSize控制,而由系统的实际可用空间来控制。
元空间替换永久代的原因分析:字符串存在永久代中,容易出现性能问题和内存溢出。通常会使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限,但是类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
当使用元空间时,可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。永久代会为GC带来不必要的复杂度,并且回收效率偏低。Oracle可能会将HotSpot与JRockit合二为一。
2。5、类常量池、运行时常量池、字符串常量池
类常量池
在类编译过程中,会把类元信息存放到元空间(方法区),类元信息其中一部分便是类常量池
主要存放字面量(字面量一部分便是文本字符)和符号引用
运行时常量池
在类加载时,会将字面量和符号引用解析为直接引用存储在运行时常量池
(文本字符会在解析时查找字符串常量池,查出这个文本字符对应的字符串对象的直接引用,将直接引用存储在运行时常量池)在JDK6,运行时常量池存在于方法区在JDK7,运行时常量池存在于Java堆
字符串常量池
存储的是字符串对象的引用,而不是字符串本身
字符串常量池在jdk7时就已经从方法区迁移到了java堆中(JDK8时,方法区就是元空间)。
2。6、MinorGC、MajorGC、FullGC的区别新生代收集(MinorGCYoungGC):只是新生代的垃圾收集老年代收集(MajorGCOldGC):只是老年代的垃圾收集整堆收集(FullGC):收集整个java堆(younggenoldgen)和方法区的垃圾收集
FullGC触发机制:调用System。gc时,系统建议执行FullGC,但是不必然执行老年代空间不足方法区空间不足通过MinorGC后进入老年代的平均大小大于老年代的可用内存由Eden区、survivorspace1(FromSpace)区向survivorspace2(ToSpace)区复制时,对象大小大于ToSpace可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小当永久代满时也会引发FullGC,会导致Class、Method元信息的卸载
2。7、堆空间分成不同区的原因堆空间分为新生代和老年代的原因
根据对象存活的时间,有的对象寿命长,有的对象寿命短。应该将寿命长的对象放在一个区,寿命短的对象放在一个区。不同的区采用不同的垃圾收集算法。寿命短的区清理频次高一点,寿命长的区清理频次低一点。新生代分为了eden、Survivor区的原因
为了更好的管理堆内存中的对象,方便GC算法(复制算法)来进行垃圾回收。
如果没有Survivor区,那么Eden每次满了清理垃圾,存活的对象被迁移到老年区,老年区满了,就会触发FullGC,而FullGC是非常耗时的。
将Eden区满了的对象,添加到Survivor区,等对象反复清理几遍之后都没清理掉,再放到老年区,这样老年区的压力就会小很多。即Survivor相当于一个筛子,筛掉生命周期短的,将生命周期长的放到老年代区,减少老年代被清理的次数。新生代的Survivor区又分为s0和s1区的原因:
分两个区的好处就是解决内存碎片化。为什么一个Survivor区不行?
假设现在只有一个survivor区,模拟一下流程:
新建的对象在Eden中,一旦Eden满了,触发一次MinorGC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行MinorGC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。GC优化的本质,也是为什么分代的原因:减少GC次数和GC时间,避免全区扫描。
2。8、GC(垃圾回收)在默认情况下,通过System。gc()或者Runtime。getRuntime()。gc()的调用,会显式触发FullGC(完整的GC事件),对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。在GC完成之前,整个JVM将冻结(即正在运行的所有服务将被暂停),通常完整的GC需要很长时间才能完成。因此在不合适的时间运行GC,将导致不良的用户体验,甚至是崩溃。JVM具有复杂的算法,该算法始终在后台运行,进行所有计算以及有关何时触发GC的计算。当显式调用System。gc()调用时,所有这些计算都将被抛掉。
2。8、GC常用算法(垃圾回收)
分代收集算法(现在的虚拟机垃圾收集大多采用这种方式)
它根据对象的生存周期,将堆分为新生代(Young)和老年代(Tenure)。
新生代中,由于对象生存期短,每次回收都会有大量对象死去,所以使用的是复制算法。
老年代里的对象存活率较高,没有额外的空间进行分配担保,所以使用的是标记整理或者标记清除。
标记清除算法
每个对象都会存储一个标记位,记录对象的状态(活着或是死亡)。
标记清除算法分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行GC操作。
优点是可以避免内存碎片。
标记压缩(标记整理)算法
标记压缩法是标记清除法的一个改进版,和标记清除算法基本相同。
不同的就是,在清除完成之后,会把存活的对象向内存的一边进行压缩(整理),然后把剩下的所有对象全部清除,这样就可以解决内存碎片问题。
复制算法
复制算法将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的。
当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。
此时,空闲区间已经与活动区间交换,而垃圾对象现在已经全部留在了原来的活动区间,也就是现在的空闲区间。事实上,在活动区间转换为空间区间的同时,垃圾对象已经被一次性全部回收。
五、JVM的性能调优
1、调优参数
配置方式java〔options〕MainClass〔arguments〕options:JVM启动参数。配置多个参数的时候,参数之间使用空格分隔。参数命名:常见为参数名参数赋值:常见为参数名参数值或参数名:参数值
内存参数:Xms(s为strating):初始堆大小,JVM启动的时候,给定堆空间大小。可以设置与Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。示例:Xms3550m:设置JVM初始内存为3550M。Xmx(x为max):最大堆大小,JVM运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。示例:Xmx3550m:设置JVM最大可用内存为3550M。Xmn(n为new):新生代大小。整个堆大小新生代大小老年代大小持久代大小(jkd1。8废弃)持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的38。示例:Xmn2g:设置年轻代大小为2G。Xss:设置每个线程的Java栈大小。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在30005000左右。
JDK5。0以后每个线程Java栈大小为1M,以前每个线程堆栈大小为256K。示例:Xss128k:设置每个线程的堆栈大小为128k。XX:NewSizen:设置年轻代大小
XX:NewRation:设置年轻代(包括Eden和两个Survivor区)与年老代的比值。
示例:设置为4:年轻代与年老代所占比值为1:4,年轻代占整个堆栈的15
XX:SurvivorRation:年轻代中Eden区与两个Survivor区的比值。
注意Survivor区有两个。示例:设置为3:表示Eden:Survivor3:2,一个Survivor区占整个年轻代的15。XX:MaxPermSizen:设置永久代大小示例:XX:MaxPermSize16m:设置持久代大小为16m。XX:MaxTenuringThresholdn:设置垃圾最大年龄
如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。
如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。
2、垃圾回收器参数
VM给了三种选择:串行收集器、并行收集器、并发收集器。串行收集器只适用于小数据量的情况。XX:UseSerialGC:设置串行收集器。XX:UseParallelGC:设置并行收集器,表示年轻代使用并行收集器。XX:UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5。0以上,JVM会根据系统配置自行设置,所以无需再设置此值。XX:UseParallelOldGC:设置并行年老代收集器。JDK6。0支持对年老代并行收集。XX:UseConcMarkSweepGC:设置年老代并发收集器CMS。XX:UseG1GC:设置G1收集器XX:ParallelGCThreadsn:设置并行收集器收集时最大线程数使用的CPU数。并行收集线程数。XX:MaxGCPauseMillisn:设置并行收集最大暂停时间,单位毫秒。可以减少STW时间。XX:GCTimeRation:设置垃圾回收时间占程序运行时间的百分比。公式为1(1n)并发收集器设置XX:CMSIncrementalMode:设置为增量模式。适用于单CPU情况。XX:UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等。此值建议使用并行收集器时,一直打开。XX:CMSFullGCsBeforeCompactionn:此值设置运行多少次GC以后对内存空间进行压缩、整理。因为并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生碎片,使得运行效率降低。XX:UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片。
3、元空间参数:XX:MetaspaceSize:初始化的Metaspace大小,该值越大触发MetaspaceGC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,而上下浮动主要由XX:MaxMetaspaceFreeRatio和XX:MinMetaspaceFreeRatio两个参数控制。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。
使用javaXX:PrintFlagsInitial命令查看本机的初始化参数。XX:MinMetaspaceFreeRatio:
当进行过MetaspaceGC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增加MetaspaceSize的大小(为了避免过早引发一次垃圾回收)。默认值为40,也就是40。
设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。XX:MaxMetaspaceFreeRatio:当进行过MetaspaceGC之后,会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会减小MetaspaceSize的大小。默认值为70,也就是70。XX:MaxMetaspaceExpansion:Metaspace增长时的最大幅度。默认值大约为5MB。XX:MinMetaspaceExpansion:Metaspace增长时的最小幅度。默认值大约330KB。XX:MaxMetaspaceSize:最大空间。默认是没有限制的。
指定该值可以防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。调优建议
1、年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达老年代的对象。
吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
2、老年代大小选择
响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。
如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。
最优化的方案,一般需要参考以下数据获得:并发垃圾收集信息持久代并发收集次数传统GC信息花在年轻代和年老代回收上的时间比例减少年轻代和老年代花费的时间,一般会提高应用的效率
吞吐量优先的应用:
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的老年代。
原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。较小堆引起的碎片问题,因为老年代的并发收集器使用标记清除算法,所以不会对堆进行压缩。
当收集器回收时,它会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现碎片,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记清除方式进行回收。
如果出现碎片,可能需要进行如下配置:XX:UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。XX:CMSFullGCsBeforeCompaction0:上面配置开启的情况下,这里设置多少次FullGC后,对老年代进行压缩。
仙岳山这座山最湖南一座城市,需要一座山,用来俯瞰。醴陵,有幸拥有这样的一座山。进入这个城市前,第一眼看到的是仙岳山,然后是仙岳山顶的那尊佛像,夏日阳光里,格外璀璨。佛像下方,是几经更迭的护国寺,佛教
宝马为iX5生产氢燃料电池系统,上汽凭借技术优势领跑氢赛道在碳排放导致全球气候变暖的大背景下,以低碳减排为目标的能源革命正在全球范围内展开。兼具清洁与高效储能载体的双重角色的氢能,被誉为21世纪最具发展潜力的清洁能源,已被包括中国在内的主
vivo占比13逆势上涨,国内Q2高端市场份额仅次于苹果欢迎来到硬派偏执狂的频道,聊聊vivo占比13逆势上涨,国内Q2高端市场份额仅次于苹果。今天为大家点一首比较好听的歌孤勇者近期,市场调研机构Counterpoint公布数据显示在2
苹果称95的iCloud用户开启了双重认证8月28日消息,随着今年秋季推出的下一轮软件更新,苹果iOS16和macOSVentura等将集成对passkey标准的支持。该标准被描述为独特的数字密钥,旨在通过简化的跨设备网站
2022为何医院连续暴雷湘雅湖南省医疗系统的一哥,曾经是多么的辉煌,就连我们敬爱的易中天老师都无比自豪的说我是在湘雅医院出生的。因为辉煌过,造就了不可一世。去过湘雅看病的人应该都会有个感慨,牛真牛必须牛。
长达8分钟新闻联播高规格罕见定调未来五年主攻方向已定在万众瞩目下,昨日华为旗舰手机Mate50终于亮相,其中昆仑玻璃超光变摄像卫星通讯技术等黑科技,可谓是有一定的看点。但其中的不足更是显而易见,比如没有5G之前看起来高大上的捅破天技
海南将建1个II型大城市1个中等城市2市入围区域中心城市近年来,随着我国经济结构的不断调整,经济增长方式也在持续优化,区域之间的合作更加紧密,一体化高质量发展已经成为新形势下我国经济发展的主方向。改革开放以来,以广东江苏浙江福建山东北京
见过玩游戏还可以征婚的吗?2022年度国产大作,仙侠国战游戏远征2首服新开!上线送近10亿钻石可在钻石商城内兑换稀有道具!!!更有价值超3000的礼包等你来领!还有机会领取飞天茅台,官方这次是真是下血本了!
吉隆县中国尼泊尔边境,每个村都是宝藏,其中一村曾是无国籍人在西藏的5条著名的沟当中,吉隆沟尤其让我迷恋,大概是因为我在这里感受到了更深刻的藏族乡村风情雄壮的雪山湖泊和造物主的神奇。吉隆是中国的重要边陲小镇,与尼泊尔交界,边境线长达162公
大疆穿越机新品阿凡达测试在买大疆mini3pro的时候曾经6次更换,原因就是镜头起雾。希望这次阿凡达没有这样的硬伤。付款后第二天中午就收到新品阿凡达穿越机。外观确实惊艳。眼镜,机身,操纵杆都是外观完美,迫
起风的日子学会依风起舞,落雨的时候学会为自己撑起一把伞起风的日子学会依风起舞,落雨的时候学会为自己撑起一把伞,认清了生活不易,所以在命运的刁难中愈发坚强习惯了单枪匹马,于是在角色的变迁中逐渐成熟看遍了人情冷暖,最终在岁月的无常中学会坦