深入理解JVM—gc相关
?
? ? ? ??深入理解JVM—gc相关周四听了毕玄的JVM分享,对于JVM的相关知识, 有了初步的了解,然后根据现场笔记整理形成以下这篇blog。
一 内存管理第一种是以c/c++为典型代表的,是需要程序员显示的管理内存,如c的malloc /free ?c++的new delete。优点非常明显,就是程序员对所有的内存具有完全的控制权,和那些语言层面实现了内存管理的语言相比,效率很高。但是缺点也很明显,就是一不小心,很容易内存泄露,学习成本较高, 特别是新手,很容易出错。
第二种是以后出现的众多高级语言,例如java、 python、 c#等等, 这些语言都不需要主动去管理内存,而是语言层面已经帮你完成了这部分功能,帮你管理内存,对于新手来说,也不容易写出太差的代码。但是同样缺点也很明显,就是你不能很明确的控制什么时候回收内存块。
内存分配和内存的回收是JVM的gc主要需要完成的事情, 我们只有通过详细的了解gc相关的触发机制,才可以写出更加建壮的程序。
如上图所示, Java内存区域,可以分成以上3个部分。
方法栈: 线程创建时产生,并在方法执行过程中, 一直存在于堆栈中, 在方法中调用新的方法, 那么新的方法栈一样也会被压入堆栈。在方法帧上又保存了方法执行过程中的局部对象和操作数栈。
方法区:存放类的元数据常量。
堆:所有通过new生成的对象都放在这个区域,并通过一个指针指向对应的对象。
Native Memory:这部分也是堆,一般是通过jni之类的c代码存放对象的地方,而不是通过Java尽心管理。
?
2.1内存区的具体划分
在sun的jdk上, 一般会分成两个区域,如下图所示,一块是新生代 (newGeneration),一个是旧生代 (oldGeneration),在新生代又分成了Eden,还有一般是一样大小的S0 S1两个可以看成是交换区的部分。通常对于新生代的回收称为minor GC 或者young GC ,而对于旧生代的回收称为old GC。
?
?
?
三 sun jdk 的GC方式
一 gc的方式
YGC: ParallelScavenge(PS)
FGC: ParallelMSC(PSOld),Parallel Compacting(ParOld)
edn空间不足
1.old空间不足;
2.perm空间不足;
3.显示调用System.gc(),包括RMI等的定时触发;
4.YGC时的悲观策略;
5.dump live的内存信息时(jmap dump:live)。
对YGC的 触发时机,相当的显而易见,就是eden空间不足, 这时候就肯定会触发ygc
最复杂的是所谓的悲观策略,它触发的机制是在首先会计算之前晋升的平均大小,也就是从新生代,通过ygc变成新生代的平均大小,然后如果旧生代剩余的空间小于晋升大小,那么就会触发一次FullGC。sdk考虑的策略是,从平均和长远的情况来看,下次晋升空间不够的可能性非常大,与其等到那时候在fullGC 不如悲观的认为下次肯定会触发FullGC,直接先执行一次FullGC。而且从实际使用过程中来看,也达到了比较稳定的效果。
所谓的并行就是多线程同时进行GC,这个是在server默认使用的模式。优点是高效, 主要是相对的串行来说, 充分利用了服务器多核多内存的特点,充分利用所有硬件资源,进行GC,因此相对串行速度快也就是是必然的了。缺点是,如果堆(heap)比较大, 那么暂停的时间可能会比较长,这是现在java语言发展到当今最大的悲剧,没有很好的跟上硬件的发展速度, 当今好的服务器都是几十G的内存,GC一次可能会时间较长, 这对于online的应用是完全无法忍受的。
YGC 和串行一样。
FGC 和串行基本一样,最大的不同是悲观策略在YGC前和YGC后会检查两次。
基本动作一样, 主要的区别是使用多线程同时gc
另外YGC之后, 会重新计算Eden和from的大小
默认ScavengeBeforeFullGC的参数 会先触发YGC
其他操作基本一样清空没有引用的对象, 卸载class信息
MSC:进行压缩
Compacting进行部分压缩
3.3并发
所谓的并发,就是和之前的串行和并行都需要相对长的暂停时间相比,并发只需要很短的应用暂停时间,相对来说,比较时候对延时要求比较高的应用,但是现在的并发并不是很成熟,存在着一些bug还没有完全fix。并发的优点是:暂停时间短, 延时小。并发的缺点: 内存碎片多,在Old区的内存分配的效率很低,而且由于和应用同时进行,会出现和应用抢cpu的现象,导致应用的响应变慢,从而影响用户体验。同时用于没有充分利用硬件资源,包括内存cpu,所以整个回收的过程会比较久。
和其他垃圾回收机制一样, 都是eden的空间不足
oldGC达到一定的使用率的时候,就必须进行FGC,因为这个GC过程中,是不暂停,如果等到空间不足才GC,会导致内存分配失败,而应用无法正常运行, 这个比例值可以通过参数进行设置。
Hotspot自己估计是否需要触发CMSGC
显式调用了System.gc()
和串行一样, 只是使用了多线程
1.在oldGen达到一定的比例之后就清除所有没有引用的对象
2.perm gen 达到一定比例之后,就清除classload所加载的信息
如果并发的GC失败, 会引发一次串行的GC。
四GC调优4.1GC调优模式
GC调优,主要有以下以下衡量尺度,包括分配内存,GC的频率, GC导致应用暂停的时间,应用的响应时间和QPS ,System的 load/cpu/IO 等等。
1 .减小Heap
2 .换成CMS?
3.升级CPU(据说最靠谱)
1 增大新生代
1减少新生代,可能造成频繁FGC
2升级CPU
4.3写出GC友好的代码
1.尽量减少使用autobox,因为在Java在将原始对类型变成对象的过程中,需要new一个新的对象过程中,在某些时候,可能会使用大量堆的内存造成内存溢出。
2.合理控制动态增长的数据结构的大小,很多时候内存的超出错误(OOM)都是这些动态数据结构造成,他给我们带来方便的同时,也同样带来了不可控制性。
3.合理使用reference。
4.不要使用Finalizers来回收资源,据写sdk的人说,Finalizers并不能保证一定会被执行,在某些特殊的时刻,可能会被跳过,这样很容易导致内存泄漏。
五sun jdk 内存管理的实现5.1内存管理的方法对于java sdk的内存管理来说, 主要需要完成的功能就是对内存的管理, 从空间的申请到释放的过程。
在所有的语言中,内存空间的分配和回收, 主要有以下两种做法。
优点是:效率高
缺点:空间浪费, 形成碎片
优点:内存的使用率较高?
缺点:? 移动内存需要时间, 而且需要更新指针
5.2GC的主要算法GC算法,需要完成的主要目标是找到垃圾内存块,并进行回收。主要有以下几种经典算法。
一引用计数
这是最老的GC算法, 它具有以下几个明显的缺点, 首先他需要额外的空间, 然后存在一个最主要的问题是无法解决循环引用。
二 Tracing Mark-Sweep
需要使用一定的策略遍历所有的对象, 然后进行标记, 之后将没有标记的进行回收。
三Tracing Mark-Compact
在标志的基础上,进行移动,使内存块连续。
缺点: 移动需要时间, 需要更新指针
优点,内存使用率高
四 copying
所谓的复制,就是在遍历过程中 将所有有引用的复制到另外一个分区, 然后剩下的就是没有引用的内存块, 直接对整个内存块进行回收就可以了。 其实就是上面提到的新生代的from和to。
优点: 没有垃圾碎片,
缺点: 需要更新指针, 浪费了一块空闲的内存
以上这篇日志,都是在分享上听到的或者PPT上的再加上一些我自己的一些理解,再次感谢毕玄这次分享,如果有错,欢迎指正。
1 楼 laier903 2011-07-25 学到不少,谢谢分享~ 2 楼 san_yun 2012-02-06 引用4.2GC的主要模式以及对应的解决方案
一 降低FGC的频率 :
应该是 降低YGC的频率 吧?