堆(新生代和老生代)是Java虚拟机进行垃圾回收的主要场所,其次要场所是方法区(永久代)。
在堆中进行垃圾回收分为新生代和老生代;将新生代分成了三个独立的区域(这里的独立区域只是一个相对的概念,并不是说分成三个区域以后就不再互相联合工作了),
分别为:Eden区、From Survivor区以及To Survivor,而Eden区分配的内存较大,其他两个区较小,每次使用Eden和其中一块Survivor。
在进行垃圾回收时,将Eden和Survivor中还存活着的对象进行一次性地复制到另一块Survivor空间上,直到其两个区域中对象被回收完成,
当Survivor空间不够用时,需要依赖其他老年代的内存进行分配担保。当另外一块Survivor中没有足够的空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老生代,大对象和长期存活的对象也会直接进入老年代。
如果老生代的空间也被占满,当来自新生代的对象再次请求进入老生代时就会报OutOfMemory异常。
新生代中的垃圾回收频率高, ,保存在JVM的方法区(永久代)中的对象一般不会被回收。
其永久代进行垃圾回收的频率就较低,速度也较慢。
永久代的垃圾收集主要回收废弃常量和无用类。
1
2
3
判断一个类是否被回收,则需同时满足的条件:
该类所有的实例和ClassLoader都已经被回收。
该类的对象没有被引用,无法通过反射访问,这里说的是可以回收而不是必然回收。
1
2
3
4
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;
同理,当老年代中没有足够的内存空间来存放对象时,虚拟机会发起一次Major GC/Full GC。只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full CG。
虚拟机通过对象年龄计数器来判断存放在哪:
如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将该对象的年龄设为1。
对象每在Survivor中熬过一次Minor GC,年龄就增加1岁,当他的年龄增加到最大值15(MaxTenuringThreshold)时,就将会被晋升到老年代中。
如果在Survivor空间中所有相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
Jdk8开始废弃永久代:
This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
(移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。)
由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen
字符串存在永久代中,容易出现性能问题和内存溢出。
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
永久代会为GC带来不必要的复杂度,而且回收效率偏低。