




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、JVM内存模型及垃圾收集策略解析(1)2010-02-22 08:58 狂放不羁 JavaEye字号:T|T垃圾收集器策略从20世纪60年代就已经流行起来了,相比于其他编程语言,Java语言是目前使用最多的依赖于垃圾收集器的语言。AD:WOT2015 互联网运维与开发者大会 热销抢票JVM内存模型是Java的核心技术之一,之前51CTO曾为大家介绍过JVM分代垃圾回收策略的基础概念,现在很多编程语言都引入了类似Java JVM的内存模型和垃圾收集器的机制,下面我们将主要针对Java中的JVM内存模型及垃圾收集的具体策略进行综合的分析。一 JVM内存模型1.1 Java栈Java栈是与每一个线程
2、关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文。栈空间随着线程的终止而释放。StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的。1.2 堆Java中堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等。1.2.1 GenerationJVM堆一般又可以分为以下三部分: PermPerm代主要保存class,method,filed对象,这部门的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及
3、到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。 TenuredTenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。 YoungYoung区被划
4、分为三部分,Eden区和两个大小严格相同的Survivor区,其中Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Young区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。1.2.2 Sizing the GenerationsJVM提供了相应的参数来对内存大小进行配置。正如上面描述,JVM中堆被分为了3个大的区间,同时JVM也提供了一些选项对Young,Tenured的大小进行控制。 Total Heap-Xms
5、:指定了JVM初始启动以后初始化内存-Xmx:指定JVM堆得最大内存,在JVM启动以后,会分配-Xmx参数指定大小的内存给JVM,但是不一定全部使用,JVM会根据-Xms参数来调节真正用于JVM的内存-Xmx -Xms之差就是三个Virtual空间的大小 Young Generation-XX:NewRatio=8意味着tenured 和 young的比值8:1,这样eden+2*survivor=1/9堆内存-XX:SurvivorRatio=32意味着eden和一个survivor的比值是32:1,这样一个Survivor就占Young区的1/34.-Xmn 参数设置了年轻代的大小 Per
6、m Generation-XX:PermSize=16M -XX:MaxPermSize=64MThread Stack-XX:Xss=128K1.3 堆栈分离的好处呵呵,其它的先不说了,就来说说面向对象的设计吧,当然除了面向对象的设计带来的维护性,复用性和扩展性方面的好处外,我们看看面向对象如何巧妙的利用了堆栈分离。如果从JAVA内存模型的角度去理解面向对象的设计,我们就会发现对象它完美的表示了堆和栈,对象的数据放在堆中,而我们编写的那些方法一般都是运行在栈中,因此面向对象的设计是一种非常完美的设计方式,它完美的统一了数据存储和运行。二 JAVA垃圾收集器2.1 垃圾收集简史垃圾收集提供了内
7、存管理的机制,使得应用程序不需要在关注内存如何释放,内存用完后,垃圾收集会进行收集,这样就减轻了因为人为的管理内存而造成的错误,比如在C+语言里,出现内存泄露时很常见的。Java语言是目前使用最多的依赖于垃圾收集器的语言,但是垃圾收集器策略从20世纪60年代就已经流行起来了,比如Smalltalk,Eiffel等编程语言也集成了垃圾收集器的机制。2.2 常见的垃圾收集策略所有的垃圾收集算法都面临同一个问题,那就是找出应用程序不可到达的内存块,将其释放,这里面得不可到达主要是指应用程序已经没有内存块的引用了,而在JAVA中,某个对象对应用程序是可到达的是指:这个对象被根(根主要是指类的静态变量,
8、或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可到达的对象引用。2.2.1 Reference Counting(引用计数)引用计数是最简单直接的一种方式,这种方式在每一个对象中增加一个引用的计数,这个计数代表当前程序有多少个引用引用了此对象,如果此对象的引用计数变为0,那么此对象就可以作为垃圾收集器的目标对象来收集。优点:简单,直接,不需要暂停整个应用缺点:1.需要编译器的配合,编译器要生成特殊的指令来进行引用计数的操作,比如每次将对象赋值给新的引用,或者者对象的引用超出了作用域等。2.不能处理循环引用的问题2.2.2 跟踪收集器跟踪收集器首先要暂停整个应用程序,然后开始从根对象扫描
9、整个堆,判断扫描的对象是否有对象引用,这里面有三个问题需要搞清楚:1如果每次扫描整个堆,那么势必让GC的时间变长,从而影响了应用本身的执行。因此在JVM里面采用了分代收集,在新生代收集的时候minor gc只需要扫描新生代,而不需要扫描老生代。2JVM采用了分代收集以后,minor gc只扫描新生代,但是minor gc怎么判断是否有老生代的对象引用了新生代的对象,JVM采用了卡片标记的策略,卡片标记将老生代分成了一块一块的,划分以后的每一个块就叫做一个卡片,JVM采用卡表维护了每一个块的状态,当JAVA程序运行的时候,如果发现老生代对象引用或者释放了新生代对象的引用,那么就JVM就将卡表的状
10、态设置为脏状态,这样每次minor gc的时候就会只扫描被标记为脏状态的卡片,而不需要扫描整个堆。具体如下图:3GC在收集一个对象的时候会判断是否有引用指向对象,在JAVA中的引用主要有四种:Strong reference,Soft reference,Weak reference,Phantom reference. Strong Reference强引用是JAVA中默认采用的一种方式,我们平时创建的引用都属于强引用。如果一个对象没有强引用,那么对象就会被回收。1. publicvoidtestStrongReference() 2. Objectreferent=newObject();
11、 3. ObjectstrongReference=referent; 4. referent=null; 5. System.gc(); 6. assertNotNull(strongReference); 7. Soft Reference软引用的对象在GC的时候不会被回收,只有当内存不够用的时候才会真正的回收,因此软引用适合缓存的场合,这样使得缓存中的对象可以尽量的再内存中待长久一点。1. PublicvoidtestSoftReference() 2. Stringstr=test; 3. SoftReferencesoftreference=newSoftReference(str)
12、; 4. str=null; 5. System.gc(); 6. assertNotNull(softreference.get(); 7. Weak reference弱引用有利于对象更快的被回收,假如一个对象没有强引用只有弱引用,那么在GC后,这个对象肯定会被回收。1. PublicvoidtestWeakReference() 2. Stringstr=test; 3. WeakReferenceweakReference=newWeakReference(str); 4. str=null; 5. System.gc(); 6. assertNull(weakReference.ge
13、t(); 7. Phantom reference Mark-Sweep Collector(标记-清除收集器)标记清除收集器最早由Lisp的发明人于1960年提出,标记清除收集器停止所有的工作,从根扫描每个活跃的对象,然后标记扫描过的对象,标记完成以后,清除那些没有被标记的对象。优点:1 解决循环引用的问题2 不需要编译器的配合,从而就不执行额外的指令缺点:1每个活跃的对象都要进行扫描,收集暂停的时间比较长。 Copying Collector(复制收集器)复制收集器将内存分为两块一样大小空间,某一个时刻,只有一个空间处于活跃的状态,当活跃的空间满的时候,GC就会
14、将活跃的对象复制到未使用的空间中去,原来不活跃的空间就变为了活跃的空间。复制收集器具体过程可以参考下图:优点:1 只扫描可以到达的对象,不需要扫描所有的对象,从而减少了应用暂停的时间缺点:1需要额外的空间消耗,某一个时刻,总是有一块内存处于未使用状态2复制对象需要一定的开销 Mark-Compact Collector(标记-整理收集器)标记整理收集器汲取了标记清除和复制收集器的优点,它分两个阶段执行,在第一个阶段,首先扫描所有活跃的对象,并标记所有活跃的对象,第二个阶段首先清除未标记的对象,然后将活跃的的对象复制到堆得底部。标记整理收集器的过程示意图请参考下图:Mark-com
15、pact策略极大的减少了内存碎片,并且不需要像Copy Collector一样需要两倍的空间。2.3 JVM的垃圾收集策略GC的执行时要耗费一定的CPU资源和时间的,因此在JDK1.2以后,JVM引入了分代收集的策略,其中对新生代采用Mark-Compact策略,而对老生代采用了“Mark-Sweep的策略。其中新生代的垃圾收集器命名为“minor gc”,老生代的GC命名为Full Gc 或者Major GC.其中用System.gc()强制执行的是Full Gc.2.3.1 Serial CollectorSerial Collector是指任何时刻都只有一个线程进行垃圾收集,这种策略有一
16、个名字“stop the whole world,它需要停止整个应用的执行。这种类型的收集器适合于单CPU的机器。Serial Copying Collector此种GC用-XX:UseSerialGC选项配置,它只用于新生代对象的收集。1.5.0以后。-XX:MaxTenuringThreshold来设置对象复制的次数。当eden空间不够的时候,GC会将eden的活跃对象和一个名叫From survivor空间中尚不够资格放入Old代的对象复制到另外一个名字叫To Survivor的空间。而此参数就是用来说明到底From survivor中的哪些对象不够资格,假如这个参数设置为31,那么也就
17、是说只有对象复制31次以后才算是有资格的对象。这里需要注意几个个问题: From Survivor和To survivor的角色是不断的变化的,同一时间只有一块空间处于使用状态,这个空间就叫做From Survivor区,当复制一次后角色就发生了变化。 如果复制的过程中发现To survivor空间已经满了,那么就直接复制到old generation. 比较大的对象也会直接复制到Old generation,在开发中,我们应该尽量避免这种情况的发生。Serial Mark-Compact Collector串行的标记-整理收集器是JDK5 update6之前默认的老生代的垃圾收集器,此收集使
18、得内存碎片最少化,但是它需要暂停的时间比较长。2.3.2 Parallel CollectorParallel Collector主要是为了应对多CPU,大数据量的环境。Parallel Collector又可以分为以下两种:Parallel Copying Collector此种GC用-XX:UseParNewGC参数配置,它主要用于新生代的收集,此GC可以配合CMS一起使用。1.4.1以后Parallel Mark-Compact Collector,此种GC用-XX:UseParallelOldGC参数配置,此GC主要用于老生代对象的收集。1.6.0Parallel scavenging
19、 Collector此种GC用-XX:UseParallelGC参数配置,它是对新生代对象的垃圾收集器,但是它不能和CMS配合使用,它适合于比较大新生代的情况,此收集器起始于jdk 1.4.0。它比较适合于对吞吐量高于暂停时间的场合,Serial gc和Parallel gc可以用如下的图来表示:2.3.3 Concurrent CollectorConcurrent Collector通过并行的方式进行垃圾收集,这样就减少了垃圾收集器收集一次的时间,这种GC在实时性要求高于吞吐量的时候比较有用。此种GC可以用参数-XX:UseConcMarkSweepGC配置,此GC主要用于老生代和Perm
20、代的收集。【编辑推荐】1. Java的未来:百家争鸣的JVM2. 有关JVM处理Java数组方法的思考3. JVM分代垃圾回收策略的基础概念4. JVM概念之Java对象的大小与引用类型5. JVM基础概念总结:数据类型、堆与栈this.memoryLimit = (long)(jobConf.getLong(MRJobConfig.REDUCE_MEMORY_TOTAL_BYTES, Math.min(Runtime.getRuntime().maxMemory(), Integer.MAX_VALUE) * maxInMemCopyUse);maxMemory()这个方法返回的是java虚
21、拟机(这个进程)能构从操作系统那里挖到的最大的内存,以字节为单位.totalMemory()这个方法返回的是java虚拟机现在已经从操作系统那里挖过来的内存大小,也就是java虚拟机这个进程当时所占用的所有内存。freeMemory为当前jvm中没有使用的内存。JVM基础:JVM参数设置、分析央邦0首付,低押金先就业后付款上海央邦学一送一,超值!50000年薪起,顶级4G手机开发!【安博亚威】CCIE考试通过率第一!大数据与互联网技术峰会免费注册java培训到达内,行业领先品牌中国IT实验室收集整理佚名2012-2-25 9:18:41保存本文推荐给好友收藏本页欢迎进入Java社区论坛,与20
22、0万技术人员互动交流 进入参数名称含义默认值-Xms初始堆大小物理内存的1/64(1GB)默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.-Xmx最大堆大小物理内存的1/4(113543K(130112K), 0.0094143 secsFull GC 121376K-10414K(130112K), 0.0650971 secs-XX:+PrintGCDetails输出形式:GC DefNew: 8614K-781K(9088K), 0.0123035 secs 118250K-113543K(130112K), 0.012
23、4633 secsGC DefNew: 8614K-8614K(9088K), 0.0000665 secsTenured: 112761K-10414K(121024K), 0.0433488 secs 121376K-10414K(130112K), 0.0436268 secs-XX:+PrintGCTimeStamps-XX:+PrintGC:PrintGCTimeStamps可与-XX:+PrintGC -XX:+PrintGCDetails混合使用输出形式:11.851: GC 98328K-93620K(130112K), 0.0082960 secs-XX:+PrintGCAp
24、plicationStoppedTime打印垃圾回收期间程序暂停的时间.可与上面混合使用输出形式:Total time for which application threads were stopped: 0.0468229 seconds-XX:+PrintGCApplicationConcurrentTime打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用输出形式:Application time: 0.5291524 seconds-XX:+PrintHeapAtGC打印GC前后的详细堆栈信息-Xloggc:filename把相关日志信息记录到文件以便分析.与上面几个配合使用
25、-XX:+PrintClassHistogramgarbage collects before printing the histogram.-XX:+PrintTLAB查看TLAB空间的使用情况XX:+PrintTenuringDistribution查看每次minor GC后新的存活周期的阈值Desired survivor size 1048576 bytes, new threshold 7 (max 15)new threshold 7即标识新的存活周期的阈值为7。GC性能方面的考虑 对于GC的性能主要有2个方面的指标:吞吐量throughput(工作时间不算gc的时间占总的时间比)
26、和暂停pause(gc发生时app对外显示的无法响应)。 1. Total Heap 默认情况下,vm会增加/减少heap大小以维持free space在整个vm中占的比例,这个比例由MinHeapFreeRatio和MaxHeapFreeRatio指定。 一般而言,server端的app会有以下规则: 对vm分配尽可能多的memory; 将Xms和Xmx设为一样的值。如果虚拟机启动时设置使用的内存比较小,这个时候又需要初始化很多对象,虚拟机就必须重复地增加内存。 处理器核数增加,内存也跟着增大。 2. The Young Generation 另外一个对于app流畅性运行影响的因素是youn
27、g generation的大小。young generation越大,minor collection越少;但是在固定heap size情况下,更大的young generation就意味着小的tenured generation,就意味着更多的major collection(major collection会引发minor collection)。 NewRatio反映的是young和tenured generation的大小比例。NewSize和MaxNewSize反映的是young generation大小的下限和上限,将这两个值设为一样就固定了young generation的大小(
28、同Xms和Xmx设为一样)。 如果希望,SurvivorRatio也可以优化survivor的大小,不过这对于性能的影响不是很大。SurvivorRatio是eden和survior大小比例。 一般而言,server端的app会有以下规则: 首先决定能分配给vm的最大的heap size,然后设定最佳的young generation的大小; 如果heap size固定后,增加young generation的大小意味着减小tenured generation大小。让tenured generation在任何时候够大,能够容纳所有live的data(留10%-20%的空余)。 经验&规则 年轻
29、代大小选择 ?响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。 避免设置过小。当新生代设置过小时会导致:1.YGC次数更加频繁 2.可能导致YGC对象直接进入旧生代,如果此时旧生代满了,会触发FGC. 年老代大小选择 ?响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造
30、成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得: 并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在年轻代和年老代回收上的时间比例。 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。 较小堆引起的碎片问题 因为年老代的并发收集器使用标记,清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现碎
31、片,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记,清除方式进行回收。如果出现碎片,可能需要进行如下配置: -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。 -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩 用64位操作系统,Linux下64位的jdk比32位jdk要慢一些,但是吃得内存更多,吞吐量更大 XMX和XMS设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力
32、使用CMS的好处是用尽量少的新生代,经验值是128M256M, 然后老生代利用CMS并行收集, 这样能保证系统低延迟的吞吐效率。 实际上cms的收集停顿时间非常的短,2G的内存, 大约2080ms的应用程序停顿时间 系统停顿的时候可能是GC的问题也可能是程序的问题,多用jmap和jstack查看,或者killall -3 java,然后查看java控制台日志,能看出很多问题。(相关工具的使用方法将在后面的blog中介绍) 仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。 采
33、用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿 JVM参数的设置(特别是 Xmx Xms Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold等参数的设置没有一个固定的公式,需要根据PV old区实际数据 YGC次数等多方面来衡量。为了避免promotion faild可能会导致xmn设置偏小,也意味着YGC的次数会增多,处理并发访问的能力下降等问题。每个参数的调整都需要经过详细的性能测试,才能找到特定应用的最佳配置。 promotion failed: 垃圾回收时promotion
34、 failed是个很头痛的问题,一般可能是两种原因产生,第一个原因是救助空间不够,救助空间里的对象还不应该被移动到年老代,但年轻代又有很多对象需要放入救助空间;第二个原因是年老代没有足够的空间接纳来自年轻代的对象;这两种情况都会转向Full GC,网站停顿时间较长。 解决方方案一: 第一个原因我的最终解决办法是去掉救助空间,设置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二个原因我的解决办法是设置CMSInitiatingOccupancyFraction为某个值(假设70),这样年老代空间到70%时就开始执行CMS,年老代有足够
35、的空间接纳来自年轻代的对象。 解决方案一的改进方案: 又有改进了,上面方法不太好,因为没有用到救助空间,所以年老代容易满,CMS执行会比较频繁。我改善了一下,还是用救助空间,但是把救助空间加大,这样也不会有promotion failed。具体操作上,32位Linux和64位Linux好像不一样,64位系统似乎只要配置MaxTenuringThreshold参数,CMS还是有暂停。为了解决暂停问题和promotion failed问题,最后我设置-XX:SurvivorRatio=1 ,并把MaxTenuringThreshold去掉,这样即没有暂停又不会有promotoin failed,而且更重要的是,年老代和永久代上升非常慢(因为好多对象到不了年老代就被回收了),所以CMS执行频率非常低,好几个小时才执行一次,这样,服务器都不用重启了。 -Xmx4000M -Xms4000M -Xmn600M -XX:PermSize=500M -X
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度宾馆客房租赁与品牌战略合作协议
- 二零二五年度工业原料采购合同书
- b站实习个人总结
- 二零二五年度北京个人汽车租赁节假日特惠合同
- 2025版紧急备用电源UPS采购及租赁合同
- 二零二五年度安徽省租赁房屋室内空气质量检测合同
- 二零二五年度绿色建筑安居工程合同
- 二零二五年车辆租赁及车辆租赁服务评价体系合同
- 2025版物业管理与房屋租赁综合服务合同
- 二零二五年度北京全景演播室租赁及拍摄协议
- 保姆级别CDH安装运维手册
- 园林绿化及广场施工方案
- 可下载打印的公司章程
- 129平米全包装修报价明细表
- DB32∕T 1332-2009 宽体金线蛭养殖技术规程
- --水库除险加固工程下闸蓄水验收建设管理工作报告
- 五十铃、丰田全球化研究
- 沪教版九年级上册化学全册ppt课件
- 智能魔镜项目可行性研究报告【参考模板】
- 印刷包装企业组织机构及职能分配图
- 信息化项目预算明细表
评论
0/150
提交评论