`

JVM分代、垃圾回收概念与一个JVM参数调优示例

 
阅读更多

前一段时间下班看了不少JVM相关资料。总结分享一下,希望对看到的人有所帮助。

大部分整理自网络,同时也可参考我的JVM分类blog:http://shensy.iteye.com/category/211309

----------------------------------------------

一、基本概念

1、现在的JVM垃圾收集主要使用分代回收算法:即把对象分为青年代,老年代,持久代,处于不同代中的对象采用不同的垃圾回收算法,例如:复制,标记整理,增量收集。

2、JVM堆中的分代

(1)年轻代

分为一个eden区和2个surivor区,大部分对象在eden区中生成,当eden区满时,还存活的对象将被复制到一个surivor区(surivor总有一个是空的),当这个surivor满时,里面存活的对象将被复制到另外一个surivor(同时清空第一个surivor中的垃圾对象),当那个surivor区也满了时,第一个surivor复制过来还存活的对象将被复制到年老区(同一个区可能同时存在从eden复制过来和从前一个surivor复制过来的对象,但复制到年老区的只有从前一个surivor过来的对象)。

(2)年老代

一般来说年老代存放的是生命期较长的对象。

(3)持久代

用于存放静态文件,持久代对垃圾回收没有显著影响,但有些应用可能动态生成或者调用一些class,这时需要设置一个比较大的持久代空间来存放这些运行中新增的类,持久代通过-XX:MaxPermSize=进行设置。

3、GC类型

(1)Scavenge GC(Young GC)

年轻代Eden区申请空间失败时,触发YOUNG GC.在堆Eden区域执行GC,清除非存活对象.把存活对象放至Survivor区,然后整理2个Survivor区.

(2)Full GC

对整个堆进行整理,包括年轻代,年老代,永久代。FullGC比YoungGC要慢,导致FullGC原因是:

年老代被写满、永久代被写满、System.gc()被调用、上一次GC之后Heap的各域分配策略动态变化。

4、垃圾回收器

串行收集器:应用停止,单线程进行所有回收工作。适用于单处理器的机器。

并行收集器:应用停止,多线程进行并发垃圾回收,对年轻代和年老代进行并行垃圾回收(可通过参数配置),但标记-清除为单线程。

并发收集器:大部分工作都并发进行(垃圾回收和标记-清除),垃圾回收只暂停很少的时间。在应用不停止的情况下使用独立的垃圾回收线程。即并发收集是在应用运行时进行收集。

垃圾回收起点

垃圾回收的起点是一些根对象(java栈, 静态变量, 寄存器...)。

栈是真正进行程序执行地方,所以要获取哪些对象正在被使用,则需要从Java栈开始。同时,一个栈是与一个线程对应的,因此,如果有多个线程的话,则必须对这些线程对应的所有的栈进行检查。

基本垃圾回收算法:http://pengjiaheng.iteye.com/blog/520228

三种垃圾收集器详解:http://pengjiaheng.iteye.com/blog/528034

5、JVM结构

程序计数器:当前线程所执行的字节码的行号指示器。每个线程独有一个计数器。该部分区域是JVM规范中唯一没有规定任何OOM Error情况的区域。

虚拟机栈:存储基本数据类型、对象引用、方法出口、局部变量,也是线程私有的。

本地方法栈:执行Native方法。

:存储对象,各线程共享。详情见堆介绍。

方法区:各线程共享内存、用于存储加载的类信息、常量、静态变量。

常量池:是方法区的一部分,存储编译期生成的字面量。运行期也可能将新的常量放入池中,例如:string.intern()方法。

直接内存:NIO引入基于Channel和Buffer的IO方式,可以使用Native函数库直接分配堆外内存。然后通过Java堆里面的DirectByteBuffer对象作为内存引用进行操作。

其中:

栈是运行时单位,堆是存储单位。

每个线程都有一个线程栈,该线程独享该栈内空间,存储局部变量、程序运行状态、方法返回值等。

堆是线程内共享,只存储对象。

Java中main就是栈的起始点也是程序起始点。

Java方法调用参数都是传值,传引用可以理解为传引用值。

堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。

堆是为栈进行数据存储服务,就是一块共享的内存。

对象引用类型

分为强引用、软引用、弱引用和虚引用。

强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。

软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。

弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

解决内存碎片问题

在程序运行一段时间以后,如果不进行内存整理,就会出现零散的内存碎片。碎片最直接的问题就是会导致无法分配大块的内存空间,以及程序运行效率降低。

基本垃圾回收算法中,“复制”方式和“标记-整理”方式,都可以解决碎片的问题。

解决应用暂停问题

并发垃圾回收算法,使用这种算法,垃圾回收线程与程序运行线程同时运行。在这种方式下,解决了暂停的问题,但是因为需要在新生成对象的同时又要回收对象,算法复杂性会大大增加,系统的处理能力也会相应降低,同时,“碎片”问题将会比较难解决。

二、JVM参数调优示例

本示例是公司某服务器在高并发生产环境正在使用的JVM启动参数。

使用的Java版本信息:java -version

java version "1.6.0_30"
Java(TM) SE Runtime Environment (build 1.6.0_30-b12)
Java HotSpot(TM) 64-Bit Server VM (build 20.5-b03, mixed mode)

优化参数如下:

nohup java -Xms4g -Xmx4g -Xmn3g -Xss256k \
    -server \
    -XX:PermSize=64M \
    -XX:MaxPermSize=64M \
    -XX:+UseConcMarkSweepGC \
    -XX:+UseAdaptiveSizePolicy \
    -XX:+CMSClassUnloadingEnabled \
    -XX:+UseCMSCompactAtFullCollection \
    -XX:+DisableExplicitGC \
    -XX:CMSFullGCsBeforeCompaction=10 \
    -XX:CMSMaxAbortablePrecleanTime=5 \
    -XX:+HeapDumpOnOutOfMemoryError \
    -jar server.jar 2>&1 >/dev/null &

逐个参数进行分析:
-Xms4g:初始堆大小4g。
-Xmx4g:最大堆大小4g。
-Xmn3g:设置年轻代大小3g。
整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

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

-server:jvm以server模式启动。
JVM Server模式与client模式启动,最主要的差别在于:-Server模式启动时,启动速度较慢(比client模式慢大概10%),但是一旦运行起来后,性能将会有很大的提升。JVM如果不显式指定是-Server模式还是-client模式,JVM能够进行自动判断(J2SE5.0检测的根据是至少2个CPU和最低2GB内存)。
通过运行:java -version来查看jvm默认工作在什么模式。

-XX:PermSize=64M:设置永久代(方法区)初始大小
在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来的。类(静态)变量也存储在方法区中。

-XX:MaxPermSize=64M 设置方法区最大值。

-XX:+UseConcMarkSweepGC:设置年老代为并发收集。
并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。

-XX:+UseAdaptiveSizePolicy:并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时,一直打开。(-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。)

-XX:+CMSClassUnloadingEnabled:设置对Perm区进行回收。
如果使用Spring/Hibernate框架大量采用cglib,导致生成的Proxy会比较多,而这些是存放在PermGen区域,SUN JDK默认情况下不会去回收,必须加上-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled参数,JDK才会去回收这部分数据。
使用并发收集器CMS(ConcMarkSweep)策略时,必须有:-XX:+CMSPermGenSweepingEnabled和-XX:+CMSClassUnloadingEnabled来配合同时启用,才可以对PermGen进行GC(
实际主要参数为:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
)。只是参数:-XX:+CMSPermGenSweepingEnabled在JDK1.6中是不需要的。

-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。
可能会影响性能,但是可以消除内存碎片。

-XX:CMSFullGCsBeforeCompaction=10:打开年老代压缩的情况下,设置10次Full GC后,对年老代进行压缩。
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。

-XX:+DisableExplicitGC:关闭System.gc()
System.gc()强迫VM执行一个堆的“全部清扫”,全部清扫比一个常规GC操作要昂贵好几个数量级。该参数将System.gc()调用转换成一个空操作.

-XX:CMSMaxAbortablePrecleanTime=5:调小CMSMaxAbortablePrecleanTime的值=5
CMS GC须要经过较多步骤才能完成一次GC的动作,在minor GC较为频繁的情况下,很有可能造成CMS GC尚未完成,从而造成concurrent mode failure,这种情况下,降低minor GC触发的频率是一种要领,另外一种要领则是加快CMS GC执行时间,在CMS的整个步骤中,JDK 5.0+、6.0+的有些版本在CMS-concurrent-abortable-preclean-start和CMS-concurrent-abortable-preclean这两步间有可能会耗费很长的时间,导致可回收的旧生代的对象很长时间后才被回收,这是Sun JDK CMS GC的一个bug[1],如通过PrintGCDetails观察到这两步之间耗费了较长的时间,可以通过-XX: CMSMaxAbortablePrecleanTime配置较小的值,以保证CMS GC尽快完成对象的回收,防止concurrent mode failure的现象。

-XX:+HeapDumpOnOutOfMemoryError
抛出OutOfMemoryError时,该命令通知JVM拍摄一个“堆转储快照”,并将其保存在一个文件中以便处理,通常使用jhat工具。可以使用相应的-XX:HeapDumpPath标志指定到保存文件的实际路径(确保文件系统和必须要有权限可以在其中写入)。 

 

其它相关命令
nohup和最后的&:代表以后台服务方式运行,\代表shell中的换行.
-jar server.jar:执行jar包。

关于2>&1 >/dev/null
shell中0>表示stdin标准输入;1>表示stdout标准输出;2>表示stderr错误输出;
&可以理解为是"等同于"的意思,2>&1,即表示2的输出重定向等同于1
符号> 等价于1>(系统默认为1,省略了先);所以">/dev/null"等同于"1>/dev/null"
/dev/null代表空设备文件。
该语句解释为:stderr错误输出与stdout标准输出都重定向到空设备文件,也就是不输出任何信息到终端,就是不显示任何信息。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics