JVM调优笔记:认识JVM内存模型(jdk1.8)

文章目录

  • 1、什么是JVM
  • 2、jdk、jre、jvm关系
  • 3、JVM执行过程
  • 4、JVM执行程序的过程
  • 5、JVM运行时数据区
    • 虚拟机栈(线程私有)
    • 本地方法栈(线程私有)
    • 程序计数器(线程私有)
    • 堆(线程共享)
    • 方法区(线程共享)
  • 6、内存分配参数
    • 大小分配
    • 比例分配
  • 7、垃圾回收
    • 算法与思想
    • 分类
      • 新生代串行收集器 Serial
      • 老年代串行收集器 Serial Old
      • 新生代并行收集器 ParNew
      • 新生代并行回收收集器 Parallel Scavenge
      • 老年代并行回收收集器 Parallel Old
      • CMS收集器
      • G1收集器
  • 8、常见调优方法
  • 9、其他实用JVM参数

1、什么是JVM

(1)JVM即Java Virtual Machine,java虚拟机。它是一个虚构出来的机器,是通过实际计算机上的仿真模拟各种功能实现的。
(2)JVM包含了一套字节码指令集,一组寄存器、一个占、一个垃圾回收堆和一个存储方法域。
(3)JVM支持java程序跨平台。因为它屏蔽了与具体操作系统平台相关信息,使我们的Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上运行。

JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令来执行。

2、jdk、jre、jvm关系

(1)JRE(Java Runtime Environment),也就是java平台。所有的java程序都要在JRE环境下才能运行。
(2)JDK(Java Development Kit),是开发者用来编译、调试程序用的开发包。JDK也是JAVA程序需要在JRE上运行。
(3)JVM(Java Virtual Machine),是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。
Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。

3、JVM执行过程

(1)jvm是java的核心和基础,在java编译器和os平台之间的虚拟处理器,可在上面执行字节码程序。
(2)java编译器只要面向jvm,生成jvm能理解的字节码文件。java源文件经编译成字节码程序,通过jvm将每条指令翻译成不同的机器码,通过特定平台运行。
JVM调优笔记:认识JVM内存模型(jdk1.8)_第1张图片

4、JVM执行程序的过程

JVM调优笔记:认识JVM内存模型(jdk1.8)_第2张图片

第一步:程序员写出各种java文件 通过编译器编译成class字节码文件

第二步:然后我们通过tomcat 或者java -jar的形式在linux或windows上运行

第三步:前提安装了jdk 我们的jar或war程序就整体是一个jvm,在运行时会首先去让最顶层的父层 启动类加载器(BootStrap ClassLoader)去java的lib包下加载核心类库。然后通过 Extension ClassLoader 扩展类加载器 在lib/ext下加载扩展类 ,再然后通过Application ClassLoader应用程序类加载器 加载我们自己的java程序中的class字节码文件 。。。。。其中加载过程中会有双亲委派机制,就是当前应用程序类加载器向上层父级扩展类中找寻是否该类应由其执行,扩展类加载器在向上。到达启动类加载器去核心类库中找寻是否是自己的类。如果是则有自己进行类加载进jvm内存方法区中。如果没有则再向下进行委派。直到找到是属于各级加载器自己的类。如果每一层都没有。那么由发起方自己去执行该类(类加载进方法区)

第四部:如果我们是通过tomcat加载的话。tomcat会将应用程序解析,验证,通过自定义类加载加载如tomcat自己的jvm方法区。

第五步:放入方法区的class类会被字节码引擎执行字节码指令进行各种操作(读写),过程中会有程序计数器来记录指令执行的位置。

第六步:执行过程从各个方法也就是线程执行起始,该对象在建立的时候会被放入堆内存中。

第七步:初始化的对象 都会有自己的成员变量,赋完默认值,会将成员变量进行压栈,进入java虚拟机栈中成为栈帧。并且每一个成员变量都在栈内存中指向堆内存的对象。堆栈相互关联。

第八步:垃圾回收时刻监测堆内存中是否有未被栈内存中成员变量指向的对象。如果有,则进行垃圾回收。

第九步:而在方法去的类和栈中的栈帧什么时候被回收呢?首先该类所有的实例化对象都在堆内存中被回收。其次加载该类的类加载器已经被回收,最后对该类的class文件没有任何堆内存的线程或线程中变量引用。这三种情况全部满足,栈和方法区的线程和类都全部被回收了。

5、JVM运行时数据区

JVM调优笔记:认识JVM内存模型(jdk1.8)_第3张图片
从图来看,我们可以把Java内存区分为堆内存(Heap)和栈内存(Stack)。虽然这种分法比较粗糙,实际上要复杂的多,不过初学者来说这是我们最关注的的两块区域。

总内存=堆内存(Xmx)+方法区内存(MaxPermSize)+栈内存(Xss)*线程数+直接内存(MaxDirectMemorySize,堆外)+虚拟机内存

虚拟机栈(线程私有)

(1)Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)

(2)指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)

(3)方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。

JVM调优笔记:认识JVM内存模型(jdk1.8)_第4张图片
这块区域存在两种异常情况
(1)如果线程请求的栈深度大于虚拟机允许的深度,抛出StackOverflowError异常;
(2)如果虚拟机栈可以动态扩展,且扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

在这里也说一个题外话,由于每个方法从进入到返回对应着栈帧的压入和弹出,这个过程需要耗费一定的时间和资源,所以也意味着代码中调用的方法越多,执行效率也会越低。所以拆分方法的也不是越多越好的。

本地方法栈(线程私有)

本地方法栈和Java虚拟机栈的功能很相似,Java虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理Native方法的调用。本地方法并不是用Java实现的,而是使用C实现的。在HotSpot虚拟机中,不区分本地方法栈和虚拟机栈,因此和虚拟机栈一样,它也会抛出StackOverFlowError和OutOfMemoryError

程序计数器(线程私有)

(1)由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的.
(2)在任一具体时刻,一个CPU的内核只会执行一条线程中的指令
(3)为了能够使得每个线程都在线程切换后能够恢复在切 换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰

程序计数器是每个线程所私有的,由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

堆(线程共享)

(1)Java堆可以说是Java运行时内存中最重要的部分,是被所有线程共享的一块区域几乎所有的对象和数组都是在堆空间中分配空间的,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术导致一些微妙的变化,所有对象堆上分配也渐渐变得不那么”绝对”了。
(2)Java堆分为新生代和老年代两个部分,新生代又可进一步分为edenSurvivor space0(from space)和survivor space1(to space)。eden意为伊甸园,即对象的出生地,s0和s1为survivor空间,可意为幸存者,如果幸存者的对象到了指定年龄仍未被回收,则有机会进入老年代(tenured)。
JVM调优笔记:认识JVM内存模型(jdk1.8)_第5张图片
默认值:
老年代:2/3的堆空间
年轻代:1/3的堆空间
eden区:8/10 的年轻代
survivor0: 1/10 的年轻代
survivor1:1/10的年轻代

方法区(线程共享)

(1)被所有方法线程共享的一块内存区域。
(2)用于存储已经被虚拟机加载的类信息,常量,静态变量等。
(3)这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。

在JDK1.8之前HotSpot的实现中是用永久代实现的,虽然叫永久代但同样也是可以被GC回收的,只是对于GC的表现也和堆空间略有不同,通常对永久代GC的回收从两个方面分析,一是GC对永久代常量池的回收,二是永久代对类元数据的回收(需要虚拟机确认该类的所有实例已被回收并不会再被使用,并且加载该类的ClassLoader已经被回收,GC就有可能回收该类型);而在JDK1.8之后永久代被抛弃,使用元空间。

6、内存分配参数

大小分配

最大堆内存:-Xmx,指新生代和老年代的大小之和的最大值

最小堆内存:-Xms,Java应用程序运行时,首先会被分配的内存大小,无法满足时会向操作系统申请更多的内存。JVM会试图将系统内存尽可能限制在-Xms中,因此当内存实际使用量触及-Xms指定大小时,会触发Full GC,因此把-Xms设置为-Xmx时,可以在系统运行初期减少GC次数和耗时

新生代:-Xmn,设置新生代的大小会影响老年代的大小,这个参数对系统性能与GC有很大的影响。一般新生代设置为整个堆内存空间的1/4到1/3左右。在HotSpot中,可以用-XX:NewSize和-XX:MaxNewSize分别设置新生代初始值和最大值,但是一般用-Xmn统一设置就足够了

永久代:-XX:PermSize和-XX:MaxPermSize,在JDK1.8之前,可以分别设置永久代的初始大小和最大值

元空间:-XX:MetaspaceSize和-XX:MaxMetaspaceSize,在JDK1.8之后使用元空间,元空间的大小仅受本地内存限制,但可以分别设置元空间的初始大小和最大值(默认没有限制)

每个线程堆栈:-Xss,在线程中进行局部变量分配,函数调用时都需要在栈中开辟空间。如果栈的空间分配太小,那线程运行中可能没有足够空间分配局部变量或达不到足够的深度导致异常退出;如果栈空间过大,那么开设线程所需的内存成本上升,系统所能支持的线程总数也会下降

Direct内存:-XX:MaxDirectMemorySize,javaNIO中通过Direct内存来提高性能,这个区域的大小默认是64M,在适当的场景可以设置大一些

比例分配

新生代:-XX:SurvivorRatio,用于设置新生代中,eden空间和s0空间的比例关系,其中s0与s1空间大小是相同的,只能也是一样的,并在MinorGC之后会互换角色,因此值为eden/s0 = eden/s1
新生代与老年代:-XX:NewRatio,用来设置老年代/新生代之间的比例

survivior使用率:-XX:TargetSurvivorRatio,设置survivior区的可使用率,当使用率达到这个数值时,会将对象送入老年代

新生代存活次数:-XX:MaxTenuringThreshold,在新生代中对象存活次数(经过Minor GC的次数)后仍然存活,就会晋升到老年代

直接进入老年代:-XX:PretenureSizeThreshold,设置大对象直接进入老年代的阈值,当对象的大小超过这个值时直接在老年代分配

堆空闲比例:-XX:MinHeapFreeRatio与-XX:MaxHeapFreeRatio,设置堆空间的对最小/最大空闲比例。当堆空间的空闲内存大于/小于对应值时,JVM便会扩展/压缩堆空间大小

元空间空闲比例:-XX:MinMetaspaceFreeRatio与-XX:MaxMetaspaceFreeRatio,设置最小/最大的Metaspace剩余空间容量的百分比,Metaspace GC之后,用来控制扩展/压缩元空间大小

可以使用-XX:PrintGCDetails来打印出堆的实际大小

7、垃圾回收

算法与思想

引用计数法:最经典也是最古老的一种垃圾收集方法。引用计数器,只需要为每个对象配备一个整形的计数器即可,但是引用计数器有个严重的问题,即无法处理循环引用问题。因此在Java的垃圾回收器中没有使用这种算法

标记-清除算法(Mark-Sweep):是现代垃圾回收算法的思想基础。将垃圾回收分为两个阶段,标记阶段和清除阶段。一种可行的实现是,在标记阶段通过根节点标记所有从根节点开始可达的对象,然后在清除节点清除所有未被标记的垃圾对象。标记-清除算法可能产生的最大问题就是空间碎片,因为回收后的空间是不连续的

复制算法(Copying):与标记-清除算法相比,复制算法是一种相对高效的回收方法。核心思想是将原有的内存空间分为两块,每次只使用一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存中,之后清除正在使用的内存块的所有对象,交换两个内存的角色,完成垃圾回收。如果系统中垃圾对象多,复制算法需要复制的对象数量不会太多,又由于对象被复制到新的内存空间,所以确保没有碎片。但是复制算法缺点是将系统内存折半,因此单纯的复制算法难以让人接受

在Java的新生代串行垃圾回收期中,使用了复制算法的思想,将新生代分为eden空间、from空间和to空间3个部分。其中from和to空间可以视为用于复制的两块大小相同、地位相等且可角色互换的空间块。复制算法比较适合新生代,因为在新生代中,垃圾对象通常会多于存活对象,复制算法的效果会比较好

标记-压缩算法(Mark-Compact):复制算法的高效建立在存活对象少、垃圾对象多的场景下,因此适合年轻代。但是对于老年代,更常见的是大部分对象都是存活对象,因此基于这种特性,需要使用新的算法。标记-压缩算法在标记-清除的基础上做了一些优化。与标记-清除一样从根节点开始对所有可达对象做一次标记,但是之后清理未标记对象时,将存活对象压缩到内存的一端,然后清理边界外的所有空间。这种方法既避免碎片的产生,又不需要两块相同的内存空间,因此性价比较高

增量算法(Incremental Collecting):对大部分垃圾回收算法来说,在回收过程应用程序都处于stop the world状态,所有线程都会挂起,暂停一切正常工作等待垃圾回收完成。如果回收时间很长就会严重影响用户体验和系统稳定性。增量算法的基本思想是,如果一次性将所有垃圾进行处理,需要造成系统长时间停顿,那么就可以让垃圾回收线程和应用程序线程交替执行。每次垃圾收集线程只收集一小块的内存空间,然后切换到应用程序线程,如此反复直到垃圾收集完成。这种方式下,能间断性地执行应用程序代码,所以能减少系统停顿时间,但是由于线程切换和上下文转换的消耗,会使得垃圾回收总成本上升,造成系统吞吐量下降

分代(Generational Collecting):所有的算法都无法完全替代其他算法,都具有独特的优势和特点,因此根据垃圾回收对象的特性,使用合适的算法才是明智之举。分代就是基于这种思想,将内存区间根据对象的特点分成几块,根据每块内存的特点选择不同的回收算法,提高回收效率。几乎所有的垃圾回收器都区分年轻代和老年代

分区算法(Region):分代算法是按照对象的生命周期长短划分成两个部分,分区算法是将整个堆空间划分为连续的不同小区间,每个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间,一般来说堆空间越大,相同条件下一次GC所需的时间就越长,那么每次合理回收若干个小区块,从而可以减少一次GC所产生的停顿

分类

按线程数分,可以分为串行垃圾回收器和并行垃圾回收器;按工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器;按碎片处理方式,可以分为压缩式垃圾回收器和非压缩式垃圾回收器;按工作的内存区间,可以分为新生代垃圾回收器和年老代垃圾回收器

评价指标:
1、吞吐量:指在应用程序的生命周期内,应用程序所花费的时间和系统总运行时间(应用耗时+GC耗时)的比值
2、垃圾回收器负载:与吞吐量相反,是垃圾回收器耗时与系统运行总时间的比值
3、停顿时间:指垃圾回收器正在运行时,应用程序的暂停时间
4、垃圾回收频率:指垃圾回收器多长时间会运行一次
5、反应时间:指当一个对象成为垃圾后,多长时间它所占用的内存会被释放
6、堆分配:不同垃圾回收器对堆内存的分配方式可能不同,一个良好的垃圾回收期需要有一个合理的堆内存区间划分
JVM调优笔记:认识JVM内存模型(jdk1.8)_第6张图片

新生代串行收集器 Serial

是最古老的一种,也是JDK最基本的垃圾收集器之一。主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它是独占式的垃圾回收。因此垃圾收集器运行时,应用所有线程都停止工作进行等待。虽然如此,但是串行收集器是一个成熟、经过长时间考验的极为高效的收集器。新生代串行收集器使用复制算法,实现相对简单,逻辑处理高效,而且没有线程切换的开销,在诸如单CPU或较小应用内存等硬件的平台,它的性能可以超过并行回收器和并发回收器

-XX:+UseSerialGC,指定使用新生代串行收集器和年老代串行收集器。当JVM在Client模式下时,它是默认的垃圾收集器

老年代串行收集器 Serial Old

采用的是标记-压缩算法,和新生代串行收集器一样,是串行的、独占式的垃圾回收器。由于老年代垃圾回收器通常比新生代垃圾回收器使用更长的时间,因此在堆空间较大的应用中,一旦老年串行收集器启动,应用程序将会因此停顿几秒甚至更长。虽然如此,作为老牌的垃圾回收器,老年代串行收集器可以和多种新生代回收器配合使用,同时它也作为CMS回收器的备用回收器

-XX:+UseSerialGC,新生代、老年代都使用串行回收器
-XX:+UseParNewGC,新生代使用并行收集器,老年代使用串行收集器
-XX:+UseParallelGC,新生代使用并行回收收集器,老年代使用串行收集器

新生代并行收集器 ParNew

并行收集器是工作在新生代的垃圾收集器,它只是简单地将串行回收器多线程化,它的回收策略、算法以及参数和串行回收器一样,并行回收器也是独占式的回收器。但由于多线程进行垃圾回收,因此在并发能力较强的CPU上,暂停时间短于串行回收器

-XX:ParallelGCThreads,指定工作时的线程数量,一般与CPU数量相当
-XX:+UseParNewGC,新生代使用并行收集器,老年代使用串行收集器
-XX:+UseConcMarkSweepGC,新生代使用并行收集器,老年代使用CMS

新生代并行回收收集器 Parallel Scavenge

新生代并行回收器也是使用复制算法的收集器,表面上和并行收集器一样,都是多线程、独占式的收集器。但是它有一个重要的特点,就是它非常关注系统的吞吐量。除此之外,并行回收收集器与并行收集器另一个不同之处在于,它还支持一种自适应的GC调节策略,这种模式下,新生代的大小、eden和survivor的比例、晋升老年代的对象年龄等参数都会被自动调整,已达到堆大小、吞吐量和停顿时间的平衡点,在手动调优比较难的场合可以使用这种自适应的方式

-XX:+UseParallelGC,新生代使用并行回收收集器,老年代使用串行收集器
-XX:+UseParallelOldGC,新生代和老年代都使用并行回收收集器
-XX:MaxGCPauseMillis,设置最大垃圾收集停顿时间,值为大于0的整数。收集器在工作时,会调整Java堆大小或其他参数,尽可能把停顿时间控制在范围内。如果把值设置过小,可能JVM会使用很小的堆(小堆回收更快),这将导致垃圾回收变得频繁,反而增加总时间,降低吞吐量
-XX:GCTimeRatio,设置吞吐量大小,值是0~100之间的整数
-XX:+UseAdaptiveSizePolicy,打开自适应GC策略

老年代并行回收收集器 Parallel Old

老年代并行回收收集器也是一种多线程并发的收集器,和新生代并行回收收集器一样,也是一种关注吞吐量的收集器。老年代并行回收收集器使用标记-压缩算法

-XX:+UseParallelOldGC,新生代和老年代都使用并行回收收集器

CMS收集器

与并行回收收集器不同,CMS收集器主要关注系统停顿时间,是一种以获取最短回收停顿时间为目标的收集器。是Concurrent Mark Sweep的缩写,意为并发标记清除,因此它使用的是标记-清除算法,同时也是一个使用多线程并行回收的垃圾收集器。只会回收老年代和永久带(1.8开始为元数据区,需要设置CMSClassUnloadingEnabled),不会收集年轻代。CMS收集器的工作过程略显复杂,主要步骤有:初始标记、并发标记、重新标记、并发清除和并发重置(回收完成后重新初始化CMS数据结构和数据)。其中初始标记和重新标记是独占系统资源的,而并发标记、并发清除和并发重置是可以和用户线程一起并发执行的。因此不算是独占式的,可以在应用程序运行过程中进行垃圾回收

-XX:+UseConcMarkSweepGC,使用CMS收集器
-XX:ParallelGCThreads,设置CMS的线程数量
-XX:CMSInitiatingOccupancyFraction,设置老年代使用率回收阈值,因为CMS回收时,应用持续工作,因此会有新的垃圾产生,而这些垃圾在CMS回收过程中无法清除,因此CMS回收过程中还需要保证有足够的内存可用,这样就不等待堆内存饱和再进行回收,而是当堆内存使用率达到某一阈值后就进行回收
-XX:UseCMSCompactAtFullCollection,开关使CMS在垃圾收集完成后,进行一次内存碎片整理,内存碎片整理不是并发进行的
-XX:CMSFullGCsBeforeCompaction,指定进行多少次CMS回收后进行一次内存压缩

G1收集器

是目前最新的垃圾回收器,目标是作为一款服务端的垃圾收集器,因此在吞吐量和停顿控制上,预期要优于CMS收集器。与CMS相比,G收集器是基于标记-压缩算法的,因此不会产生空间碎片,G1收集器还可以进行非常精确的停顿控制。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。G1的运行步骤有1、初始标记;2、并发标记;3、最终标记;4、筛选回收

-XX:+UseG1GC,启用G1回收器
-XX:+UnlockExperimentalVMOptions,允许使用实验性参数
-XX:GCPauseIntervalMillis,设置停顿间隔时间
-XX:MaxGCPauseMillis,设置最大垃圾收集停顿时间

8、常见调优方法

将新对象预留在新生代:由于Full GC的成本要远远高于Minor GC,因此尽可能将对象分配在新生代是一项明智的做法。虽然大部分情况下,JVM会尝试在eden区分配对象,但是由于空间紧张等问题,很可能不得不将部分年轻代对象提前先老年代压缩。因此在JVM参数调优时,可以为应用分配一个合理的新生代空间,最大限度避免新对象直接进入老年代的情况

-Xmn,-XX:NewRatio,-XX:SurvivorRatio

大对象进入老年代:大部分情况下将对象分配到新生代是合理的,但是对于大对象,很可能扰乱新生代GC,并破坏新生代原有的对象结构。因为尝试在新生代分配大对象,可能导致空间不足,JVM不得不将新生代中的年轻对象移动到年老代。因为大对象占用空间大,所以可能需要移动大量小的年轻对象进入老年代,这对GC来说是相当不利的。但是如果大对象是个短命的对象,这种情况出现比较频繁,那对GC也是一种灾难,扰乱了分代内存回收的思想,因此应该尽可能避免使用短命的大对象

-XX:PretenureSizeThreshold

设置对象进入老年代的年龄:对象在eden区经过一次GC后还存活,就移到survivior区并年龄加1,直到达到阈值进入老年代。默认值是15,但不意味着必须要达到这个年龄才能进入老年代。实际上,对象实际进入老年代的年龄是虚拟机在运行时根据内存使用情况动态计算的,参数只是可以指定阈值年龄的最大值,即实际晋升年龄取阈值与动态计算年龄中的最小值

-XX:MaxTenuringThreshold

稳定与震荡堆大小:稳定的堆大小对垃圾回收是有利的,获得稳定堆大小的方式就是将-Xms和-Xmx设为大小一致。这样运行时堆大小恒定,稳定的堆空间可以减少GC次数。但是不稳定的堆也并不是毫无用处,稳定的堆虽然减少GC次数,但是也可能增加每次GC的时间。当系统不需要大内存时,让堆大小在一个区间中震荡,压缩堆空间,使GC应对一个较小的堆,可以加快单次GC速度

-XX:MinHeapFreeRatio,-XX:MaxHeapFreeRatio

吞吐量优先案例:-Xms与-Xmx一致,使用-XX:+UseParallelGC新生代使用并行回收收集器并设置线程数,-XX:UseParallelOldGC老年代也使用并行回收收集器

使用大页案例:-XX:LargePageSizeInBytes设置大页的大小,使用大的内存分页可以增强CPU的内存寻址能力,从而提高系统性能

降低停顿案例:首先考虑使用关注系统停顿时间的CMS回收器,其次考虑减少Full GC次数,应尽可能将对象预留在新生代

9、其他实用JVM参数

JIT编译参数,JVM的JIT编译器,可以在运行时将字节码编译为本地代码,从而提高函数的执行效率,JIT编译会花费一定时间,但未来运行中这些时间会被赚回来

-XX:CompileThreshold,JIT编译阈值,当函数调用次数超过该值,JIT就将字节码编译为本地机器码。在Client模式默认1500,Server模式下默认10000
-XX:+CITime,打印JIT编译的耗时
-XX:+PrintCompilation,打印JIT编译的信息

堆快照

-XX:+HeapDumpOnOutOfMemoryError,将当前的堆信息保存到文件中,对于排查问题是很有帮助的
-XX:HeapDumpPath,指定堆快照保存位置

取得GC信息

-verbose:gc-XX:+PrintGC,获取简要的GC信息
-XX:+PrintGCDetails,获取详细GC信息

类和对象跟踪

-XX:+TraceClassLoading,跟踪类加载信息
-XX:+TraceClassUnloading,跟踪类卸载信息
-verbose:class,同时打开类加载和类卸载信息

控制GC

-XX:+DisableExplicitGC,禁止在程序中使用System.gc()触发Full GC
-Xnoclassgc,不需要回收类
-Xincgc,开启后系统会进行增量式的GC,增量式GC使用特定算法让GC线程与应用线程交叉执行,从而减小停顿时间

Solaris下线程控制

-XX:+UseBoundThreads,绑定所有用户线程到内核线程,减少线程进饥饿状态的次数
-XX:+UseLWPSynchronization,使用内核线程代替线程同步
-XX:+UseVMInterruptibleIO,允许运行时中断线程

使用大页

-XX:+UseLargePages,启用大页
-XX:LargePageSizeInBytes,设置大页的大小

你可能感兴趣的:(JVM)