JVM性能调优1:JVM性能调优理论及实践(收集整理)

本文详细介绍了JVM内存结构、垃圾收集的性能指标和不同类型,包括吞吐量、暂停时间和内存足迹。讨论了各种引用类型、内存分配回收次序,以及不同垃圾收集算法,如标记-清除、复制、标记-整理等。此外,还涵盖了垃圾收集器的类型,如串行、并行和并发收集器,并提供了JVM调优的实践经验,包括参数设置和案例分析。
摘要由CSDN通过智能技术生成
本系列包括:


注:本文部分内容收集整理了网上的资料。

1.      内存结构


1.1.     分代结构图


注意:


JVM中,非堆内存,根据模式不同分为不同的几个部分。


-Server下:非堆包括:持久代和代码缓存(Code cache


-client下:非堆包括:持久代、代码缓存(Code cache)、Perm rwperm ro


2.      性能指标


对于垃圾收集的性能,主要有两个指标。


吞吐量throughput)指用来进行垃圾收集之外工作所用的时间占总时间的百分比,一般要通过长时间的观察和测量。吞吐量包括了分配内存所花费的时间在内(一般来说无需对分配进行调优)。


Throughput is the percentage of total time not spent in garbage collection,considered over long periods of time. Throughput includes time spent inallocation (but tuning for speed of allocation is generally not needed.) fromTuning GC with jdk 1.5


暂停Pause)指由于进行垃圾收集而导致应用无法响应的时间。


Pauses are the times when an application appears unresponsive becausegarbage collection is occurring.


Footprint is the working set of a process, measured in pages and cachelines. On systems with limited physical memory or many processes, footprint maydictate scalability.      Promptnessis the time between when an object becomes dead and when the memory becomesavailable, an important consideration for distributed systems, including remotemethod invocation (RMI). fromTuning GC with jdk 1.5


用户对于垃圾收集有不同的需求,例如,对于Web服务器应用来说,吞吐量是要着重考虑的,而暂停时间可能由于网络的反应时间而不那么明显;而对于一个交互式图形界面的应用来说,即使是短暂的暂停都会带来非常不好的用户体验。


In general, aparticular generation sizing chooses a trade-off between these considerations.For example, a very largeyoung generation may maximize throughput,but does so at the expense of footprint, promptness, and pause times.younggeneration pauses can be minimized by using a smallyoung generationat the expense of throughput. To a first approximation, the sizing of onegeneration does not affect the collection frequency and pause times for anothergeneration .fromTuning GC with jdk 1.5


通常来说,如何设置代的大小是在这些考虑因素之间作出的一个权衡。例如,将新生代设置得很大会得到很好的吞吐性能,但是会增加暂停时间;反之,较小的新生代设置会减小暂停时间,但是降低了吞吐量。


一个代的大小不应该影响在其他代上进行垃圾收集的频率和暂停时间。


3.      引用类型


对象引用类型分为强引用、软引用、弱引用和虚引用


3.1.     强引用


就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。


3.2.     软引用


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


3.3.     弱引用


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


3.4.     虚引用


 


4.      内存分配回收次序


新生代是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。


新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivorpace),所有的类都是在endennew出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。


Eden的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对enden区进行垃圾回收,将enden区中的不再被其他对象所引用的对象进行销毁(次收集)。


然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收(次收集),然后移动到1区。那如果1区也满了呢?(次收集)


再移动到养老区。


 



新生代采用复制算法,old区采用标记清除算法。


无论是复制算法还是标记清除算法,在垃圾收集期间都要暂停客户的应用程序(cms垃圾收集器除外,它在初始标记和重新标记时暂停,并发标记和并发清除时,客户线程不暂停)。


无论是复制算法还是标记清除算法,在最开始,倒要标记活动的对象,即对象的可达性分析,在这里,必须要求对象是一致的(即可达性分析期间,内存状态是冻结的);在cms收集器是,在初始标记(对root对象标记时,要求冻结)冻结,但此时会产生浮动垃圾,即在并发标记时,由分配了新的对象(因为没有冻结)。


综述,基本过程是,复制算法:标记(暂停)-->复制(暂停,因对象地址发生变化);标记清除整理算法:标记(暂停)-->清除整理(消除碎片,暂停,因对象地址发生变化)。


root对象标记很快,对内存扫描分析,可达性分析过程很慢。清除很慢(内存回收),内存整理、复制(慢)。


内存分配需要时间。


5.      对象标记算法(Object Marking Algorithms


5.1.     引用计数法(ReferenceCounting


堆中每一个对象都有一个引用计数。当新创建一个对象,或者有变量被赋值为这个对象的引用,则这个对象的引用计数加1;当一个对象的引用超过生存期或者被设置一个新的值时,这个对象的引用计数减1。当对象的引用计数变为0时,就可以被当作垃圾收集。


这种方法的好处是垃圾收集较快,适用于实时环境。缺点是这种方法无法监测出循环引用。例如对象A引用对象B,对象B也引用对象A,则这两个对象可能无法被垃圾收集器收集。因此这种方法是垃圾收集的早期策略,现在很少使用。


5.2.     根搜索算法(Garbage Collection Roots Tracing


5.2.1.    基本思想


这种方法把每个对象看作图中一个节点,对象之间的引用关系为图中各节点的邻接关系。垃圾收集器从一个或数个根结点遍历对象图,如果有些对象节点永远无法到达,则这个对象可以被当作垃圾回收。


容易发现,这种方法可以检测出循环引用,避免了引用计数法的缺点,较为常用。步骤如下:


  1. 选定一些对象,作为 GC Roots,组成基对象集;

  2. 由基对象集内的对象出发,搜索所有可达的对象;

  3. 其余的不可达的对象,就是可以被回收的对象。

    这里的可达不可达与图论中的定义一样,所有的对象被看做点,引用被看做有向连接,整个引用关系就是一个有向图。在引用计数法中提到的循环引用,其实就是有向图中有环的情况,即构成有向有环图。引用计数法不适用于有向有环图,而根搜索算法适用于所有有向图,包括有环的和无环的。


5.2.2.    GCRoots


如果你的逻辑思维够清晰,你会说一定与选取基对象集的方法有关。是的,没错。选取 GC Roots 组成基对象集,其实就是选取如下这些对象:


《深入理解 Java 虚拟机:JVM高级特性与最佳实践》一书中提到的 GC Roots 为:


1.     方法区(Method Area,即 Non-Heap)中的类的 static 成员引用的对象,和 final成员引用的对象;


2.     Java方法栈(Java Method Stack)的局部变量表(Local Variable Table)中引用的对象;


3.     原生方法栈(Native Method Stack)中 JNI中引用的对象。


但显然不够全面,[参考2]中提到的要更全面:(March 6th,2012 update


1.     由系统类加载器加载的类相应的对象:这些类永远不会被卸载,且这些类创建的对象都是 static 的。注意用户使用的类加载器加载的类创建的对象,不属于 GC Roots,除非是 java.lang.Class 的相应实例有可能会称为其他类的 GC Roots


2.     正在运行的线程。


3.     Java方法栈(Java Method Stack)的局部变量表(Local Variable Table)中引用的对象。


4.     原生方法栈(Native Method Stack)的局部变量表(Local Variable Table)中引用的对象。


5.     JNI中引用的对象。


6.     同步监控器使用的对象。


7.      JVM GC控制的对象:这些对象是用于 JVM内部的,是实现相关的。一般情况下,可能包括系统类加载器(注意与“1”不一样,“1”中是 objects created by the classes loaded bysystem class loaders,这里是 theobjects, corresponding instances of system class loaders)、JVM内部的一些重要的异常类的对象、异常句柄的预分配对象和在类加载过程中自定义的类加载器。不幸的是,JVM并不提供这些对象的任何额外的详细信息。因此这些实现相关的内容,需要依靠分析来判定。


所以这个算法实施起来有两部分,第一部分就是到 JVM 的几个内存区域中找对象,第二部分就是运用图论算法


 


 


 


 


6.      垃圾回收算法


6.1.     标记-清除(Mark-Sweep



此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。


6.2.     标记-整理(Mark-Compact


此算法结合了标记-清除复制两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象压缩到堆的其中一块,按顺序排放。此算法避免了标记-清除的碎片问题,同时也避免了复制算法的空间问题。


6.3.     复制(Copying


此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现碎片问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间


6.4.     增量收集算法


增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。


6.5.     分代收集算法


这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。虚拟机生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。这样可以减少复制对象的时间。


6.6.     并发收集算法


并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。


6.7.     并行收集器


并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多CPU机器上使用多线程技术可以显著的提高java应用程序的可扩展性。


6.8.     自适应收集器


根据程序运行状况以及堆的使用状况,自动选一种合适的垃圾回收算法。这样可以不局限与一种垃圾回收算法。


6.9.     火车增量算法


垃圾收集算法一个很大的缺点就是难以控制垃圾回收所占用的CPU时间,以及何时需要进行垃圾回收。火车算法是分代收集器所用的算法,目的是在成熟对象空间中提供限定时间的渐进收集。目前应用于SUN公司的Hotspot虚拟机上。


在火车算法中,内存被分为块,多个块组成一个集合。为了形象化,一节车厢代表一个块,一列火车代表一个集合,见图一


图一


注意每个车厢大小相等,但每个火车包含的车厢数不一定相等。垃圾收集以车厢为单位,收集顺序依次为1.11.21.31.42.12.22.33.13.23.3。这个顺序也是块被创建的先后顺序。


垃圾收集器先从块1.1开始扫描直到1.4,如果火车1四个块中的所有对象没有被火车2和火车3的对象引用,而只有火车1内部的对象相互引用,则整个火车1都是垃圾,可以被回收。


如图二,车厢1.1中有对象A和对象B1.3中有对象C1.4中有对象D,车厢2.2中有对象E,车厢3.3中有对象F。在火车1中,对象C引用对象A,对象B引用对象D,可见,火车2和火车3没有引用火车1的对象,则整个火车1都是垃圾。


图二


如果火车1中有对象被其它火车引用,见图三,扫描车厢1.1时发现对象A被火车2中的E引用,则将对象A从车厢1.1转移到车厢2.2,然后扫描A引用的对象D,把D也转移到车厢2.2,然后扫描D,看D是否引用其它对象,如果引用了其它对象则也要转移,依次类推。扫描完火车1的所有对象后,剩下的没有转移的对象都是垃圾,可以把整个火车1都作为垃圾回收。注意如果在转移时,如果车厢2.2空间满了,则要在火车2末尾开辟新的车厢2.4,将新转移的对象都放到2.4,即火车的尾部)


图三


补充说明:垃圾回收器一次只扫描一个车厢。图三中的对象BC并不是立即被回收,而是先会被转移到火车1的尾部车厢。即扫描完1.1后,B被转移到火车1尾部,扫描完1.3后,C被转移到车尾。等垃圾收集器扫描到火车1尾部时,如果仍然没有外部对象引用它们,则BC会被收集。


火车算法最大的好处是它可以保证大的循环结构可以被完全收集,因为成为垃圾的循环结构中的对象,无论多大,都会被移入同一列火车,最终一起被收集。还有一个好处是这种算法在大多数情况下可以保证一次垃圾收集所耗时间在一定限度之内,因为一次垃圾回收只收集一个车厢,而车厢的大小是有限度的。


 


 


7.      触发垃圾收集的条件


由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GCFull GC


7.1.     次收集ScavengeGC


一般情况下,当Enden区对象已满,或当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。


Survivor区,当达到设置的预定值(默认50%),则进行一次垃圾次收集。


7.2.     全收集FullGC


对整个堆进行整理,包括YoungTenuredPermFull GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC


·年老代(Tenured)被写满


·持久代(Perm)被写满


· System.gc()被显示调用


·上一次GC之后Heap的各域分配策略动态变化


8.      垃圾收集器类型


8.1.     串行收集器


用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。可以使用-XX:+UseSerialGC打开。


8.2.     并行收集器


注:图有问题,是并发垃圾收集器的图。


并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。


8.3.     并发垃圾收集


并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。在每个年老代垃圾回收周期中,在收集初期并发收集器会对整个应用进行简短的暂停,在收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程中多个线程同时进行垃圾回收工作。


浮动垃圾:由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“Floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要20%的预留空间用于这些浮动垃圾。


Concurrent Mode Failure:并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生并发模式失败,此时整个应用将会暂停,进行垃圾回收。


启动并发收集器:因为并发收集在应用运行时进行收集,所以必须保证收集完成之前有足够的内存空间供程序使用,否则会出现“Concurrent Mode Failure”。通过设置-XX:CMSInitiatingOccupancyFraction=<N>指定old区达到百分之多少时开始执行并发收集。


9.      垃圾收集器介绍


9.1.     串行垃圾收集


Serial Collector是指任何时刻都只有一个线程进行垃圾收集,这种策略有一个名字“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,那么也就是说只有对象复制31次以后才算是有资格的对象。


这里需要注意几个个问题:


  • From SurvivorTo survivor

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值