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

引用

http://shensy.iteye.com/blog/1678246
一、基本概念
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) 
优化参数如下:
Shell代码  收藏代码
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标准输出都重定向到空设备文件,也就是不输出任何信息到终端,就是不显示任何信息。

猜你喜欢

转载自dannyhz.iteye.com/blog/2355137
今日推荐