JVM内存组成&调优参数详解

总内存大小=堆内存区 + 持久代(永久代、方法区)区大小+程序计数器+Java虚拟机栈+本地方法栈;

名词解释:

1、堆内存区:Java程序在运行时创建的所有类实例或数组都放在同一个堆中。而一个Java虚拟实例中只存在一个堆空间,因此所有线程都将共享这个堆。每一个 java程序独占一个JVM实例,因而每个 java程序都有它自己的堆空间,它们不会彼此干扰。但是同一java程序的多个线程都共享着同一个堆空间,就得考虑多线程访问对象(堆数据)的同步问题。Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError: Java heap space异常。

 

2、持久代区大小(非堆内存):方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区域又被称为“永久代”,但这仅仅对于Sun HotSpot来讲,JRockit和IBM J9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分,而且它和Java Heap一样不需要连续的内存,可以选择固定大小或可扩展,另外,虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的是String类的intern()方法。根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

 

3、程序计数器:一块较小的内存空间,它是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的字节码指令,分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的的程序计数器,在线程启动时创建的,各线程间的计数器互不影响,因此该区域是线程私有的。。当线程在执行一个Java方法时,该计数器记录的是正在执行的虚拟机字节码指令的地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。另外,该内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM(内存溢出:OutOfMemoryError)情况的区域。

 

4、Java虚拟机栈:Java stack 以帧为单位保存线程的运行状态。虚拟机只会直接对 Java stack执行两种操作:以帧为单位的压栈或出栈。每当线程调用一个方法的时候,就对当前状态作为一个帧保存到 java stack 中(压栈);当一个方法调用返回时,从java stack 弹出一个帧(出栈)。栈的大小是有一定的限制,一般用于存放方法入口参数和返回值,以及原子类型的本地变量(即方法内部变量),这个可能出现StackOverFlow 问题,例如递归的层数太深。换一个说法,该区域也是线程私有的,它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,栈它是用于支持续虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了,并且写入了方法表的Code属性之中。因此,一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

在Java虚拟机规范中,对这个区域规定了两种异常情况:

1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。

2、如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

注意点:由于Sun的HotSpot虚拟机不区分java虚拟机栈和本地方法栈,因此对于HotSpot虚拟机来说-Xoss参数(设置本地方法栈大小)虽然存在,但是实际上是无效的,栈容量只能由-Xss参数设定。

 

5、本地方法栈:该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,不止如此,它还可以做任何它想做的事情。比如,可以调用寄存器,或在操作系统中分配内存等。总之,本地方法具有和JVM 相同的能力和权限。  (这里出现 JVM无法控制的内存溢出问题 native heap OutOfMemory ) 。

 

 

综上,从线程角度,堆内存(Heap) 和持久代区(Method Area) 是被所有线程的共享使用的;而Java虚拟机栈(Java stack), 程序计数器(Program counter) 和 本地方法栈(Native method stack) 是以线程为粒度的,每个线程独自拥有。

 

被分配的堆内存需要进行垃圾回收,先介绍与垃圾回收相关的概念:

 

1、堆内存:在HotSpot虚拟机中,物理的将堆内存分为两个—年轻代(young generation)和老年代(old generation),即Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation)。年老代和年轻代的划分对垃圾收集影响比较大。

    a、年轻代:所有新生成的对象首先都是放在年轻代。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代一般分3个区,1个Eden区,2个Survivor区(from 和 to)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当另一个Survivor区也满了的时候,从前一个Survivor区复制过来的并且此时还存活的对象,将可能被复制到年老代。2个Survivor区是对称的,没有先后关系,所以同一个Survivor区中可能同时存在从Eden区复制过来对象,和从另一个Survivor区复制过来的对象;而复制到年老区的只有从另一个Survivor区过来的对象。而且,因为需要交换的原因,Survivor区至少有一个是空的。特殊的情况下,根据程序需要,Survivor区是可以配置为多个的(多于2个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。当对象从这块内存区域消失时,我们说发生了一次“minor GC”。

 

 

    b、老年代:在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,存活下来的年轻代对象被复制到这里。这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。对象从老年代消失时,我们说“major GC”(或“full GC”)发生了。

 

2、持久代:用于存放静态类型数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响。但是有些应用可能动态生成或调用一些Class,例如 Hibernate CGLib 等,在这种时候往往需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。

 

那么我们了解当一组对象生成时,内存申请过程如下:

  1. JVM会试图为相关Java对象在年轻代的Eden区中初始化一块内存区域。
  2. 当Eden区空间足够时,内存申请结束。否则执行下一步。
  3. JVM试图释放在Eden区中所有不活跃的对象(minor GC)。释放后若Eden空间仍然不足以放入新对象,JVM则试图将部分Eden区中活跃对象放入Survivor区。
  4. Survivor区被用来作为Eden区及年老代的中间交换区域。当年老代空间足够时对象会,Survivor区中存活了一定次数的被移到年老代。
  5. 当年老代空间不够时,JVM会在年老代进行完全的垃圾回收(Full GC)。
  6. Full GC后,若Survivor区及年老代仍然无法存放从Eden区复制过来的对象,则会导致JVM无法在Eden区为新生成的对象申请内存,即出现“Out of Memory”。

 

下面详细谈谈每个内存设置参数的意义:

A、堆内存设置=年轻代大小 + 年老代大小:

  • -Xms1024M:初始化堆内存大小(注意,不加M的话单位是KB),设置JVM初始内存为1024m。生产环境下-Xms可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存,否则可能需要对Heap Size进行频繁的扩展和收缩,增加处理时间;缺省值2M,(--server选项把缺省值增加到32M)
  • -Xmx2048M:设置JVM最大可用内存空间;缺省值为64M。(-server选项把缺省尺寸增加到128M。)
  • -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代),默认值为8。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 
  • -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值,缺省值是10。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6 

 

A.1年轻代区内存设置(年轻代=Eden区+2Survivor区):

  • -Xmn1024M:直接设定年轻代内存值大小为1024M。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8;缺省值为640K。(-server选项把缺省尺寸增加到2M。)注意:-Xmn的优先级比-XX:NewRatio高,若-Xmn已指定,则OldSize=HeapSize-NewSize,无需再按比例计算。生产环境中一般只需指定-Xmn就足够了。
  • -XX:NewSize=size in bytes   设置年轻代初始内存尺寸。它的缺省值是640K。(-server选项把缺省尺寸增加到2M。)生产环境中一般只需设置-Xmn或者设置Xmn和NewSize相等,避免调整Size增加处理时间。 
  • -XX:MaxNewSize=size in bytes 设置年轻代的最大内存值,新建对象所需的内存就是从这个空间中分配来的,这个选项的缺省值是640K。(-server选项把缺省尺寸增加到2M。)     1)调大年轻代的内存值能够减少Full GC次数。    2)一般不允许年轻代比年老代还大,因为要考虑GC时最坏情况,所有对象都晋升到老年代。 -XX:MaxNewSize 最大可以设置为-Xmx/2 。

(如上设置年轻代内存大小参数的优先级

    1. 高优先级:-XX:NewSize/-XX:MaxNewSize 

    2. 中优先级:-Xmn(默认等效  -Xmn=-XX:NewSize=-XX:MaxNewSize=?) 

    3. 低优先级:-XX:NewRatio 

 

A.2垃圾回收内存设置(很多垃圾收集器的选项依赖于堆大小的设定。请在微调垃圾收集器使用内存空间的方式之前,确认是否已经正确设定了堆的尺寸):

  • -XX:MaxTenuringThreshold=0:设置垃圾最大年龄,每一次经历GC没有被回收,则其age自增一,达到设置的阀值后不是被回收就是移至年老代。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。 
  • -XX:MinHeapFreeRatio=percentage as a whole number:修改垃圾回收之后堆中可用内存的最小百分比,缺省值是40。如果垃圾回收后至少还有40%的堆内存没有被释放,则系统将增加堆的尺寸。 
  • -XX:MaxHeapFreeRatio=percentage as a whole number: 改变垃圾回收之后和堆内存缩小之前可用堆内存的最大百分比,缺省值为70。这意味着如果在垃圾回收之后还有大于70%的堆内存,则系统就会减少堆的尺寸。 
  • -XX:TargetSurvivorRatio=percentage 设置Survivor区的目标使用率,缺省值为50.设置较大值可以提高其使用率,一旦存放总大小超过比例,则迁移至年老区。通常可以和-XX:MaxTenuringThreshold配合使用,同时关注设置的老代阀值上限和设置的目标使用率,最优的状态即大部分的年龄均不大于1,且从未达到设置的使用率,则表明对象均被GC回收。

 

A.3垃圾回收策略:

JVM给出了3种选择:串行收集器、并行收集器、并发收集器。串行收集器只适用于小数据量的情况,所以生产环境的选择主要是并行收集器、并发收集器和G1(“Garbage-First”)收集器。

默认情况下JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行智能判断。

其中并发与并行存在一定的区别,并发可以理解为在同一个时间间隔内多个任务同时开展,但是通常相互不相关;并行是为了解决同一个任务。

 

a、串行收集器:适合客户端使用,不适合服务器。

  • -XX:+UseSerialGC:设置串行收集器。

 

b、并行收集器(吞吐量优先):parallel收集器使用多线程并行处理GC,因此更快。当有足够大的内存和大量芯数时,parallel收集器是有用的。

  • -XX:+UseParallelGC:设置为并行收集器。此配置仅对年轻代有效。即年轻代使用并行收集,而年老代仍使用串行收集。
  • -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时有多少个线程一起进行垃圾回收。此值建议配置与CPU数目相等。
  • -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0开始支持对年老代并行收集。
  • -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间(单位毫秒)。如果无法满足此时间,JVM会自动调整年轻代大小,以满足此时间。
  • -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动调整年轻代Eden区大小和Survivor区大小的比例,以达成目标系统规定的最低响应时间或者收集频率等指标。此参数建议在使用并行收集器时,一直打开。

 

c、并发收集器(响应时间优先):垃圾收集器进行垃圾收集时,其他线程的依旧在工作。一旦采取了这种GC类型,由于垃圾回收导致的停顿时间会极其短暂。CMS 收集器也被称为低延迟垃圾收集器。它经常被用在那些对于响应时间要求十分苛刻的应用上。缺点是它会比其他GC类型占用更多的内存和CPU,默认情况下不支持压缩步骤,在使用这个GC类型之前需要慎重考虑。如果因为内存碎片过多而导致压缩任务不得不执行,那么stop-the-world的时间要比其他任何GC类型都长,需要考虑压缩任务的发生频率以及执行时间。

  • -XX:+UseConcMarkSweepGC:即CMS收集,设置年老代为并发收集。CMS收集是JDK1.4后期版本开始引入的新GC算法。它的主要适合场景是对响应时间的重要性需求大于对吞吐量的需求,能够承受垃圾回收线程和应用线程共享CPU资源,并且应用中存在比较多的长生命周期对象。CMS收集的目标是尽量减少应用的暂停时间,减少Full GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存。 此选项在Heap Size 比较大而且Full GC收集时间较长的情况下使用更合适。
  • -XX:+UseParNewGC:设置年轻代为并发收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此参数。
  • -XX:CMSFullGCsBeforeCompaction=0:由于并发收集器不对内存空间进行压缩和整理,所以运行一段时间并行收集以后会产生内存碎片,内存使用效率降低。此参数设置运行0次Full GC后对内存空间进行压缩和整理,即每次Full GC后立刻开始压缩和整理内存。
  • -XX:+UseCMSCompactAtFullCollection:打开内存空间的压缩和整理,在Full GC后执行。可能会影响性能,但可以消除内存碎片。
  • -XX:+CMSIncrementalMode:设置为增量收集模式。一般适用于单CPU情况。
  • -XX:CMSInitiatingOccupancyFraction=70:表示年老代内存空间使用到70%时就开始执行CMS收集,以确保年老代有足够的空间接纳来自年轻代的对象,避免Full GC的发生。

 

d、垃圾优先型(G1)收集器:G1是一个适用于服务器端、大内存、多CPU情景的垃圾收集器,主要目标是在维持高效率回收(high thoughput)的同时,提供软实时中断特性。用户可以指定一个时间上限,如果垃圾回收导致的程序暂停超过了用户设定的时间上限,会打断垃圾回收,恢复程序的执行。G1的原理在于将堆划分成等一系列大小的区域,每一个区域都有一个对应的remembered set结构,用来记录指向这个区域中的地址的其他区域的指针。 在垃圾回收时,选择记录最少的一个区域进行,按找这种方式选择出来的区域,通常是有用数据最少、垃圾最多的区域,这也就是“Garbage-first ”名称的由来。假如没有外部的指针指向这个区域,就可以直接回收整块区域而不用进行内存Copy。如果你想要理解G1收集器,首先你要忘记你所理解的新生代和老年代,在G1中,年轻代和年老代之间没有物理隔离。每个对象被分配到不同的网格中,随后执行垃圾回收。当一个区域填满之后,对象被转移到另一个区域,并再执行一次垃圾回收。在这种垃圾回收算法中,不再有从新生代移动到老年代的三部曲。这个类型的垃圾收集算法是为了替代CMS 收集器而被创建的,因为CMS 收集器在长时间持续运行时会产生很多问题。G1最大的好处是他的性能,他比我们在上面的任何一种GC都要快。

G1有以下属性:

 

◆并行和并发性:G1利用了当今硬件中存在的并行性,当Java应用程序的线程被停止时,它使用所有可用的CPU(核心,硬件线程等)加速其停止,在停止过程中运行Java线程最小化整个堆栈。

◆代:和其他HotSpot GC一样,G1是一代,意味着它在处理新分配的对象(年轻代)和已经生存了一段时间的对象(年老代)时会不同,它主要集中于新对象上的垃圾回收活动,因为它们是最可能回收的,旧对象只是偶尔访问一下,对于大多数Java应用程序,代的垃圾回收对于替代方案具有重要优势。

◆压缩:和CMS不同,G1会随时间推移对堆栈进行压缩,压缩消除了潜在的碎片问题,确保长时间运行的操作流畅和一致。

◆可预测性:G1比CMS预测性更佳,这都是由于消除了碎片问题带来的好处,再也没有CMS中停止期间出现的负面影响,另外,G1有一个暂停预测模型,允许它满足(或很少超过)暂停时间目标。

因为G1GC还不是默认的jvm gc策略(目前为止),需要使用的话可以加入以下参数开启:

  • -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC   :     #开启
  • -XX:MaxGCPauseMillis =50                :#最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间(暂停时间目标50ms,默认200ms) 
  • -XX:GCPauseIntervalMillis =200         :#使用G1时可以指定时间间隔,当GC暂停持续时间没有XX:MaxGCPauseMillis给出的时间长则设置(暂停间隔目标200ms) 
  • -XX:+G1YoungGenSize=512m           : #年轻代的大小可以明确指定影响消除暂停时间(年轻代大小512M) 
  • -XX:InitiatingHeapOccupancyPercent=n  :#堆占用了多少比例的时候触发GC,就即触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%
  • -XX:ParallelGCThreads=n                 :#配置并行收集器的线程数,即:同时有多少个线程一起进行垃圾回收。将 n 的值设置为逻辑处理器(CPU数目)的数量。n 的值与逻辑处理器的数量相同,最多为 8。如果逻辑处理器不止八个,则将 n 的值设置为逻辑处理器数的 5/8 左右。这适用于大多数情况,除非是较大的 SPARC 系统,其中 n 的值可以是逻辑处理器数的 5/16 左右。
  • -XX:ConcGCThreads=n                      :#并发GC使用的线程数,将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。
  • -XX:G1ReservePercent=n                   :#设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险。默认值是 10%,增加或减少百分比时,需确保对总的 Java 堆调整相同的量。Java HotSpot VM build 23 中没有此设置
  • -XX:G1HeapRegionSize=n                  :#设置的 G1 区域的大小。值是2的幂,范围是1 MB 到32 MB。目标是根据最小的 Java 堆大小划分出约 2048 个区域。
  • -XX:G1NewSizePercent=5                   :#设置要用作年轻代大小最小值的堆百分比。默认值是 Java 堆的 5%。这是一个实验性的标志。此设置取代了 -XX:DefaultMinNewGenPercent 设置。Java HotSpot VM build 23 中没有此设置。    
  • -XX:G1MaxNewSizePercent=60          :#设置要用作年轻代大小最大值的堆大小百分比。默认值是 Java 堆的 60%。这是一个实验性的标志。此设置取代了 -XX:DefaultMaxNewGenPercent 设置。Java HotSpot VM build 23 中没有此设置
  • -XX:G1MixedGCLiveThresholdPercent=65:为混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%。这是一个实验性的标志。此设置取代了 -XX:G1OldCSetRegionLiveThresholdPercent 设置。Java HotSpot VM build 23 中没有此设置。
  • -XX:G1HeapWastePercent=10:设置您愿意浪费的堆百分比。如果可回收百分比小于堆废物百分比,Java HotSpot VM 不会启动混合垃圾回收周期。默认值是 10%。Java HotSpot VM build 23 中没有此设置。
  • -XX:G1MixedGCCountTarget=8:设置标记周期完成后,对存活数据上限为 G1MixedGCLIveThresholdPercent 的旧区域执行混合垃圾回收的目标次数。默认值是 8 次混合垃圾回收。混合回收的目标是要控制在此目标次数以内。Java HotSpot VM build 23 中没有此设置。
  • -XX:G1OldCSetRegionThresholdPercent=10:设置混合垃圾回收期间要回收的最大旧区域数。默认值是 Java 堆的 10%。Java HotSpot VM build 23 中没有此设置。

    #据称下面两个参数可能会引发一个罕见的竞争状态(race condition),慎用。
    -XX:+G1ParallelRSetUpdatingEnabled
    -XX:+G1ParallelRSetScanningEnabled

    G1实际应用建议:

  • 年轻代大小:避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小,因为固定年轻代的大小会覆盖暂停时间目标。
  • 暂停时间目标:每当对垃圾回收进行评估或调优时,都会涉及到延迟与吞吐量的权衡。G1是增量垃圾回收器, 其吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。因此,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。
  • 掌握混合垃圾回收:当您调优混合垃圾回收时,请尝试以下选项
    • -XX:InitiatingHeapOccupancyPercent
    • -XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent
    • -XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent

 

其它垃圾回收参数

  • -XX:+ScavengeBeforeFullGC:年轻代GC优于Full GC执行。
  • -XX:+DisableExplicitGC:不响应 System.gc() 代码。
  • -XX:+UseThreadPriorities:启用本地线程优先级API。即使 java.lang.Thread.setPriority() 生效,不启用则无效。
  • -XX:SoftRefLRUPolicyMSPerMB=0:软引用对象在最后一次被访问后能存活0毫秒(JVM默认为1000毫秒)。

辅助信息参数设置

  • -XX:+CITime:打印消耗在JIT编译的时间。
  • -XX:ErrorFile=./hs_err_pid.log:保存错误日志或数据到指定文件中。
  • -XX:HeapDumpPath=./java_pid.hprof:指定Dump堆内存时的路径,拿到heap文件后可以用Eclipse MAT进行分析,找出引起内存泄漏的class。
  • -XX:+HeapDumpOnOutOfMemoryError:当首次遭遇内存溢出时Dump出此时的堆内存,与-XX:HeapDumpPath一起使用。
  • -XX:OnError=";":出现致命ERROR后运行自定义命令。
  • -XX:OnOutOfMemoryError=";":当首次遭遇内存溢出时执行自定义命令。
  • -XX:+PrintClassHistogram:按下 Ctrl+Break 后打印堆内存中类实例的柱状信息,同JDK的 jmap -histo 命令。
  • -XX:+PrintConcurrentLocks:按下 Ctrl+Break 后打印线程栈中并发锁的相关信息,同JDK的 jstack -l 命令。
  • -XX:+PrintCompilation:当一个方法被编译时打印相关信息。
  • -XX:+PrintGC:每次GC时打印相关信息,与 -verbose:gc 是一样的,可以认为-verbose:gc 是 -XX:+PrintGC的别名,打印日志稍有差异。
  • -XX:+PrintGCDetails:每次GC时打印详细信息。
  • -XX:+PrintGCTimeStamps:打印每次GC的时间戳。
  • -XX:+TraceClassLoading:跟踪类的加载信息。
  • -XX:+TraceClassLoadingPreorder:跟踪被引用到的所有类的加载信息。
  • -XX:+TraceClassResolution:跟踪常量池。
  • -XX:+TraceClassUnloading:跟踪类的卸载信息。
  • -Xloggc:../logs/gc.log 垃圾收集日志文件的输出路径

 

关于参数名称等

  • 标准参数(-),所有JVM都必须支持这些参数的功能,而且向后兼容;例如:
    • -client——设置JVM使用Client模式,特点是启动速度比较快,但运行时性能和内存管理效率不高,通常用于客户端应用程序或开发调试;在32位环境下直接运行Java程序默认启用该模式。
    • -server——设置JVM使Server模式,特点是启动速度比较慢,但运行时性能和内存管理效率很高,适用于生产环境。在具有64位能力的JDK环境下默认启用该模式。
  • 非标准参数(-X),默认JVM实现这些参数的功能,但是并不保证所有JVM实现都满足,且不保证向后兼容;
  • 非稳定参数(-XX),此类参数各个JVM实现会有所不同,将来可能会不被支持,需要慎重使用;

 

B、持久代区内存设置:

  • -XX:PermSize=512M:初始化持久区(启动是主要是类加载)内存池大小;生产环境中一般设置MaxPermSize和PermSize相等,避免调整size需要的处理时间。
  • -XX:MaxPermSize=2048M:最大类持久区内存池大小;缺省值32M。

 

C、Java虚拟机栈内存设置:

  • -Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右;

 

 

Z、直接内存设置(Direct Memory Size):

  • -XX:MaxDirectMemorySize ---direct byte buffer用到的本地内存。默认跟Xmx相等,所以生产环境中一般不设置Xmx大于物理内存的一半

直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,它直接从操作系统中分配,因此不受Java堆大小的限制,但是会受到本机总内存的大小及处理器寻址空间的限制,因此它也可能导致OutOfMemoryError异常出现。在JDK1.4中新引入了NIO机制,它是一种基于通道与缓冲区的新I/O方式,可以直接从操作系统中分配直接内存,即在堆外分配内存,这样能在一些场景中提高性能,因为避免了在Java堆和Native堆中来回复制数据。

 

大型网站服务器案例

承受海量访问的动态Web应用

服务器配置:8 CPU, 8G MEM, JDK 1.6.X

参数方案:

-server -Xmx3550m -Xms3550m -Xmn1256m -Xss128k -XX:SurvivorRatio=6 -XX:MaxPermSize=256m -XX:ParallelGCThreads=8 -XX:MaxTenuringThreshold=0 -XX:+UseConcMarkSweepGC

调优说明:

  • -Xmx 与 -Xms 相同以避免JVM反复重新申请内存。-Xmx 的大小约等于系统内存大小的一半,即充分利用系统资源,又给予系统安全运行的空间。
  • -Xmn1256m 设置年轻代大小为1256MB。此值对系统性能影响较大,Sun官方推荐配置年轻代大小为整个堆的3/8。
  • -Xss128k 设置较小的线程栈以支持创建更多的线程,支持海量访问,并提升系统性能。
  • -XX:SurvivorRatio=6 设置年轻代中Eden区与Survivor区的比值。系统默认是8,根据经验设置为6,则2个Survivor区与1个Eden区的比值为2:6,一个Survivor区占整个年轻代的1/8。
  • -XX:ParallelGCThreads=8 配置并行收集器的线程数,即同时8个线程一起进行垃圾回收。此值一般配置为与CPU数目相等。
  • -XX:MaxTenuringThreshold=0 设置垃圾最大年龄(在年轻代的存活次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。根据被海量访问的动态Web应用之特点,其内存要么被缓存起来以减少直接访问DB,要么被快速回收以支持高并发海量请求,因此其内存对象在年轻代存活多次意义不大,可以直接进入年老代,根据实际应用效果,在这里设置此值为0。
  • -XX:+UseConcMarkSweepGC 设置年老代为并发收集。CMS(ConcMarkSweepGC)收集的目标是尽量减少应用的暂停时间,减少Full GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存,适用于应用中存在比较多的长生命周期对象的情况。

 

 

最后从内存溢出的异常来分析下我们设置的每个参数具体对应关系:

1、java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出

JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Old Generaion 之和。
提示:在JVM中如果98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。建议:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值(与sun推荐的3/8稍有差异)。 
解决方法:调整Heap size大小。

 

2、 java.lang.OutOfMemoryError: PermGen space  ---- PermGen space溢出

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader的时候被放入PermGen space区域,它和存放类实例(Instance)的Heap区域不同。sun的GC不会在主程序运行期对PermGen space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen space溢出。

建议:这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB 应用下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以达到减少jar 文档重复占用内存的目的。

解决方法: 调整XX:MaxPermSize大小。

 

3、java.lang.StackOverflowError   ---- 栈溢出

JVM依然是采用栈式的虚拟机,这个和C和Pascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 “层”太多了,以致于把栈区溢出了。通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通常递归即使递归的层次不会过多,也很容易溢出。

解决方法:修改程序。

 

 

内存溢出

下面给出个内存区域内存溢出的简单测试方法

这里有一点要重点说明,在多线程情况下,给每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。操作系统为每个进程分配的内存是有限制的,虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值,忽略掉程序计数器消耗的内存(很小),以及进程本身消耗的内存,剩下的内存便给了虚拟机栈和本地方法栈,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少。因此,如果是建立过多的线程导致的内存溢出,在不能减少线程数的情况下,就只能通过减少最大堆和每个线程的栈容量来换取更多的线程。

另外,由于Java堆内也可能发生内存泄露(Memory Leak),这里简要说明一下内存泄露和内存溢出的区别:

内存泄露是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。Java中一般不会产生内存泄露,因为有垃圾回收器自动回收垃圾,但这也不绝对,当我们new了对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这边会造成内存泄露,

内存溢出是指程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限。

 

对象实例化分析

对内存分配情况分析最常见的示例便是对象实例化:

Object obj = new Object();

这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中,及时对JVM虚拟机不了解的Java使用这,应该也知道obj会作为引用类型(reference)的数据保存在Java栈的本地变量表中,而会在Java堆中保存该引用的实例化对象,但可能并不知道,Java堆中还必须包含能查找到此对象类型数据的地址信息(如对象类型、父类、实现的接口、方法等),这些类型数据则保存在方法区中。

另外,由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄池和直接使用指针。

通过句柄池访问的方式如下:

通过直接指针访问的方式如下:

 这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存放的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处是速度快,它节省了一次指针定位的时间开销。目前Java默认使用的HotSpot虚拟机采用的便是是第二种方式进行对象访问的。

 

 

参考资料:

Java中的垃圾回收机制:http://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651477309&idx=1&sn=ab7ffd42cb2632721c5810529a3d1806&scene=23&srcid=0714aSeVNPU59W346VKtp5Ky#rd

深入Java虚拟机(1):Java内存区域与内存溢出:http://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651477210&idx=2&sn=57955ff79f6c930a54b6ed52b9b30e70&scene=23&srcid=0714PCSMOm7CvZDlXNkcHUrn#rd

JVM性能调优:http://my.oschina.net/chape/blog/200790

Java7中G1垃圾回收收集器使用:http://ju.outofmemory.cn/entry/65368

Java7中G1垃圾回收收集器特性:http://developer.51cto.com/art/200907/138943.htm

垃圾优先型垃圾回收器调优:http://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值