一、 JVM规范
JVM规范对Java运行时的内存划定了几块区域(详见这里),有:JVM栈(Java Virtual Machine Stacks)、堆(Heap)、方法区(Method Area)、常量池(Runtime Constant Pool)、本地方法栈(Native Method Stacks),但对各块区域的内存布局和地址空间却没有明确规定,而留给各JVM厂商发挥的空间。
二、 HotSpot JVM
Sun自家的HotSpot JVM实现对堆内存结构有相对明确的说明。按照HotSpot JVM的实现,堆内存分为3个代:Young Generation、Old(Tenured) Generation、Permanent Generation。众所周知,GC(垃圾收集)就是发生在堆内存这三个代上面的。Young用于分配新的Java对象,其又被分为三个部分:Eden Space和两块Survivor Space(称为From和To),Old用于存放在GC过程中从Young Gen中存活下来的对象,Permanent用于存放JVM加载的class等元数据。详情参见HotSpot内存管理白皮书。
1.Java Heap分为3个区
1).Young(分为两个同等大小的survior区和一个eden区,JVM默认分配内存大小为survior:eden = 1 : 8, 可配置)
2).Old(一个Old区,JVM默认分配内存大小为 Old:Young = 2:1, 可配置)
3).Permanent
Young保存刚实例化的对象。当该区被填满时,GC会将对象移到Old区(按照一定的算法,如GC超过15次后某一对象还存活,则会移动到Old区,可配置)
2.JVM有2个GC线程 (未查找到详细资料)
第一个线程负责回收Heap的Young区
第二个线程在Heap不足时,遍历Heap,将Young 区升级为Older区
以下是我的机器的执行该命令查看到的JVM相关线程:jstack -l pid
"VMThread" prio=10 tid=0x00007fa2c4061000 nid=0x18431 runnable
"GCtask thread#0 (ParallelGC)" prio=10 tid=0x00007fa2c401a000 nid=0x1842frunnable
"GCtask thread#1 (ParallelGC)" prio=10 tid=0x00007fa2c401b800 nid=0x18430runnable
"VMPeriodic Task Thread" prio=10 tid=0x00007fa2c4097800 nid=0x18438 waitingon condition
三、 JVM参数设置
非稳态选项使用说明:
-XX:+
性能选项
选项与默认值 默认值与限制 描述
-Xms 初始堆大小 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制.
注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。
整个堆大小=年轻代大小 + 年老代大小 + 持久代大小.
-Xmn 年轻代大小(1.4or lator) 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-Xss 每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右
一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长)
和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:"”
-Xss is translated in a VM flag named ThreadStackSize”
一般设置这个值就可以了。
-XX:MaxTenuringThreshold 垃圾最大年龄 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率
该参数只有在串行GC时才有效.
-XX:ParallelGCThreads 并行收集器的线程数 此值最好配置与处理器数目相等 同样适用于CMS
-XX:+AggressiveOpts JDK 5 update 6后引入,但需要手动启用。 启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等。
JDK6默认启用。
-XX:LargePageSizeInBytes=4m 默认4m,amd64位:2m 设置堆的内存页大小。
-XX:MaxHeapFreeRatio=70 70 GC后,如果发现空闲堆内存占到整个预估堆内存的70%,则收缩堆内存预估最大值。
什么是预估堆内存?
预估堆内存是堆大小动态调控的重要选项之一。堆内存预估最大值一定小于或等于固定最大值(-Xmx指定的数值)。前者会根据使用情况动态调大或缩小,以提高GC回收的效率。
-XX:NewSize 设置年轻代大小(for 1.3/1.4)
-XX:MaxNewSize=size 1.3.1 Sparc: 32m 新生代占整个堆内存的最大值。
1.3.1 x86: 2.5m
-XX:MaxPermSize=64m 5.0以后: 64 bit VMs会增大预设值的30% Perm占整个堆内存的最大值。
1.4 amd64: 96m
1.3.1 -client: 32m
其他默认 64m
-XX:MinHeapFreeRatio=40 40 GC后,如果发现空闲堆内存占到整个预估堆内存的40%,则放大堆内存的预估最大值,但不超过固定最大值。
关联选项:
-XX:MaxHeapFreeRatio=70
-XX:NewRatio=2 Sparc -client: 8
新生代和年老代的堆内存占用比例。
这里的2表示,则Old Generation是 Yong Generation的2倍,即Yong Generation占据内存的1/3
x86 -server: 8
x86 -client: 12
-client: 4 (1.3)
8 (1.3.1+)
x86: 12
其他默认 2
-XX:NewSize=2.125m 5.0以后: 64 bit Vms会增大预设值的30% 新生代预估堆内存占用的默认值。(什么是预估堆内存?见 -XX:MaxHeapFreeRatio 处的描述)
x86: 1m
x86, 5.0以后: 640k
其他默认 2.125m
-XX:ReservedCodeCacheSize=32m Solaris 64-bit, amd64, -server x86: 48m 设置代码缓存的最大值,编译用。
1.5.0_06之前, Solaris 64-bit amd64: 1024m
其他默认 32m
-XX:SurvivorRatio=8 Solaris amd64: 6 Eden与Survivor的占用比例。这里的8表示,一个survivor区占用 1/8 的新生代内存,因为survivor有2个,所以是 2/8,那么Eden的占比为 6/8。
Sparc in 1.3.1: 25
Solaris platforms5.0以前: 32
其他默认 8
-XX:TargetSurvivorRatio=50 50 实际使用的survivor空间大小占比。默认是50%,最高90%。
-XX:ThreadStackSize=512 Sparc: 512 线程堆栈大小
Solaris x86: 320(5.0以前 256)
Sparc 64 bit: 1024
Linux amd64: 1024 (5.0 以前 0)
其他默认 512.
-XX:+UseBiasedLocking JDK 5 update 6后引入,但需要手动启用。 启用偏向锁。
JDK6默认启用。
-XX:+UseFastAccessorMethods 默认启用 启用原始类型的getter方法优化。
-XX:-UseISM 默认启用 启用solaris的ISM。
详见Intimate Shared Memory.
-XX:+UseLargePages JDK 5 update 5后引入,但需要手动启用。 启用大内存分页。
JDK6默认启用。
-XX:+UseMPSS 1.4.1 之前: 不启用 启用solaris的MPSS,不能与ISM同时使用。
其余版本默认启用
-XX:+StringCache 默认启用 启用字符串缓存。
-XX:AllocatePrefetchLines=1 1 与机器码指令预读相关的一个选项,资料比较少,本文档不做解释。有兴趣的朋友请自行阅读官方doc。
-XX:AllocatePrefetchStyle=1 1 与机器码指令预读相关的一个选项,资料比较少,本文档不做解释。有兴趣的朋友请自行阅读官方doc。
-XX:-AllowUserSignalHandlers 限于Linux和Solaris,默认不启用 允许为java进程安装信号处理器。
-XX:-DisableExplicitGC 默认不启用 禁止在运行期显式地调用 System.gc()。
开启该选项后,GC的触发时机将由Garbage Collector全权掌控。
需要注意的是,你程序里没调用System.gc(),你依赖的框架,工具可能在使用。例如RMI。请仔细权衡禁用带来的影响。
-XX:-RelaxAccessControlCheck 默认不启用 在Class校验器里,放松对访问控制的检查。作用与reflection里的setAccessible类似。
-XX:-UseConcMarkSweepGC 默认不启用 启用CMS低停顿垃圾收集器。
-XX:-UseParallelGC 默认不启用,-server时启用 策略为新生代使用并行清除,年老代使用单线程Mark-Sweep-Compact清除的垃圾收集器。
-XX:-UseParallelOldGC 默认不启用 策略为老年代和新生代都使用并行清除的垃圾收集器。
-XX:-UseSerialGC 默认不启用,-client时启用 使用串行垃圾收集器。
-XX:+UseSplitVerifier java5默认不启用,java6默认启用 使用新的Class类型校验器 。
什么是新Class类型校验器?
新Class类型校验器,将老的校验步骤拆分成两步:
1,类型推断。2,类型校验。
新类型校验器通过在javac编译时嵌入类型信息到bytecode中,省略了类型推断这一步,从而提升了classloader的性能。
Classload顺序:load -> verify -> prepare -> resove -> init
关联选项:
-XX:+FailOverToOldVerifier
-XX:+FailOverToOldVerifier Java6新引入选项,默认启用 如果新的Class校验器检查失败,则使用老的校验器。
关联选项:
-XX:+UseSplitVerifier
-XX:+HandlePromotionFailure java5以前是默认不启用,java6默认启用 关闭新生代收集担保。
什么是新生代收集担保?
在一次理想化的minor gc中,活跃对象会从Eden和First Survivor中被复制到Second Survivor。然而,Second Survivor不一定能容纳所有的活跃对象。为了确保minor gc能够顺利完成,需要在年老代中保留一块足以容纳所有活跃对象的内存空间。这个预留的操作,被称之为新生代收集担保(New Generation Guarantee)。当预留操作无法完成时,就会触发major gc(full gc)。
为什么要关闭新生代收集担保?
因为在年老代中预留的空间大小,是无法精确计算的。为了确保极端情况的发生,GC参考了最坏情况下的新生代内存占用,即Eden+First Survivor。这种策略无疑是在浪费年老代内存,并从时序角度看,可能提前触发Full GC。为了避免如上情况的发生,JVM允许开发者关闭新生代收集担保。
在开启本选项后,minotr gc将不再提供新生代收集担保,而是在出现survior或年老代不够用时,抛出promotion failed异常。
-XX:+UseSpinning java1.4.2和1.5需要手动启用, java6默认已启用 启用多线程自旋锁优化。
自旋锁优化原理
大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意。原因是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖操作系统互斥(mutex)来实现的。互斥是一种会导致线程挂起,并在较短的时间内又需要重新调度回原线程的,较为消耗资源的操作。为了避免进入OS互斥,Java6的开发者们提出了自旋锁优化方法。自旋锁优化的原理是在线程进入OS互斥前,通过CAS自旋一定的次数来检测锁的释放。如果在自旋次数未达到预订值前,发现锁已被释放,则会立即持有该锁。
CAS检测锁的原理详见: 关联选项:
-XX:PreBlockSpin=10
-XX:PreBlockSpin=10 -XX:+UseSpinning必须先启用,对于java6来说已经默认启用了,这里默认自旋10次 控制多线程自旋锁优化的自旋次数。(什么是自旋锁优化?见 -XX:+UseSpinning 处的描述)
关联选项:
-XX:+UseSpinning
-XX:+ScavengeBeforeFullGC 默认启用 在Full GC前触发一次Minor GC。
-XX:+UseGCOverheadLimit 默认启用 限制GC的运行时间。如果GC耗时过长,就抛OOM。
-XX:+UseTLAB 1.4.2以前和使用-client选项时,默认不启用,其余版本默认启用 启用线程本地缓存区(Thread Local)。
-XX:+UseThreadPriorities 默认启用 使用本地线程的优先级。
-XX:+UseAltSigs 限于Solaris,默认启用 为了防止与其他发送信号的应用程序冲突,允许使用候补信号替代 SIGUSR1和SIGUSR2。
-XX:+UseBoundThreads 限于Solaris, 默认启用 绑定所有的用户线程到内核线程。
减少线程进入饥饿状态(得不到任何cpu time)的次数。
-XX:+UseLWPSynchronization 限于solaris,默认启用 使用轻量级进程(内核线程)替换线程同步。
-XX:+MaxFDLimit 限于Solaris,默认启用 设置java进程可用文件描述符为操作系统允许的最大值。
-XX:+UseVMInterruptibleIO 限于solaris,默认启用 在solaris中,允许运行时中断线程 。
-XX:CMSInitiatingOccupancyFraction=70 92 使用cms作为垃圾回收 使用70%后开始CMS收集 为了保证不出现promotion failed(见下面介绍)错误,该值的设置需要满足以下公式CMSInitiatingOccupancyFraction计算公式
调试选项
选项与默认值 默认值与限制 描述
-XX:-CITime 1.4引入。 打印JIT编译器编译耗时。
默认启用
-XX:ErrorFile=./hs_err_pid
-XX:-ExtendedDTraceProbes Java6引入,限于solaris 启用dtrace诊断。
默认不启用
-XX:HeapDumpPath=./java_pid
什么是堆内存快照?
当java进程因OOM或crash被强制退出后,生成hprof(Heap PROFling)格式的堆快照文件。用于出问题后调试,诊断。文件名一般为java_
四、 经验
1.年轻代大小选择
1)响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,年轻代收集发生的频率也是最小的.同时,减少到达年老代的对象.
2)吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用.
3)避免设置过小.当新生代设置过小时会导致:1.YGC次数更加频繁 2.可能导致YGC对象直接进入旧生代,如果此时旧生代满了,会触发FGC.
2.年老代大小选择
1)响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得: 并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在年轻代和年老代回收上的时间比例。
2)吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象.
3.较小堆引起的碎片问题:promotion failed
1)因为年老代的并发收集器使用标记,清除算法,所以不会对堆进行压缩.当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象.但是,当堆空间较小时,运行一段时间以后,就会出现"碎片",如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记,清除方式进行回收.如果出现"碎片",可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩.
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
4.用64位操作系统,Linux下64位的jdk比32位jdk要慢一些,但是吃得内存更多,吞吐量更大
5.XMX和XMS设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力
6.使用CMS的好处是用尽量少的新生代,经验值是128M-256M, 然后老生代利用CMS并行收集, 这样能保证系统低延迟的吞吐效率。 实际上cms的收集停顿时间非常的短,2G的内存, 大约20-80ms的应用程序停顿时间
7.系统停顿的时候可能是GC的问题也可能是程序的问题,多用jmap和jstack查看,或者killall -3 java,然后查看java控制台日志,能看出很多问题。(相关工具的使用方法将在后面的blog中介绍)
8.增加Heap的大小虽然会降低GC的频率,但也增加了每次GC的时间。并且GC运行时,所有的用户线程将暂停,也就是GC期间,Java应用程序不做任何工作。
9.Heap大小并不决定进程的内存使用量。进程的内存使用量要大于-Xmx定义的值,因为Java为其他任务分配内存,例如每个线程的Stack等。
每个线程都有他自己的Stack。-Xss
Stack的大小限制着线程的数量。如果Stack过大就好导致内存溢漏。-Xss参数决定Stack大小,例如-Xss1024K。如果Stack太小,也会导致Stack溢漏。
10.Java线程的内存是位于JVM或操作系统的栈(Stack)空间中,不同于对象——是位于堆(Heap)中。这是很多新手程序员容易误解的地方。注意,“Java线程的内存”这个用词不是指Java.lang.Thread对象的内存,java.lang.Thread对象本身是在Heap中分配的,当调用start()方法之后,JVM会创建一个执行单元,最终会创建一个操作系统的native thread来执行,而这个执行单元或native thread是使用Stack内存空间的。
11.VM进程的内存大致分为Heap空间和Stack空间两部分。Heap又分为Young、Old、Permanent三个代。Stack分为Java方法栈和native方法栈(不做区分),在Stack内存区中,可以创建多个线程栈,每个线程栈占据Stack区中一小部分内存,线程栈是一个LIFO数据结构,每调用一个方法,会在栈顶创建一个Frame,方法返回时,相应的Frame会从栈顶移除(通过移动栈顶指针)。在这每一部分内存中,都有可能会出现溢出错误
12.仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。
13.采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿
14.JVM参数的设置(特别是 –Xmx –Xms –Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold等参数的设置没有一个固定的公式,需要根据PV old区实际数据 YGC次数等多方面来衡量。为了避免promotion faild可能会导致xmn设置偏小,也意味着YGC的次数会增多,处理并发访问的能力下降等问题。每个参数的调整都需要经过详细的性能测试,才能找到特定应用的最佳配置。
五、 常见内存错误及解决方案
1.OutOfMemoryError在开发过程中是司空见惯的,遇到这个错误,新手程序员都知道从两个方面入手来解决:一是排查程序是否有BUG导致内存泄漏;二是调整JVM启动参数增大内存。OutOfMemoryError有好几种情况,每次遇到这个错误时,观察OutOfMemoryError后面的提示信息,就可以发现不同之处,如:
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: unable to create newnative thread
java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Requested array sizeexceeds VM limit
2.java.lang.OutOfMemoryError:Java heap space
1)原因:Heap内存溢出,意味着Young和Old generation的内存不够。
2)解决:调整java启动参数 -Xms -Xmx 来增加Heap内存。
3.java.lang.OutOfMemoryError:unable to create new native thread
1)原因:Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了。
2)解决:由于JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;而系统的用户空间一共是3G,除了Text/Data/BSS/MemoryMapping几个段之外,Heap和Stack空间的总量有限,是此消彼长的。因此遇到这个错误,可以通过两个途径解决:1.通过-Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);2.通过-Xms -Xmx 两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。
4.java.lang.OutOfMemoryError:PermGen space
1)原因:Permanent Generation空间不足,不能加载额外的类。
2)解决:调整-XX:PermSize= -XX:MaxPermSize= 两个参数来增大PermGen内存。一般情况下,这两个参数不要手动设置,只要设置-Xmx足够大即可,JVM会自行选择合适的PermGen大小。
5.java.lang.OutOfMemoryError:Requested array size exceeds VM limit
1)原因:这个错误比较少见(试着new一个长度1亿的数组看看),同样是由于Heap空间不足。如果需要new一个如此之大的数组,程序逻辑多半是不合理的。
2)解决:修改程序逻辑吧。或者也可以通过-Xmx来增大堆内存。
6.java.lang.OutOfMemoryError: GC overhead limit exceeded
1)原因:在GC花费了大量时间,却仅回收了少量内存时,也会报出OutOfMemoryError,我只遇到过一两次。当使用-XX:+UseParallelGC或-XX:+UseConcMarkSweepGC收集器时,在上述情况下会报错,在HotSpot GC Turning文档上有说明:
The parallel(concurrent) collector will throwan OutOfMemoryError if too much time is being spent in garbage collection: ifmore than 98% of the total time is spent in garbage collection and less than 2%of the heap is recovered, an OutOfMemoryError will be thrown.
对这个问题,一是需要进行GC turning,二是需要优化程序逻辑。
7.java.lang.StackOverflowError
1)原因:这也内存溢出错误的一种,即线程栈的溢出,要么是方法调用层次过多(比如存在无限递归调用),要么是线程栈太小。
2)解决:优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小。
8.concurrentmode failure
1)原因:原文是这样描述的(if theconcurrent collector is unable tofinish reclaiming the unreachable objectsbefore the tenured generation fillsup, or if an allocation cannot be satisfiedwith the available free space blocksin the tenured generation, then theapplication is paused and the collection iscompleted with all the applicationthreads stopped),简单解释就是old gen剩余的内存不足以满足来自于young gen的垃圾回收,导致jvm通过卸载已经生成的反射类来释放足够的内存。这种现象会造成应用较长时间的中断,从而影响性能。所以理论上应该保证eden + from survivor < old gen区剩余内存。
2)通过设置-XX:CMSInitiatingOccupancyFraction=50(此值的初始值为10,即预留10%的空间给minor collection),当old gen已经收集了50%的内存后,就开始进行major collection,从而保证old gen 始终预留50%的可用内存。
3)这种设置虽然会提高major collection的频率,但是根据目前major collection的频率来看(大概几个小时一次)足以承受。
六、 参考资料
1.Java6性能调优白皮书
http://java.sun.com/performance/reference/whitepapers/6_performance.html
2.Java6 GC调优指南
http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html
3.更为全面的options列表
http://blogs.sun.com/watt/resource/jvm-options-list.html
4.Java 6 JVM参数选项大全(中文版)
http://www.blogjava.net/bitbit/archive/2009/11/30/304247.html
5.http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
6.http://www.open-open.com/home/space-1-do-blog-id-4782.html
7.http://www.cnblogs.com/jack204/archive/2012/07/02/2572932.html
8.http://www.cnblogs.com/likehua/p/3369823.html
9.http://blog.sina.com.cn/s/blog_707577700100vy4m.html
10.http://dmouse.iteye.com/blog/1264118
---------------------
作者:Tate-Ling
来源:CSDN
原文:https://blog.csdn.net/szzt_lingpeng/article/details/50463375