一、背景:
Java程序员编写程序时,对于新建的对象,当不再需要此对象时,不必去释放这个对象所占用的空间,这个工作是由Java虚拟机自己完成的 ,即内存回收或垃圾回收。
二、如何知道一个对象所占用的空间可以回收了呢?
1.常用的一种算法是引用计数法,如果一个对象的引用为0了,那就可以回收了。但是对于这种方法致命缺陷,当对象之间存在循环引用的时候,A引用B,B引用A,这样A和B的引用就一直不会为0,那就无法回收了。
2、Java采用的算法为根搜索算法,以一系列GC Roots为起点,向下搜索,如果存在引用,则对象依然在用,不能回收;如果不存在引用,则可以回收。
可以作为GC Roots的有如下几种:
a、虚拟机栈(栈帧中的本地变量表)中的引用的对象
b、方法区中的类静态属性引用的对象
c、方法区中的常量引用的对象
d、本地方法栈中JNI的引用的对象
三、java堆栈内存结构划分:
1.新生代(Young Generation):又细分为三块(eden,S0,S1)
新的对象总是在新生代创建。
2.老年代(Old Generation)
在新生代存放一定时间之后,在新生代空间不够时,对象会被移到老年代。
3.永久代(Permanent)
用于存在类信息。对于动态生成的类,后续不会再使用到,所以可以进行回收。
四、为什么采用分代(区)的方式来进行堆栈空间的管理呢?
因为不同的对象存活时间不同,多数对象存活时间很短,少数对象存活时间很长。如果每次回收都遍历所有对象,效率将会非常低。按对象存活时间长短,划分不同的内存区域来管理对象,对不同的区采用不同的回收算法。
五、主要的空间回收算法:
1.复制算法
对于新生代,划分了eden/S0/S1三块区域,开始对象创建在eden区域上。
下面来描述一下可能的操作过程,各个java虚拟机的实现会有差异,下面是大致进行阐述。
第一次清理的时候,将可以清除的对象删除,将要保留的对象都复制到S0区域,eden区域清空,对象年龄标记增长1;
第二次清理的时候,将可以清除的对象删除,将要保留的对象从eden和S0都复制到S1,并将eden和S0都清空,对象年龄标记增长1;
第三次清理的时候,将可以清除的对象删除,将要保留的对象从eden和S1都复制到S0,并将eden和S1都清空,对象年龄标记增长1;
后续清理的时候,以此重复上面的步骤,当对象的年龄达到一定值时,就被移到老年代中去。
对于上面的过程,可以通过jvisualvm观测到,会发现eden和S0同时被清空,对象转移到S1,或eden和S1同时被清空,对象转移到S0.
下面给出两张jvisualvm的截图:
时刻1:
时刻2:
时刻3:
通过上面三张图对比可以看到,当S0被占用时,S1就是空的;S1被占用时,S0就是空的,这也就印证了上面算法描述里说的信息。
2.标记清除算法(包括整理)
因为老年代中,可以回收的对象很少,所以触发对老年代的回收几率也比较低,对于老年代也不采用复制算法,因为那样空间利用率比较低,而是采用标记清除(整理)方式。如果对应对象的空间可以回收了,就进行回收,并对对象存储位置进行移动,将对象移到到一起,避免中间存在空隙,便于后续内存分配。
六、几个可以用于指定java虚拟机各个块大小的参数:
-XX:PermSize 设置永久代初始大小(样例:-XX:PermSize=40M)
-XX:MaxPermSize 设置永久代最大大小(样例:-XX:MaxPermSize=100M)
(注:java对此参数值限制,具体限制可能因操作系统、CPU/内存差异而又不同,在Window7上测试时,最小值为12M;不能设置为奇数,必须为偶数)
-XX:NewSize 设置新生代初始大小(样例:-XX:NewSize=10M)
-XX:MaxNewSize 设置新生代最大值(样例:-XX:MaxNewSize=20M)
-XX:SurvivorRatio 设置新生代中eden和survivor区(S0/S1)的比例,通过验证,实际大小比例为该值*1.2,如设置为8,则eden空间大小/S0=8*1.2(样例:-XX:SurvivorRatio=8)
-Xmx 设置堆栈最大值,包括老年代和新生代,不包括永久代(样例-Xmx100M)
-Xms 设置堆栈初始大小(样例:-Xms50M)
-XX:NewRatio 老年代和新生代的大小比例,设置此参数时不能设置MaxNewSize,否则此参数失效(样例:-XX:NewRatio=2)
参考资料: