垃圾回收算法

备注:博客摘自周志明老师《深入理解java虚拟机》一书

回收方法区:(永久代:方法区  存在垃圾回收)

    很多人认为方法区(或者说是HotSpot虚拟机中的永久代)是没有垃圾回收的,java虚拟机规范中确实说过可以不要虚拟机在方法区中实现垃圾收集,而且方法区中进行垃圾收集的性价比比较低,在堆中尤其是新生代,常规应用进行一次垃圾收集一般可以回收70%-95%的空间,而永久代的效率远低于此。

    永久代的垃圾回收主要包含两部分:废弃常量  和  无用类。

    回收废弃常量和java堆中回收对象很相似,以常量池中的字面量的回收为例:String a= "hello";String b= a+ "word";可以看到“word”是直接入池,且没有String类对象去引用它,也就没有对象去引用这个字面量,如果这时候发生内存回收,这个常量“word”会被清除常量池,常量池中的其他类(接口)、方法、字段的符号引用也如此。

    无用类的判断则相对苛刻,要满足下面三个条件才能算是无用类:

  1. 该类所有的实例都已经被回收(不能存在存活的实例对象)。
  2. 加载类的ClassLoader已经被回收(因为每一个类经过classloader加载过后会在虚拟机中有对应的class实例)。
  3. 该类对应的java.lang.Class对象没有在任何地方引用(无法通过该类的反射去访问该类的方法)。

垃圾收集算法:(堆)

(1)标记-清除  算法

    最基础的算法“标记-清除”算法,如同它的名字一样,算法分为“标记”和“清除”两个阶段,首先标出需要回收的对象,在标记完成后统一回收所有被标记的对象,之所以说它是最基础的算法,因为后面的算法都是基于这种思路并且对其不足改进。有两个不足:①效率问题:标记和清除两个过程都不高②空间问题,标记和清除会产生大量的不连续的内存碎片,空间碎片太对会导致以后程序在运行规程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

(2)复制  算法(9:1)

    为了解决效率问题,复制算法应运而生,它可以将内存按照容量大小划分为大小相等的两块区域,每次只使用其中的一块,当这一块内存用完了就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存清理掉,这样使得每次都是对半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况了,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。这种算法的代价就是将内存减少为原来一半,未免太高了点。

    现在商业的虚拟机都采用此方法回收新生代,然而新生代对象98%时朝生夕死的,所以并不需要按照1:1比例来瓜分内存空间,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden其中的一块Survivor,当回收时,将Eden区和Survivor区还活着的对象一次性复制到另外一块Survivor区,然后清理掉Eden和Survivor空间,HotSpot虚拟机默认Eden空间:Survivor空间大小比例为8:1,也就是说每次新生代中可用内存空间为整个新生代容量的90%,只有10%的内存会被浪费掉。

    当Survivor空间不够时,需要依赖其它内存(这里指老年代)进行分配担保。就好比银行贷款,虽然你98%的机会能按时偿还(这里98是指大多数情况下是能回收这么多,这样看起来8:1:1就很合理了对吧,但不能保证每次都只有不到10%的对象存活),但总有例外嘛,所以为了风险银行推迟了担保人的策略,若不能及时还款则从担保人账户扣钱。同理,若S1内存不够,则直接将这些存活的对象通过分配担保机制进入老年代。如下图

通过代码也可以发现:运行时通过设定-Xms20M(最小内存)、-Xmx20M(最大可用内存)、-Xmn10M( 新生代的设置)-XX:SurvivorRatio=8(新生代内部Survivor和Eden区域的比例;)示例: 8表示两个Survivor:Eden=2:8, 即一个Survivor占1/10的新生代空间  这三个参数限制了java堆的大小为20M 不可扩展,并且新生为10M,剩下的10M分配给老年代。

 

/**
 * @author Heian
 * @time 19/01/30 10:05
 * @copyright(C) 2019 深圳市北辰德科技股份有限公司
 * 用途:对象优先在Eden区分配(复制算法)
 */
public class JvmTets {
    private static final int M = 1024*1024;

    /**
     *  VM参数 -Xms:20M  -Xmn:20M  -Xmn:10M -XX:+PrintGCDetalis(虚拟机发生垃圾回收会打印GC日志) -XX:SurvivorRatio=8
     */
    public static void main(String[] args) {
        byte[] b1,b2,b3,b4;
        b1 = new byte[2*M];
        b2 = new byte[2*M];
        b3 = new byte[2*M];
        b4 = new byte[4*M];//出现一次Minor GC
    }
/*    分析:从上面中我们设置了堆内存的为20M,其中10M分给了新生代(Eden:8M S0:1M S1:1M),剩余10M分给了老年代。
    b1,b2,b3各占用了6M,所以Eden和S0区域(新生代)也就被用了6M,等到b4出现,发现Eden + S0所剩余的空间3M已经不足以
    继续容纳b4,于是就把存活的对象转移到S1(发生了Monor GC),但是还是发现S1空间1M也不足于是就采取担保人机制,直接将存活对象移到老年代*/

}

(3)标记-整理算法

    复制收集算法在对象存活率较高是就要进行较多的复制操作,效率变低,更关键是,如果不想浪费50%的空间(标记-清理),就需要有额外的空间进行担保,以应对被使用的内存中所有对象都是100%存活的极端情况,所以在老年代就不能选用这种算法。

    根据老年代的特点,有人提出了“标记-整理”算法,标记过程仍然与标记算法一样,但是后续的步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存,示意图如下

(4)分代收集算法

    当前商业虚拟机都采用“分代收集”算法,这种算法并没有什么新的思想,只是根据对象的存活周期的不同将内存划分为几块。一般是java堆划分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中每次垃圾收集时都会发现大批对象死去,只有少量存活,那就选用复制算法,只需要复制少量的存活对象,成本低。而在老年代中因为对象存活率极高,没有额外空间对它进行担保分配,就必须使用“标记-清理”或者“标记-整理”算法进行回收。

参考资料:

参数的含义(idea目录在bin下面的idea.exe.vmoptions文件中)

-server
-Xms128m   Java Heap初始值      默认是物理内存的1/64
-Xmx512m   Java Heap最大值     Server端JVM最好将-Xms和-Xmx设为相同值
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50

下面几个参数需要自行设置(idea无此参数设置)

  1. -Xmn   Java Heap Young区大小,不熟悉最好保留默认值;
  2. -Xss   每个线程的Stack大小,不熟悉最好保留默认值;
  3. -XX:PermSize 永久区的大小。 
  4. -XX:+UseParNewGC 使用并行收集算法。

下面是eclipse bin目录下eclipse.ini文件参数
-XX:PermSize=64M JVM初始分配的非堆内存
-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配  。 

堆内存分配:

        JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
 说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try...catch捕捉

非堆内存分配:

     JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。(还有一说:MaxPermSize缺省值和-server -client选项相关,-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。)上面错误信息中的PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。还没有弄明白PermGen space是属于非堆内存,还是就是非堆内存,但至少是属于了。
XX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。 
说说为什么会内存益出: 
(1)这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。 
(2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。

JVM内存限制(最大值):

      首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。

    为什么有的机器我将-Xmx和-XX:MaxPermSize都设置为512M之后Eclipse可以启动,而有些机器无法启动?

    通过上面对JVM内存管理的介绍我们已经了解到JVM内存包含两种:堆内存和非堆内存,另外JVM最大内存首先取决于实际的物理内存和操作系统。所以说设置VM参数导致程序无法启动主要有以下几种原因:

  1. 参数中-Xms的值大于-Xmx,或者-XX:PermSize的值大于-XX:MaxPermSize;
  2.  -Xmx的值和-XX:MaxPermSize的总和超过了JVM内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,如果你的内存是1024MB,但实际系统中用到的并不可能是1024MB,因为有一部分被硬件占用了。

出自:http://www.cnblogs.com/mingforyou/archive/2012/03/03/2378143.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值