JAVA虚拟机基本原理(五:GC基本理论)

      垃圾回收,顾名思义就是将分配出去的内存,但不在被使用的内存回收回来,以便能够再次分配。垃圾指的是死亡的对象所占据的堆内存。

   GC判断算法:引用计数法与可达性分析

     引用计数法:

       引用计数法是为每个对象添加一个引用计数器。用来统计指向该对象的引用个数。一旦某个对象的引用计数器为0 ,则表示该对象已经死亡,便可以被垃圾回收了。

一个引用被赋值给某一对象,那么该对象的计数引用加1,如果一个指向某对象的引用被赋值给其他对象,则该对象的引用计数减去1。期间我们需要截取所有的引用更新操作,并且相应的增减目标对应的引用计数器。

除了需要额外的空间来存储计数器,以及繁琐的更新操作外,引用计数法还有一个漏洞,无法处理循环引用的对象,对象a,b相互引用,没有其他引用指向a,b,实际上a,b已经死亡,但是他们的引用计数都不是0,则他们被判断是存活的,这些循环引用的内存空间无法被回收,从而造成内存泄露。

    可达性分析法:

       现在JVM主流的垃圾收集器采用的是可达性分析法。实质上是将依稀下列GC ROOTS作为初始的存活对象集合,然后从该集合出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程就是标记,最后那些未被探索到的对象就是可以被回收的对象。

     GC ROOTS:可以理解为堆外指向堆内的引用。

             一般包含如下:   1 JAVA方法栈帧中的局部变量。    2 已加载类的静态变量 。   3 JNI handles 。     4 已加载且未停止的JAVA线程。

可达性分析法解决了引用计数法的循环引用问题。但是时间过程中也存在一些问题,比如多线程环境下,其他线程可能会去更新已经访问过对象的引用,造成误报(引用设置为null)或者漏报(将引用设置为未被访问过的对象)。

stop the world:

     传统的垃圾收集算法采用简单粗暴的方法,就是stop the world,停止其他非垃圾回收的线程,知道垃圾回收完成。

stop the world是通过安全点来实现的,当JVM收到stop the world的请求时,它会等待所有线程都达到安全点,才允许stop the world线程进行独占工作。安全点不是让其他线程都停下,而是找到一个稳定执行的状态,在这个状态下,JVM的堆栈不会发生变化,垃圾回收器就可以安全的执行可达性分析了。由于本地代码需要通过JNI的API来完成,所以JVM在API的入口处进行安全点检测,测试是否有其他线程请求停留在安全点里,便可以再必要的时候挂起线程。

垃圾回收的三种方式:

      1 标记清除算法。 缺点:是会造成内存碎片,由于JAVA虚拟机的堆中对象必须是连续分布的,因此可能会出现总的空闲内存是足够的,但是无法分配的情况。    CMS垃圾收集器。

     2 复制算法。 把内存分成两等分,分别用from和to来维护,用from的指针指向的内存区域来分配内存,当发生GC时,便把存活的对象复制到to指针指向的内存区域中。这种方式解决了空间碎片的问题,但是对空间的使用效率低下,实际使用的对空间只有一半。新生代会用复制算法。

     3 标记整理算法 / 压缩算法。把存活的对象聚集到内存的区域的起始位置,从而留下一段连续的内存空间,做法也可以解决空间碎片的问题,但是压缩算法比较消耗性能。   

   4 分代回收算法: 大部分的JAVA对象只能存活一小段时间,而存活下来的小部分对象则会存活很长一段时间,分代回收由此而来。新生代可以使用耗时较短的垃圾回收算法,比如复制算法,复制少量的对象。而老年代的对象存活率较高,并且也没有多余的内存进行分配,建议使用标记清楚算法或者标记整理算法。  G1收集器使用。

JVM的堆划分:

    JVM将堆分为新生代和老年代,其中新生代分为一个eden区域和两个大小相等的survivor区域。

   默认情况下JVM采取动态分配的策略,根据对象的生成速率,以及survivir区域的使用情况调整eden和survivor的大小比例。也可以通过-XX:SurvivorRatio来固定这个比例。

当调用new指令时会在eden区域划分出一块作为存储对象的内存。由于堆空间是线程共享的,所以划分堆内存是需要进行同步的,否则可能出现两个对象公用一段内存的情况。

       当eden区域的空间内存耗尽时,JVM会触发一次Minor GC收集新生代的垃圾。eden区域和from区域指向的survivor区中存活的对象会被复制到to指向的survivor区域,然后交换from指针和to指针,以保证下一次Minor时,to区域是空的。如果一个对象被复制的次数为15次,那么该对象就会存入老年代。如果单个survivir区域占用达到百分之50,那么较高复制次数的对象也会进入老年代。

内存分配和回收策略:

      对象优先在eden区域分配,新生代默认为8比1

      大对象直接进入老年代,可以使用-XX:PretenureSizeThreshold设置大对象多大进入老年代,新生代只有Serial和ParNew两种新生代垃圾收集器生效。 

      新生代的年龄超过15岁进入老年代,-XX:MaxTenuringThreshold=15。

垃圾收集器:

   新生代: serial 单线程的新生代垃圾收集器

                  parnew  多线程的垃圾收集器,默认线程数与cpu相同

                 parallel scavenge  吞吐量优先的垃圾收集器    吞吐量=运行用户代码/(运行用户代码时间+垃圾收集时间)

   老年代: serial old      serial的老年代版本

                  parallel old   parallel scavenge的老年代版本

                CMS  服务器端的首选。

                G1

这里留下CMS收集器和G1收集器的问题,后面补上。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值