java特种兵读书笔记(3-3)——java程序员的OS之虚拟机回收算法

来源:互联网 发布:手游刷金币的软件 编辑:程序博客网 时间:2024/05/22 00:55

串行GC与并行GC


一个线程做GC,不要考虑征用和锁的问题以及开销。

适用场景:单机程序,客户端代码,小内存程序(小于100M),CPU数量有限。

并发量大,或者处理大数据的服务端程序,通常用较大内存承载用户的访问。面对较大内存,对YoungGC用串行GC还可以,FullGC用串行GC回收效率远远不够。对于这种情况,线程分配的开销通常是值得的。

而且服务器一般有很多很好的CPU,并行GC可以充分利用CPU。

并行GC的悲观策略


YoungGC后,会检查Old区剩余空间大小,如果Old区剩余空间比平均每次对象“晋升”需要的空间要小的话,就会提前做一次FullGC。

由于GC操作大多是CPU操作,所以线程数不宜多于CPU数。过多线程会使GC过程锁征用开销加大。即使CPU够多,也没必要设置那么多的线程。

基本参数参考


在32位模式下,通常Heap区分配1.5G,使用ParallelOldGC。可以通过ParallelGCThreads改变GC线程数,但是GC过程大多为CPU操作,所以线程数不宜超过CPU个数,否则会使GC锁征用增大。

当有大量存活对象时,哪一种GC效果都不会太好,无论是标记,回收,还是压缩动作都会受到影响。

存活的节点越多,对象之间的空隙越小,移动这些对象的难度会越来越高。

在可控的情况下,可以扩大Survivor区大小,尽量防止对象直接进入Old区。

FullGC发生条件


Old区满:晋升的对象大小大于Old区的剩余空间。

Old剩余空间小于平均晋级空间:Old区剩余空间小于每次平均晋级的大小(每次晋级都会记录大小,这里是每次晋级大小的平均值,Old区剩余空间小于这个值,“悲观”的认为下次晋升肯定会发生FullGC)

Perm区满:Perm区域满的时候,也会发生FullGC。

系统调用:使用System.gc()的时候会发生FullGC。这时在FullGC日志中可以看到system字样。可以使用ExplicitDisable参数使其失效。当关闭显示GC后,在DirectBuffer满的时候也会FullGC。

dump:用jmap工具dump内存的时候,同样会发生一次FullGC然后再开始dump。

CMS GC


对于1~2GB的内存,如果代码没有问题,那么GC的开销时间并不多。采用并行收集的话,每次GC在毫秒的级别。对于传统web应用基本足够,因为用户的感知是秒级别的。

除非遇到大量本不该存活的对象存活着,这种情况哪一种GC都很慢。这种情况下,GC的暂停时间甚至会打到100s甚至以上。

那么如何处理大内存?

可以将大内存机器划分多个虚拟机,或者启动多个JVM。多例方式性能高于虚拟机,但是部署麻烦,资源隔离性差。

Concurrent mark sweep,并发标记、清除。它希望用户在使用过程中,暂停尽量减小(但并非完全没有暂停)。

注意:CMS GC在标记过程中用户是可以操作的,也就是说会引起内存变化,所以CMS GC会remark。

当系统有98%的时间在做GC,并且只有小于2%的内存恢复时,此时会抛出OOM异常。

一旦CMS GC失败或者promotion fail,就会引发FullGC,转入全暂停的FullGC。CMS GC只会与Serial GC兼容,就是说当CMS GC失败时,会转入串行GC(MSC)。CMS GC是配合大内存使用的,一个大内存应用发生串行GC,是我们非常不愿意看到的事情。

增量GC


一次只做一部分GC,防止做GC时间太长。这样清理一部分空间,让程序可以多利用一部分空间,下次再清理一些,程序可以再多利用一些空间。会比一次性清理所有空间,让程序在那里等着要好。

G1 GC


JVM堆被划分为多个板块。当做GC时,理论上只有访问当前板块的线程会被暂停。

垃圾多的区域优先级高,会被优先处理。垃圾多的区域存活的对象少,存活的少那么找到它们就快。对于垃圾少的区域,优先级靠后,GC很少或者永远不会去扫描到这些区域。

注意:再牛的GC算法,也无法解决代码上的问题。写代码时就有大量数据放在内存里,无论多么牛的GC都干不掉,那么最后只能宕机。

小而美的内存


小内存:程序中申请的对象不是那么大。如果一次性分配10MB内存,即使它能马上变成垃圾,可能也会在变为垃圾前先进入Old区(如果Survivor区放不下),甚至直接进入Old区(创建时Eden区放不下)。

美:像昙花一样短暂。干净利落的代码,跑的很快的程序,持有的对象编程垃圾的越快。

将不用的对象设置为null可以帮助gc(但是不是每个地方都要做到这么极致)。有时cache也是有存在必要的,对于那些反复读取的内容,创建它们需要很大代价(比如数据库的连接,由连接池管理就很好,不要每次都创建,销毁)。

大而不美:为了保存用户信息而把它们全都放在session中,又不知道什么时候该去注销掉。

stop the world


安全点:JVM编译为本地代码(汇编指令)后,会生成OopMap,用来帮助GC root的枚举。但是JVM不会为每条指令都生成OopMap,代价太大,只会在特定位置生成OopMap,这个位置叫做“安全点”。指令只要运行到安全点,就可以暂停。

抢先式中断:先强制中断所有线程,发现哪些线程没有运行到安全点,再让它们运行到安全点。该方式难度太大。

主动式中断:先将虚拟机的内存页0x160100设置为不可读的状态,然后每个线程执行到安全点的时候,判断内存页是否可读,如果不可读就停止(类似于java程序的分布式锁,每给线程都会去轮询该锁的状态来做相应的操作)。

对于正在阻塞或者sleep的线程,它们被唤醒的时候GC还在进行中就会有问题。因此有了安全区域,上述代码(不会改变引用变化的)进入安全区域,如果线程要离开这个区域,需要检查系统是否在做GC。

0 0
原创粉丝点击