为网站网站做宣传,wordpress 首页折叠,服装网站策划书,三亚做网站那家效果好2019独角兽企业重金招聘Python工程师标准 ##5.1 Java虚拟机内存模型## Java虚拟机内存模型是Java程序运行的基础。JVM将其内存数据分为程序计数器#xff0c;虚拟机栈#xff0c;本地方法栈#xff0c;Java堆和方法区等部分。 程序计数器#xff1a;用于存放… 2019独角兽企业重金招聘Python工程师标准 ##5.1 Java虚拟机内存模型## Java虚拟机内存模型是Java程序运行的基础。JVM将其内存数据分为程序计数器虚拟机栈本地方法栈Java堆和方法区等部分。 程序计数器用于存放下一条运行的指令 虚拟机栈和本地方法栈用于存放函数调用堆栈信息 Java堆用于存放Java程序运行时所需的对象等数据 方法区用于存放程序的类元数据信息 ###5.1.1 程序计数器### 程序计数器是一块很小内存空间。由于Java是支持线程的语言当线程数量超过CPU数量时线程之间根据时间片轮询抢夺CPU资源。对于单核CPU而言每一时刻只能有一个线程在运行而其他线程必须被切换出去。为此每一个线程都必须用一个独立的程序计数器用于记录下一条要运行的指令。各个线程之间的计数器互不影响独立工作是一块线程私有的内存空间。 如果当前线程正在执行一个Java方法则程序计数器记录正在执行的Java字节码地址如果当前线程正在执行一个Native方法程序计数器为空。 ###5.1.2 Java虚拟机栈### Java虚拟机栈也是线程私有的内存空间它和Java线程在同一时间创建它保存方法的局部变量部分结果并参与方法的调用与返回。 Java虚拟机规范允许Java栈的大小是动态的或者是固定的。在Java虚拟机规范中定义了两种异常与栈空间有关StackOverflowError和OutOfMemoryError。如果线程在计算过程中请求的栈深度大于最大可用的栈深度则抛出StackOverflowError如果Java栈可以动态扩展而在扩展栈的过程中没有足够的内存空间来支持栈的扩展则抛出OutOfMemoryError。 在HotSpot虚拟机中可以使用-Xss参数来设置栈的大小。栈的大小直接决定了函数调用的可达深度。 虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中存放了方法的局部变量表操作数栈动态连接方法和返回地址等信息。每一个方法的调用都伴随着栈帧的入栈操作。相应地方法的返回则表示栈帧的出栈操作。如果方法调用时方法的参数和局部变量相对较多那么栈帧中的局部变量表就会比较大栈帧会膨胀以满足方法调用所需传递的信息。因此单个方法调用所需的栈空间大小也会比较多。 注意函数嵌套调用的次数由栈的大小决定。栈越大函数嵌套调用次数越多。对一个函数而言它的参数越多内部局部变量越多它的栈帧就越大其嵌套调用次数就会越少。 在栈帧中与性能调优关系最为密切的部分就是局部变量表。局部变量表用于存放方法的参数和方法内部的局部变量。局部变量表以“字”为单位进行内存的划分一个字为32位长度。对于long和double型的变量则占用2个字其余类型使用1个字。在方法执行时虚拟机使用局部变量表完成方法的传递对于非static方法虚拟机还会将当前对象this作为参数通过局部变量表传递给当前方法。 注意使用jclasslib工具可以深入研究Class类文件的结构有助于读者对Java语言更深入的了解。 局部变量表的字空间是可以重用的。因为在一个方法体内局部变量的作用范围并不一定是整个方法体。 // 最大局部变量表容量213
public void test1() {{long a 0;}long b 0;
}
// 最大局部变量表容量2213
public void test2() {long a 0;long b 0;
}局部变量表的字对系统GC也有一定影响。如果一个局部变量被保存在局部变量表中那么GC根就能引用到这个局部变量所指向的内存空间从而在GC时无法回收这部分空间。 // GC无法回收因为b还在局部变量表中
public static void test1() {{byte[] b new byte[6*1024*1024];}System.gc();System.out.println(first explict gc over);
}
// GC可以回收因为赋值为null将销毁局部变量表中的数据
public static void test1() {{byte[] b new byte[6*1024*1024];b null;}System.gc();System.out.println(first explict gc over);
}// GC可以回收因为变量a复用了b的字GC根无法找到b
public static void test1() {{byte[] b new byte[6*1024*1024];}int a 0;System.gc();System.out.println(first explict gc over);
}// GC无法回收因为变量a复用了c的字b仍然存在
public static void test1() {{int c 0;byte[] b new byte[6*1024*1024];}int a 0; // 复用c的字System.gc();System.out.println(first explict gc over);
}// GC可以回收因为变量d复用了b的字
public static void test1() {{int c 0;byte[] b new byte[6*1024*1024];}int a 0; // 复用c的字int d 0; // 复用b的字System.gc();System.out.println(first explict gc over);
}在这个函数体内即使在变量b失效后又未能定义足够多的局部变量来复用该变量所占的字变量b仍会在该栈帧的局部变量表中。因此GC根可以引用到该内存块阻碍了其回收过程。在这种环境下手工对要释放的变量赋值为null是一种有效的做法。 当方法一结束该方法的栈帧就会被销毁即栈帧中的局部变量表也被销毁。 注意局部变量表中的字可能会影响GC回收。如果这个字没有被后续代码复用那么它所引用的对象不会被GC释放。 ###5.1.3 本地方法栈### 本地方法栈和Java虚拟机栈的功能很相似Java虚拟机栈用于管理Java函数的调用而本地方法栈用于管理本地方法的调用。本地方法并不是用Java实现的而是使用C语言实现的。 和虚拟机栈一样它也会抛出StackOverflowError和OutOfMemoryError。 ###5.1.4 Java堆### Java堆分为新生代和老年代两个部分新生代用于存放刚刚产生的对象和年轻的对象如果对象一直没有被回收生存得足够长老年对象就会被移入老年代。新生代又可进一步分为edensurvivor0survivor1。eden意义为伊甸园即对象的出生地大部分对象刚刚建立时通常会存放在这里。s0和s1为Survivor空间直译为幸存者也就是说存放其中的对象至少经历了一次垃圾回收并得以幸存。如果在幸存区的对象到了指定年龄仍未被回收则有机会进入老年代。 package com.king.gc;/*** -XX:PrintGCDetails -XX:SurvivorRatio8* -XX:MaxTenuringThreshold15 -Xms40M -Xmx40M -Xmn20M* author taomk* version 1.0* since 15-5-10 上午9:46*/
public class TestHeapGC {public static void main(String [] args) {byte[] b1 new byte[1024 * 1024 / 2];byte[] b2 new byte[1024 * 1024 * 8];b2 null;b2 new byte[1024 * 1024 * 8];System.gc();}
}###5.1.5 方法区### 方法区也是JVM内存区中非常重要的一块内存区域与堆空间类似它也是被JVM中所有线程共享的。方法区主要保存的信息是类的元数据。 方法区中最为重要的是类的类型信息常量池域信息方法信息。类型信息包括类的完整名称父类的完整名称类型修饰符和类型的直接接口类表常量池包括这个类方法域等信息所引用的常量信息域信息包括域名称域类型和域修饰符方法信息包括方法名称返回类型方法参数方法修饰符方法字节码操作数栈和方法栈帧的局部变量区大小及异常表。总之方法区内保持的信息大部分来自于class文件是Java应用程序运行必不可少的重要数据。 在HotSpot虚拟机中方法区也称为永久区是一块独立于Java堆的内存空间。虽然叫做永久区但是在永久区中的对象同样也是可以被GC回收的。只是对于GC的表现也和Java堆空间略有不同。对永久代GC的回收通常主要从两个方面分析1GC对永久代常量池的回收2GC对永久代类元数据的回收。 **GC对永久代常量池的回收**只要常量池中的常量没有被任何地方引用就可以被回收。String.intern()方法的含义是如果常量池中已经存在当前String则返回池中的对象如果常量池中不存在当前String的对象则先将String加入常量池并返回池中的对象引用。 **GC对永久代类元数据的回收**如果虚拟机确认该类的所有实例已经被回收并且加载该来的ClassLoader实例也已经被回收GC就有可能回收该类型。 ##5.2 JVM内存分配参数## ###5.2.1 设置最大堆内存### 最大堆内存可以用-Xmx参数指定。最大堆指的是新生代和老年代的大小之和的最大值它是Java应用程序的上限。 ###5.2.2 设置最小堆内存### 最小堆内存可以用-Xms参数指定。也就是JVM启动时所占据的操作系统内存大小。 如果-Xms的数值较小那么JVM为了保证系统尽可能地在指定内存范围内运行就会更加频繁地进行GC操作以释放失效的内存空间从而会增加Minor GC和Full GC的次数对系统性能产生一定影响。 注意JVM会试图将系统内存尽可能限制在-Xms中因此当内存实际使用量触及-Xms指定的大小时会触发Full GC。因此把-Xms值设置为-Xmx时可以在系统运行初期减少GC的次数和耗时。 ###5.2.3 设置新生代### 参数-Xmn用于设置新生代的大小。新生代的大小一般设置为整个堆空间的1/4到1/3左右。设置-Xmn的效果等同于设置了相同的-XX:NewSize和-XX:MaxNewSize。若设置不同的-XX:NewSize和-XX:MaxNewSize可能会导致内存震荡从而产生不必要的系统开销。 ###5.2.4 设置持久代### 持久代方法区不属于堆的一部分。在HotSpot虚拟机中使用-XX:MaxPermSize可以设置持久代的最大值使用-XX:PermSize可以设置持久代的初始大小。持久代的大小直接决定了系统可以支持多少个类定义和多次常量。 一般来说-XX:MaxPermSize设置为64MB已经可以满足绝大部分应用程序正常工作。如果依然出现永久区溢出可以将-XX:MaxPermSize设置为128MB。 ###5.2.5 设置线程栈### 线程栈是线程的一块私有空间。在JVM中可以使用-Xss参数设置线程栈的大小。 在线程中进行局部变量分配函数调用时都需要在栈中开辟空间。如果栈的空间分配太小那么线程在运行时可能没有足够的空间分配局部变量或者达不到足够的函数调用深度导致程序异常退出如果栈空间过大那么开设线程所需的内存成本就会上升系统所能支持的线程总数就会下降。 由于Java堆也是向操作系统申请内存空间的因此如果堆空间过大就会导致操作系统可用于线程栈的内存减少从而间接减少程序所能支持的线程数量。当系统由于内存空间不够而无法创建新的线程时会抛出OOM异常如下 java.lang.OutOfMemoryError: unable to create new native thread 根据以上可知这并不是由于堆内存不够而导致的OOM而是因为操作系统内存减去堆内存后剩余的系统内存不足而无法创建新的线程。在这种情况下可以尝试减少堆内存以换取更多的系统空间来解决这个问题。 ###5.2.6 堆的比例分配### -XX:SurvivorRatio可以设置eden区与survivor区的比例 -XX:NewRatio可以设置老年代与新生代的比例 ###5.2.7 堆分配参数总结### -Xms设置Java应用程序启动时的初始堆大小 -Xmx设置Java应用程序能获得的最大堆大小 -Xss设置线程栈的大小 -XX:MinHeapFreeRatio设置堆空间最小空闲比例。当堆空间的空闲内存小于这个数值时JVM便会扩展堆空间 -XX:MaxHeapFreeRatio设置堆空间最大空闲比例。当堆空间的空闲内存大于这个数值时便会压缩堆空间得到一个较小的堆 -XX:NewSize设置新生代的初始大小 -XX:MaxNewSize设置新生代能获得的最大大小 -XX:NewRatio设置老年代与新生代的比例它等于老年代大小除以新生代大小 -XX:SurvivorRatio设置新生代中eden区与survivor区的比例 -XX:PermSize设置永久代的初始大小 -XX:MaxPermSize设置永久代能获得的最大大小 -XX:TargetSurvivorRatio设置Survivor区的可使用率。当Survivor区的空间使用率达到这个数值时会将对象送入老年代 ##5.3 垃圾收集基础## Java语言的一大特点就是可以进行自动垃圾回收处理而无需开发人员过于关注系统资源尤指内存资源的释放情况。 ###5.3.1 垃圾收集的作用### 拥有垃圾收集器可以说是Java语言与C语言的一项显著区别。在C语言中程序员必须小心谨慎地处理每一项内存分配且内存使用完后必须手工释放曾经占用的内存空间。当内存释放不够完全时即存在分配但永不释放的内存块就会引起内存泄漏严重时会导致程序崩溃。 ###5.3.2 垃圾回收算法与思想### 1. 引用计数法 引用计数法是最经典也是最古老的一种垃圾收集算法其实现也非常简单只需要为每个对象配备一个整型的计数器即可。但是引用计数器有一个严重的问题即无法处理循环引用的情况。因此在Java的垃圾回收器中没有使用这种算法。 2. 标记—清除算法 标记—清除算法是现代垃圾回收算法的思想基础。标记—清除算法将垃圾回收分为两个阶段标记阶段和清除阶段。在标记阶段首先通过根节点标记所有从根节点开始的可达对象。因此未被标记的对象就是未被引用的垃圾对象。然后在清除阶段清除所有未被标记的对象。标记—清除算法可能产生的最大问题就是空间碎片。 该算法回收后的空间是不连续的。在对象的堆空间分配过程中尤其是大对象的内存分配不连续的内存空间的工作效率要低于连续的空间。因此这也是该算法的最大缺点。 3. 复制算法 与标记—清除算法复制算法是一种相对高效的回收方法。它的核心思想是将原有的内存空间分为两块每次只使用其中一块在垃圾回收时将正在使用的内存中的存活对象复制到未使用的内存块中之后清除正在使用的内存块中的所有对象交换两个内存块的角色完成垃圾回收。 如果系统中的垃圾对象很多复制算法需要复制的存活对象数量并不会太大。因此在真正需要垃圾回收的时刻复制算法的效率是很高的。又由于对象是在垃圾回收过程中统一被复制到新的内存空间中因此可确保回收后的内存空间是没有碎片的。虽然有以上两优点但是复制算法的代价缺点是将系统内存折半因此单纯的复制算法也很难让人接受。 4. 标记—压缩算法 复制算法的高效性是建立在存活对象少垃圾对象多的前提下。这种情况在年轻代经常发生但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法由于存活对象较多复制的成本也将很高。因此基于老年代垃圾回收的特性需要使用新的算法。 标记—压缩算法是一种老年代的回收算法它在标记—清除算法的基础上做了一些优化。和标记—清除算法一样标记—压缩算法也首先需要从根节点开始对所有可达对象做吃一次标记。之后清理未标记的对象而是将所有的存活对象压缩到内存的一端。之后清理边界外所有的空间。这种方法既避免了碎片的产生又不需要两块相同的内存空间因此其性价比较高。 5. 增量算法 对大部分垃圾回收算法而言在垃圾回收的过程中应用将处于一种Stop the World的状态。在Stop the World状态下应用所有的线程都会挂起暂停一切正常的工作等待垃圾回收的完成。如果垃圾回收时间很长应用会被挂起很久将严重影响用户体验或者系统的稳定性。 增量算法的基本思想是如果一次性将所有的垃圾进行处理需要造成系统长时间的停顿那么就可以让垃圾收集线程和应用程序交替执行。每次垃圾收集线程只收集一小片区域的内存空间接着切换到应用程序线程。以此反复直到垃圾收集完成。使用这种方式由于在垃圾回收的过程中间断性地还执行了应用程序代码所以能减少系统的停顿时间。但是因为线程切换和上下文转换的消耗会使得垃圾回收的总体成本上升造成系统吞吐量的下降。 6. 分代 分代算法就是基于这种思想它将内存区间根据对象的特点分成几块根据每块内存区间的特点使用不同的收集算法以提高垃圾回收的效率。该算法被现有的HotSpot虚拟机广泛使用几乎所有的垃圾回收器都区分年轻代和老年代。 ###5.3.3 垃圾收集器的类型### 按线程数分串行垃圾回收器和并行垃圾回收器。串行垃圾回收器一次只使用一个线程进行垃圾回收并行垃圾回收器将开启多个线程同时进行垃圾回收。在并行能力较强的CPU上使用并行垃圾回收器可以缩短GC的停顿时间。 按工作模式分并发式垃圾回收器和独占式垃圾回收器。并发式垃圾回收器与应用程序线程交替执行以尽可能减少应用程序的停顿时间独占式垃圾回收器一旦运行就停止应用程序中的其他所有线程直到垃圾回收过程完全结束。 按碎片处理方式分压缩式垃圾回收器和非压缩式垃圾回收器。压缩式垃圾回收器会在回收完成后对存活对象进行压缩管理消除回收后的碎片非压缩式的垃圾回收器不进行这步操作。 按工作区间分新生代垃圾回收器和老年代垃圾回收器。顾名思义新生代垃圾回收器只在新生代工作老年代垃圾回收器则工作在老年代。 ###5.3.4 评价GC策略的指标### 吞吐量指在应用程序的生命周期内应用程序所花费的时间和系统总运行时间的比值。系统总运行时间应用程序耗时GC耗时。如果系统运行了100minGC耗时1min那么系统的吞吐量就是100-1/ 10099%。 垃圾回收器负载和吞吐量相反垃圾回收器负载指垃圾回收器耗时与系统运行总时间的比值。 停顿时间指垃圾回收器正在运行时应用程序的暂停时间。对于独占回收器而言停顿时间可能会比较长。使用并发的回收器时由于垃圾回和应用程序交替执行程序的停顿时间会变短但是由于其效率很可能不如独占回收器故系统的吞吐量可能会降低。 垃圾回收频率指垃圾回收器多长时间会运行一次。一般来说对于固定的应用而言垃圾回收的频率应该是越低越好。通常增大堆空间可以有效降低垃圾回收发生的频率但是可能会增加回收产生的停顿时间。 反应时间指当一个对象称为垃圾后多长时间内它所占据的内存空间会被释放。 堆分配不同的垃圾回收器对堆内存的分配方式可能是不同的。一个良好的垃圾收集器应该有一个合理的堆内存区间划分。 通常情况下很难让一个应用程序在所有的指标上都达到最优。因此只能根据应用本身的特点尽可能使垃圾回收器配合应用程序的工作。 ###5.3.5 新生代串行收集器### 串行收集器是所有垃圾收集器中最古老的一种也是JDK中最基本的垃圾收集器之一。串行收集器主要有两个特点第一它仅仅使用单线程进行垃圾回收第二它是独占式的垃圾回收。 在串行收集器进行垃圾回收时Java应用程序中的线程都需要暂停等待垃圾回收的完成。这种现象称为“Stop the World”。虽然如此串行收集器却是一个成熟经过长时间生产环境考验的极为高效的收集器。新生代串行处理器使用复制算法实现相对简单逻辑处理特别高效且没有线程切换的开销。在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合它的性能表现可以超越并行收集器和并发收集器。 在HotSpot虚拟机中使用-XX:UseSerialGC参数可以指定使用新生代串行收集器和老年代串行收集器。当JVM在Client模式下运行时它是默认的垃圾收集器。 ###5.3.6 老年代串行收集器### 老年代串行收集器使用的是标记—压缩算法。和新生代串行收集器一样它也是一个串行的独占式的垃圾回收器。若要启用老年代串行收集器可以尝试使用以下参数 **-XX:UseSerialGC**新生代老年代都使用串行收集器 **-XX:UseParNewGC**新生代使用并行收集器老年代使用串行收集器 **-XX:UseParallelGC**新生代使用并行收集器老年代使用串行收集器 ###5.3.7 并行收集器### 并行收集器是工作在新生代的垃圾收集器它只是简单地将串行收集器多线程化。它的回收策略算法以及参数和串行收集器一样。并行收集器也是独占式的回收器在收集过程中应用程序会全部暂停。但由于并行收集器使用多线程进行垃圾回收因此在并发能力比较强的CPU上它产生的停顿时间要短于串行回收器而在单CPU或者并发能力较弱的系统中并行收集器的效果不会比串行收集器好由于多线程的压力它的实际表现很可能比串行收集器差。 开启并行收集器可以使用以下参数 **-XX:UseParNewGC**新生代使用并行收集器老年代使用串行收集器 **-XX:UseConcMarkSweepGC**新生代使用并行收集器老年代使用CMS 并行收集器工作时的线程数量可以使用-XX:ParallelGCThreads参数指定。一般最好与CPU数量相当避免过多的线程数影响垃圾收集性能。在默认情况下当CPU数量小于8个时ParallelGCThreads的值等于CPU数量当CPU数量大于8个时ParallelGCThreads的值等于3[(5*CPU_Count)/8]。 ###5.3.8 新生代并行回收收集器### 新生代并行回收收集器也是使用复制算法的收集器。从表面上看它和并行收集器一样都是多线程独占式的收集器。但是并行回收收集器有一个重要的特点它非常关注系统的吞吐量。 新生代并行回收收集器可以使用以下参数启用 -XX:UseParallelGC新生代使用并行回收收集器老年代使用串行收集器。 -XX:UseParallelOldGC新生代和老年代都使用并行回收收集器。 并行回收收集器提供了两个重要的参数用于控制系统的吞吐量 1-XX:MaxGCPauseMillis设置最大垃圾收集停顿时间它的值是一个大于0的整数。收集器在工作时会调整Java堆大小或者其他一些参数尽可能地把停顿时间控制在MaxGCPauseMillis以内。如果希望减少停顿时间而把这个值设得很小为了达到预期的停顿时间JVM可能会使用一个较小的堆一个小堆比一个大堆回收快而这将导致垃圾回收变得很频繁从而增加了垃圾回收总时间降低了吞吐量。 2-XX:GCTimeRatio设置吞吐量大小它的值一个0~100之间的整数。假设GCTimeRatio的值为n那么系统将花费不超过1/1n的时间用于垃圾收集。比如GCTimeRatio等于19默认值则系统用于垃圾收集的时间不超过1/1195%。默认情况下它的取值是99即不超过1/1991%的时间用于垃圾收集。 除此以外并行回收收集器与并行收集器另一个不同之处在于它还有一种自适应的GC调节策略。使用-XX:UseAdaptiveSizePolicy可以打开自适应GC策略。在这种模式下新生代的大小eden和survivor的比例晋升老年代的对象年龄等参数会被自动调整以达到在堆大小吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合可以直接使用这种自适应的方式仅指定虚拟机的最大堆目标吞吐量GCTimeRatio和最大停顿时间MaxGCPauseMillis让虚拟机自己完成调优工作。 ###5.3.9 老年代并行回收收集器### 老年代并行回收收集器也是一种多线程并行的收集器。和新生代并行回收收集器一样它也是一种关注吞吐量的收集器。老年代并行回收收集器使用标记—压缩算法。 使用-XX:UseParallelOldGC可以在新生代和老年代都使用并行回收收集器这是一对非常关注吞吐量的垃圾收集器组合在对吞吐量敏感的系统中可以考虑使用。参数-XX:ParallelGCThreads也可以用于设置垃圾回收时的线程数量。 ###5.3.10 CMS收集器### 与并行回收收集器不同CMS收集器主要关注于系统停顿时间。CMS是Concurrent Mark Sweep的缩写意味并发标记清除。从名称上就可以得知它使用的是标记—清除算法同时它又是一个使用多线程并行回收的垃圾收集器。 CMS收集器的工作过程与其他垃圾收集器相比略显复杂。CMS工作时主要步骤有初始标记并发标记重新标记并发清除和并发重置。其中初始标记和重新标记是独占系统资源的而并发标记并发清除和并发重置是可以和用户线程一起执行的。因此从整体上说CMS收集不是独占式的它可以在应用程序运行过程中进行垃圾回收。 根据标记—清除算法初始标记并发标记和重新标记都是为了标记出需要回收的对象。并发清理则是在标记完成后正式回收垃圾对象并发重置是指在垃圾回收完成后重新初始化CMS数据结构和数据为下一次垃圾回收做好准备。并发标记并发清理和并发重置都是可以和应用程序线程一起执行的。 CMS收集器在其主要的工作阶段虽然没有暴力地彻底暂停应用程序线程但是由于它和应用程序线程并发执行相互抢占CPU故在CMS执行期内对应用程序吞吐量将造成一定影响。CMS默认启动的线程数是ParallelGCThreads3/4ParallelGCThreads是新生代并行收集器的线程数也可以通过-XX:ParallelGCThreads参数手工设定CMS的线程数量。当CPU资源比较紧张时受到CMS收集器线程的影响应用系统的性能在垃圾回收阶段可能会非常糟糕。 由于CMS收集器不是独占式的回收器在CMS回收过程中应用程序仍然在不停地工作。在应用程序工作过程中又会不断地产生垃圾。这些新生成的垃圾在当前CMS回收过程中是无法清除的。同时因为应用程序没有中断故在CMS回收过程中还应该确保应用程序有足够的内存可用。因此CMS收集器不会等待堆内存饱和时才进行垃圾回收而是当堆内存使用率达到某一阀值时便开始进行回收以确保应用程序在CMS工作过程中依然有足够的内存空间支持应用程序运行。 这个回收阀值可以使用-XX:CMSInitiatingOccupancyFraction来指定默认是68。即当老年代的空间使用率达到68%时会执行一次CMS回收。如果应用程序的内存使用率增长很快在CMS的执行过程中已经出现了内存不足的情况此时CMS回收就会失败JVM将启动老年代串行收集器进行垃圾回收。如果这样应用程序将完全中断直到垃圾收集完成这时应用程序的停顿时间可能很长。 -XX:UseCMSCompactAtFullCollection开关可以使CMS在垃圾收集完成后进行一次内存碎片整理。内存碎片的整理不是并发进行的。-XX:CMSFullGCsBeforeCompaction参数可以用于设定进行多少次CMS回收后进行一次内存压缩。 综上CMS收集器是一个关注停顿的垃圾收集器。同时CMS收集器在部分工作流程中可以与用户程序同时运行从而降低应用程序的停顿时间。 ###5.3.11 G1收集器### G1收集器的目标是作为一款服务端的垃圾收集器因此它在吞吐量和停顿控制上预期要优于CMS收集器。G1收集器是基于标记—压缩算法的。因此它不产生空间碎片也没有必要在收集完成后进行一次独占式的碎片整理工作。G1收集器还可以进行非常精确的停顿控制。它可以让开发人员指定在长度为M的时间段中垃圾回收时间不超过N。 使用以下参数可以启用G1回收器-XX:UnlockExperimentalVMOptions -XX:UseG1GC设置G1回收器的目标停顿时间-XX:MaxGCPauseMillis50 -XX:GCPauseIntervalMillis200表示指定在200ms内停顿时间不能超过50ms这两个参数是G1回收器的目标G1回收器并不保证能执行它们。 ###5.3.12 Stop the World### 在JVM垃圾回收时应用系统会产生一定的停顿。尤其在独占式的垃圾回收器中整个应用程序会被停止直到垃圾回收的完成。这种现象称为Stop the World。慎重选择回收器并对其进行调优是相当重要的。 ###5.3.13 GC相关参数总结### 与串行回收器相关的参数 **-XX:UseSerialGC**在新生代和老年代使用串行收集器 **-XX:SurvivorRatio**设置Eden区大小和survivor区大小的比例 **-XX:PretenureSizeThreshold**设置大对象直接进入老年代的阀值。当对象的大小超过这个值时将直接在老年代分配 **-XX:MaxTenuringThreshold**设置对象进入老年代的年龄的最大值。每一次MinorGC后对象年龄就加1。任何大于这个年龄的对象一定会进入老年代 与并行GC相关的参数 **-XX:UseParNewGC**在新生代使用并行收集器 **-XX:UseParallelOldGC**在老年代使用并行回收收集器 **-XX:ParallelGCThreads**设置用于垃圾回收的线程数。通常情况下可以和CPU数量相等。但在CPU数量比较多的情况下设置相对较小的数值也是合理的 **-XX:MaxGCPauseMillis**设置最大垃圾停顿时间。它的值是一个大于0的整数。收集器在工作时会调整Java堆大小或者其他一些参数。尽可能地把停顿时间控制在MaxGCPauseMillis以内 **-XX:GCTimeRatio**设置吞吐量大小它的值是一个0~100之间的整数。假设GCTimeRatio的值为n那么系统将花费不超过1/1n的时间用于垃圾收集 **-XX:UseAdaptiveSizePolicy**打开自适应GC策略。在这种模式下新生代的大小eden和survivor的比例晋升老年代的对象年龄等参数会被自动调整以达到在堆大小吞吐量和停顿时间之间的平衡点 与CMS回收器相关的参数 **-XX:UseConcMarkSweepGC**新生代使用并行收集器老年代使用CMS串行收集器 **-XX:ParallelCMSThreads**设置CMS的线程数量 **-XX:CMSInitiatingOccupancyFraction**设置CMS收集器在老年代空间被使用多少后触发。默认为68% **-XX:UseCMSCompactAtFullCollection**设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理 **-XX:CMSFullGCsBeforeCompaction**设置进行多少次CMS垃圾收集后进行一次内存压缩 **-XX:CMSClassUnloadingEnabled**允许对类元数据进行回收 **-XX:CMSParallelRemarkEnabled**启用并行重标记 **-XX:CMSInitiatingPermOccupancyFraction**当永久区占用率达到这一百分比时启动CMS回收前提是-XX:CMSClassUnloadingEnabled开启 **-XX:UseCMSInitiatingOccupancyOnly**表示只在到达阀值的时候才进行CMS回收 **-XX:CMSIncrementalMode**使用增量模式比较适合单CPU 与G1回收器相关的参数 **-XX:UseG1GC**使用G1回收器 **-XX:MaxGCPauseMillis**设置最大垃圾收集停顿时间 **-XX:GCPauseIntervalMillis**设置停顿间隔时间 其他参数 **-XX:DisableExplicitGC**禁用显示GCSystem.gc() ##5.4 常用调优案例和方法## ###5.4.1 将新对象预留在新生代### 在JVM参数调优中可以为应用程序分配一个合理的新生代空间 以最大限度避免新对象直接进入老年代的情况。 通过参数-Xmn6M分配足够大的新生代空间通过参数-XX:NewRatio可以指定新生代大小通过参数-XX:TargetSurvivorRatio提高from区的利用率使from区使用到90%时再将对象送入到老年代通过参数-XX:SurvivorRatio设置了一个更大的from区###5.4.2 大对象进入老年代### 在大部分情况下将对象分配在新生代是合理的。但是对于大对象这种做法确实值得商榷的。因为大对象出现在新生代可能扰乱新生代GC并破坏新生代原有的对象结构。因为尝试在新生代分配大对象很可能导致空间不足为了有足够的连续空间容纳大对象JVM不得不将新生代中的年轻对象挪到老年代。因为大对象占用空间多所以可能需要移动大量小的年轻对象进入老年代。这对GC来说是相当不利的。 基于以上原因 通过参数-XX:PretenureSizeThreshold设置大对象进入老年代的大小阀值字节可以将大对象直接分配到老年代。尤其是短命的大对象对于垃圾回收是一场灾难。 ###5.4.3 设置对象进入老年代的年龄### 通过参数-XX:MaxTenuringThreshold设置进入老年代的对象的年龄的阀值。默认值是15。这不意味着新对象非要达到这个年龄才能进入老年代。事实上对象实际进入老年代的年龄是虚拟机在运行时根据内存使用情况动态计算的这个参数指定的是阀值年龄的最大值。即实际晋升老年代年龄等于动态计算所得的年龄与-XX:MaxTenuringThreshold中较小的哪个。 ###5.4.4 稳定与震荡的堆大小### 一般来说稳定的堆大小是对垃圾回收有利的。获得一个稳定的堆大小的方式是使-Xms和-Xmx的大小一致即最大堆和最小堆一致。如果这样设置系统在运行时堆大小是恒定的稳定的堆空间可以减少GC的次数。 但是一个不稳定的堆也并不是毫无用处。稳定的堆大小虽然可以减少GC次数但同时也增加了每次GC的时间。让堆大小在一个区间中震荡在系统不需要使用大内存时压缩堆空间使GC应对一个较小的堆可以加快单次GC的速度。基于这样的考虑JVM提供了两个参数用于压缩和扩展堆空间。 **-XX:MinHeapFreeRatio**设置堆空间最小空闲比例默认是40。当堆空间的空闲内存小于这个数值时JVM便会扩展堆空间。 **-XX:MaxHeapFreeRatio**设置堆空间最大空闲比例默认是70。当堆空间的空闲内存大于这个数值时JVM便会压缩堆空间。 注意当-Xms和-Xmx相等时-XX:MinHeapFreeRatio和-XX:MaxHeapFreeRatio这两个参数是无效的。 ###5.4.5 吞吐量优先案例### 吞吐量优先的方案将会尽可能减少系统的执行垃圾回收的总时间故可以考虑关注系统吞吐量的并行回收收集器。 **-XX:UseParallelGC**设置新生代使用并行回收收集器。这是一个关注吞吐量的收集器可以尽可能地减少GC时间。 **-XX:UseParallelOldGC**设置老年代使用并行回收收集器。 **-XX:ParallelGCThreads**设置用于垃圾回收的线程数通常情况下可以和CPU数量相等。但在CPU数量比较多的情况下设置相对较小的数值也是合理的。 ###5.4.6 使用大页案例### 使用大的内存分页可以增强CPU的内存寻址能力从而提升系统的性能。 **-XX:LargePageSizeInBytes**设置大页的大小。如-XX:LargePageSizeInBytes256m。 ###5.4.7 降低停顿案例### 首先考虑的是使用关注系统停顿的CMS回收器其次为了减少Full GC次数应尽可能将对象留在新生代。 **-XX:ParallelGCThreads**设置用于垃圾回收的线程数通常情况下可以和CPU数量相等。但在CPU数量比较多的情况下设置相对较小的数值也是合理的。 **-XX:UseParNewGC**设置新生代使用并行回收器。 **-XX:UseConcMarkSweepGC**设置老年代使用CMS收集器降低停顿。 **-XX:SurvivorRatio**设置eden区和survivor区的比例为8:1。稍大的survivor空间可以提高在新生代回收生命周期较短的对象的可能性如果survivor不够大一些短命的对象可能直接进入老年代这对系统是不利的。 **-XX:TargetSurvivorRatio**设置survivor区的可使用率。这里设置为90%则允许90%的survivor空间被使用。默认值是50%。故该设置提高了survivor区的使用率。当存放的对象超过这个百分比则对象会向老年代压缩。因此这个选项更有助于将对象留在新生代。 **-XX:MaxTenuringThreshold**设置年轻对象晋升到老年代的年龄。默认值是15次。也就是说对象经过15次MinorGC依然存活则进入老年代。 ##5.5 实用JVM参数## ###5.5.1 JIT编译参数### JVM的JIT编译器可以在运行时将字节码编译成本地代码从而提高函数的执行效率。-XX:CompileThreshold为JIT编译的阀值当函数的调用次数超过-XX:CompileThreshold时JIT就将字节码编译成本地机器码。在client模式下-XX:CompileThreshold取值是1500在server模式下取值是10000JIT编译完成后JVM便会用本地代码代替原来的字节码解释执行。 JIT编译会花费一定的时间为了能合理地设置JIT编译的阀值可以使用-XX:CITime打印出JIT编译的耗时也可以使用-XX:PrintCompilation打印出JIT编译的信息。 ###5.5.2 堆快照堆Dump### 在性能排查问题中分析堆快照Dump是必不可少的一环。-XX:HeapDumpOnOutOfMemoryError参数在程序发生OOM时导出应用程序的当前堆快照。通过参数-XX:HeapDumpPath可以指定堆快照的保存位置。 -Xmx10M -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/log/dump.hprof ###5.5.3 错误处理### 在系统发生OOM错误时虚拟机在错误发生时运行一段第三方脚本。比如当OOM发生时重置系统 -XX:OnOutOfMemoryError/data/reset.bat ###5.5.4 取得GC信息### 如果需要获得一段简要的GC信息可以使用-verbose:gc或者-XX:PrintGC。如果需要获得更加详细的信息可以使用-XX:PrintGCDetails。如果需要在GC发生的时候打印GC发生的时间则可以追加使用-XX:PrintGCTimeStamps选项。如果需要查看新生代对象晋升老年代的实际阀值则可以使用参数-XX:PrintTenuringDistribution选项。如果需要在GC时打印详细的堆信息则可以打开-XX:PrintHeapAtGC开关。一旦打开它那么每次GC时都将打印堆的使用情况。如果需要查看GC与应用程序相互执行的耗时可以使用-XX:PrintGCApplicationStoppedTime和-XX:PrintGCApplicationConcurrentTime参数。它们将分别显示应用程序在GC发生时的停顿时间和应用程序在GC停顿期间的执行时间。还可以使用-Xloggc参数指定GC日志的输出位置如-Xloggc:/data/gc.log。###5.5.5 类和对象跟踪### JVM还提供了一组参数用于获取系统运行时加载卸载类的信息。-XX:TraceClassLoading参数用于跟踪类加载情况-XX:TraceClassUnloading参数用于跟踪类卸载情况。 如果需要同时跟踪类的加载和卸载信息可以同时打开这两个开关也可以使用-verbose:class参数。除了类的跟踪JVM还提供了-XX:PrintClassHistogram开关用于打印运行时实例的信息。当此开关被打开时当ctrlbreak被按下会输出系统内类的统计信息。 ###5.5.6 控制GC### -XX:DisableExplicitGC选项用于禁止显示的GC操作即禁止在程序中使用System.gc()触发的Full GC。对应用程序来说在绝大多数的情况下是不需要进行类的回收的。因为回收类的性价比非常低类元数据一旦被载入通常会伴随应用程序整个声明周期。 如果应用程序不需要回收类则可以使用-Xnoclassgc参数启动应用程序那么在GC的过程中就不会发生类的回收进而提升GC的性能。因此如果尝试使用-XX:TraceClassUnloading -Xnoclassgc参数运行程序将看不到任何输出因为系统不会卸载任何类所以类卸载是无法跟踪到任何信息的。 另一个应用的GC控制参数是-Xincgc一旦启用这个参数系统便会进行增量式的GC。增量式的GC使用特定算法让GC线程和应用程序线程交叉执行从而减小应用程序因GC而产生的停顿时间。 ###5.5.7 使用大页### 对同样大小的内存空间使用大页后内存分页的表项就会减少从而可以提升CPU从虚拟内存地址映射到物理内存地址的能力。在支持大页的操作系统中使用JVM参数让虚拟机使用大页从而提升系统性能 -XX:UseLargePages启用大页 -XX:LargePageSizeInBytes指定大页的大小 ###5.5.8 压缩指针### 在64位虚拟机上应用程序所占内存的大小要远远超出其32为版本约1.5倍左右。这是因为64位系统拥有更宽的寻址空间与32位系统相比指针对象的长度进行了翻倍。为了解决这个问题64位的JVM虚拟机可以使用-XX:UseCompressedOops参数打开指针压缩从一定程度上减少了内存消耗。可以对以下指针进行压缩 Class的属性指针静态成员变量对象的属性指针普通对象数组的每个元素指针虽然压缩指针可以节省内存但是压缩和解压缩指针也会对JVM造成一定的性能损失。 ##5.6 实战JVM调优## ###5.6.1 Tomcat启动加速### 减少启动过程中的Full GC次数1查看GC发生在哪个代中相应的增大代内存空间2禁用显示GC进一步减少启动过程中的Minor GC次数扩大新生代的大小在新生代中使用并行回收收集器-XX:UseParallelGC考虑禁用类校验-Xverify:none###5.6.2 JMeter介绍与使用### JMeter是Apache下基于Java的一款性能测试和压力测试工具。它基于Java开发可对HTTP服务器和FTP服务器甚至是数据库进行压力测试。P293 ###5.6.3 WEB应用调优过程### 合理设置堆大小和永久代大小减少GC次数禁用显示GC并去掉类校验一律使用并行回收收集器代替串行收集器尽量将对象保留在新生代中1通过-XX:SurvivorRatio设置了一个较大的survivor区2通过-XX:CMSInitiatingOccupancyFraction的值将CMS的Full GC触发的阀值设置78%即当老年代使用到78%时才触发Full GC。综上所述JVM调优的主要过程有确定堆内存大小-Xmx-Xms合理分配新生代和老年代-XX:NewRatio-XX:SurvivorRatio,确定永久代大小-XX:Permsize-XX:MaxPermSize选择垃圾收集对垃圾收集器进行合理的设置。除此之外禁用显示GC-XX:DisableExplicitGC,禁用类数据回收-Xnoclassgc禁用类校验-Xverify:none等设置。 转载于:https://my.oschina.net/xianggao/blog/412818