这篇本来是想好好写的,今天看了一天了的,可是,组会好像有种要悲剧的感觉啊。。暴风雨前的宁静,尽量记录一些吧。
上周把爬虫代码优化后,就哈皮哈皮的和未优化的版本一起跑了。起初,由于未优化的版本已经run了几天了,内存占用在200M多,而发现优化后的内存占用只有40M,所以很开心。但是不久就发现,优化后的版本,内存占用也会慢慢增加,大有一种“老娘给你分多少,你就要用多少”的赶脚。
用jprofiler监控,自己开发的类的对象数量都比较正常,所以决定学习一下JVM内存管理相关的东西。
JVM主要管理两种类型的内存:堆(Heap)和非堆。
按照官方的说法:“Java虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。” “在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
堆内存分配
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。
组成 | 详解 |
---|---|
Young Generation | |
Eden | 存放新生的对象 |
Survivor Space | 有两个,存放每次垃圾回收后存活的对象 |
Old(Tenured) Generation | 主要存放应用程序中生命周期长的存活对象 |
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
组成 | 详解 |
---|---|
Permanent Generation | 保存虚拟机自己的静态(refective)数据 主要存放加载的Class类级别静态对象如class本身,method,field等等 |
Code Cache | 用于编译和保存本地代码(native code)的内存 JVM内部处理或优化 |
内存分区与GC
绝大部分的object被分配在young generation(生命周期短),并且大部分的object在这里die。当young generation满了之后,将引发minor collection(YGC)。在minor collection后存活的object会被移动到tenured generation(生命周期比较长)。最后,tenured generation满之后触发major collection。major collection(Full gc)会触发整个heap的回收,包括回收young generation。permanet generation区域比较稳定,主要存放classloader信息。
young generation有eden、2个survivor 区域组成。其中一个survivor区域一直是空的,是eden区域和另一个survivor区域在下一次copy collection后活着的object的目的地。object在survivor区域被复制直到转移到tenured区。
我们要尽量减少 Full gc 的次数(tenured generation 一般比较大,收集的时间较长,频繁的Full gc会导致应用的性能收到严重的影响)。
如下图所示,是我用JProfiler监视自己的爬虫程序的过程中,内存各个区域的变化状况。从图中可以看到,heap中的小锯齿就是minor GC,大锯齿为Full GC(对应在tenured区满时)。tenured区第一次满的时候发生Full GC,并且内存占用量从xms扩展到xmx。
堆内存GC
JVM(采用分代回收的策略),用较高的频率对年轻的对象(young generation)进行YGC,而对老对象(tenured generation)较少(tenured generation 满了后才进行)进行Full GC。这样就不需要每次GC都将内存中所有对象都检查一遍。
非堆内存不GC
GC不会在主程序运行期对PermGen Space进行清理,所以如果你的应用中有很多CLASS(特别是动态生成类,当然permgen space存放的内容不仅限于类)的话,就很可能出现PermGen Space错误。如下图可以看到,非堆内存没有发生过GC。
内存申请过程
- JVM会试图为相关Java对象在Eden中初始化一块内存区域;
- 当Eden空间足够时,内存申请结束。否则到下一步;
- JVM试图释放在Eden中所有不活跃的对象(minor collection),释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;
- Survivor区被用来作为Eden及old的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;
- 当old区空间不够时,JVM会在old区进行major collection;
- 完全垃圾收集后,若Survivor及old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现"Out of memory错误";
参考内容:
http://www.cnblogs.com/redcreen/archive/2011/05/04/2036387.html
http://www.cnblogs.com/redcreen/archive/2011/05/04/2037056.html
这个JVM系列写得还蛮好的,MARK下。
来源:https://www.cnblogs.com/yuki-lau/archive/2013/03/12/2954700.html