jvm GC垃圾回收机制(四)

GC垃圾收集,回收的是什么呢?是对象占用的内存空间。首先看一张简图

之前jvm系列的博客中介绍到JVM的垃圾回收机制中GC主要发生在堆中,堆区由所有线程共享,在虚拟机启动时创建。主要用于存放对象实例和数组,所有new出来的对象都存储在该区域。

jvm虚拟机,本地方法栈,程序计数器不需要进行垃圾回收,因为它们的生命周期是和线程同步的,随着线程的销毁自动释放内存,所有,只有方法区和堆区需要进行垃圾回收,回收的对象就是不存在任何引用的对象。

堆中内存分布

       针对于分代收集算法来定义分为新生代(年轻代)和老年代,其实新生代和老年代就是针对于对象做分区存储,更便于回收等等。默认情况,新生代=1/3的堆空间大小,老年代=2/3的堆空间大小

垃圾回收机制是什么

       垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。当一个对象不再被引用的时候,内存回收它占领的空间,以免造成内存泄漏。

特点

      java语言不允许程序员直接控制内存空间的使用。内存空间的分配和回收都由JRE负责在后台自动进行,尤其是无用内存空间的回收操作(garbagecollection),只能由运行环境提供的一个超级线程进行检测和控制。一般是在CPU空闲或空间不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时机和顺序等。

垃圾回收机制

1. GC的主要任务:

     1.1 分配内存

     1.2 确保被引用对象的内存不被错误回收

     1.3 回收不再被引用的对象的内存空间

垃圾回收前,会确定对象中哪些是"存活",哪些是"死亡"(不可能再被任何途径使用的对象)。如何判断?

1. 引用计数法

给对象增加一个引用计数器,每当有一个地方引用它,计数器加一,如果引用失效,计数器值减一,当计数器值为0时,代表该对象可以被回收了。但是存在两个对象之间互相循环引用的问题。

2. 可达性分析算法:

把一系列"GC Roots"作为起点,从节点向下搜索,路径称为引用链,当一个对象到GC Roots没有任何引用链相连,即不可达时,则此对象是不可用的。

注:在Java中可作为GCRoots的对象:

    1)虚拟机栈(栈帧中的本地变量表)中引用的对象;

    2)方法区中类静态属性引用的对象;

    3)方法区中常量引用的对象;

    4)本地方法栈中JNI引用的对象。

GC何时回收?

     被判断为不可达的对象,也要进行筛选,当对象没有覆盖finalize()方法,或者finalize方法已经被虚拟机调用过,则没有必要执行。

如何回收?涉及到垃圾回收算法和垃圾收集器

1. 复制算法

复制算法把内存按容量划分为大小相等的两块区域,每次只使用其中的一块,当这一块的内存空间用完了,就把还存活的对象复制到另一块内存中去,然后把已经使用过的内存空间一次性清理掉。这样每次都是对半个内存区域进行GC回收,并不会产生内存碎片,但是代价是把内存缩小了一半,效率比较低。适用于新生代。

2. 标记清除法

“标记-清除”算法是最基础的收集算法。算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

3. 标记压缩算法(标记整理算法)

其标记过程与标记清理法过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。适用于老年代。

4. 标记清除压缩

标记清除,标记压缩结合使用,先多次进行标记清除,产生许多内存碎片,然后对碎片清理(多次FULL GC再压缩),相对标记压缩减少了许多移动成本。

垃圾回收过程:

     1. 刚new出来的对象一般会首先分配到Eden区,

     2. 当Eden没有足够空间的时候就会触发jvm发起一次轻量级的GC,如果对象经过一次轻量级的GC还存活,并且又能被幸存区空间接受,那么将被移动到幸存区中。并将其年龄设为1,

     3. 对象在幸存区每熬过一次轻量级GC,年龄就加1,

     4. 当年龄达到一定的程度(默认为15)时就会就会被晋升到老年代了(晋升到老年代的年龄可以设置的)。

     5. 当老年代达到一定的比例,则会触发Major GC释放老年代,

     6. 当老年代满了,则触发一个一次完整的垃圾回收(Full GC),

     7. 如果内存还是不够,jvm会抛出内存不足,发生oom内存泄漏。

补充1.为什么新生代中要设置幸存区,为什么要有两个Survivor呢?

      防止频繁触发FULL GC。如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代,这样会使老年代很快被填满,导致老年代触发FULL GC,由于老年代的内存空间远大于新生代,所以进行一次Full GC消耗的时间比Minor GC长得多。

      设置两个Survivor是为了防止产生内存空间碎片。如果只有Survivor1,那么每一次当Eden区满时,触发Minor GC并把对象移入Survivor1中,如此循环对导致Survivor1中产生大量的空间碎片;所以需要有Survivor2,当Eden再一次满时,触发Minor GC,虚拟机会把 Eden中和Survivor1中的存活对象通过复制算法移入Survivor2中,这样Survivor2就不会产生内存碎片,同时Eden和Survivor1会清理内存,保证下一次Minor GC触发时的操作。

补充2:

 在对jvm调优的过程,很大一部分就是对于Full GC调节。当老年代被写满,System.gc()被显示调用,上一次GC之后Heap的各域分配策略动态变化都可能导致Full GC。

开发中容易造成内存泄漏的操作:

  1. 创建大量无用对象,如需要大量连接字符串时,使用string而不是StringBuilder/StringBuffer。

  2. 静态集合类的使用,HashMap、Vector、List。

  3. 各种连接对象未及时释放关闭。

  4. 监听器的使用。

如有不同见解,欢迎留言指正。望不吝赐教!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值