JVM内存管家GC

现有的主流JVM分别是HotSpot和JRockit。我们研究HotSpot,也就是所谓的Sun JVM。
  java开发过程中不用我们去管理内存的申请与释放,那么JVM就必须帮助我们自动的去管理这些内存,其中最大的功臣就是GC(Garbage Collection)。

- 管理对象

1. 堆内存GC
  JVM(采用分代回收的策略),用较高的频率对年轻代进行YGC,而对年老代较少(满了后才进行)进行Full GC。这样就不需要每次GC都将内存中所有对象都检查一遍。
  当年轻代满了之后,将引发minor collection(YGC)。在minor collection后存活的object会被移动到年老代。年老代满之后触发major collection。major collection(Full gc)会触发整个heap的回收,包括回收年轻代。永久代比较稳定。

GC运行过程中,全过程暂停应用。目前配置并发收集器,使用多线程的方式,利用多CUP来提高GC的效率,并发完成大部分工作,使得应用暂停的时间大大缩短

2. 非堆内存不GC
  GC不会在主程序运行期对方法区进行清理,所以如果你的应用中有很多CLASS(特别是动态生成类,当然方法区存放的内容不仅限于类)的话,就很可能出现方法区错误。补遗:Java 虚拟机管理堆之外的内存(称为非堆内存)。

- 内存申请过程

  1. JVM会试图为相关Java对象在Eden中初始化一块内存区域;
  2. 当Eden空间足够时,内存申请结束。否则到下一步;
  3. JVM试图释放在Eden中所有不活跃的对象(minor collection),释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;
  4. Survivor区被用来作为Eden及old的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;
  5. 当old区空间不够时,JVM会在old区进行major collection(Full GC),此时JVM GC停止所有在堆中运行的线程并执行清除动作;
  6. 完全垃圾收集后,若Survivor及old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现"Out of memory错误";

申请过程图
JVM内存结构-4.1jvm内存管家GC

- 各内存数据GC过程

  • 年轻代

    • 年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。
    • HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to),默认比例为8:1。
    • 一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。
    • 对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
      在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
    • 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。
    • 紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。
    • 经过这次GC后,Eden区和From区已经被清空。
    • 这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。
    • 不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

    演示图
    JVM内存结构-4.1jvm内存管家GC

  • 年老代

  • 年老代主要存放JVM认为生命周期比较长的对象(经过几次年轻代的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁(譬如可能几个小时一次)。
  • 年老代主要采用压缩的方式来避免内存碎片(将存活对象移动到内存片的一边,也就是内存整理)。
  • 当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。
  • 永久代
    永久代主要存放类定义、字节码和常量等很少会变更的信息。

- 堆对象引用

  • 对象可回收
    • 堆是GC管理的主要区域,GC在对堆进行回收前,首先要确定对象是否已死(不可能再被使用的对象)
    • 判断对象是否存活的算法有两种:引用计数算法、可达性分析算法
    • 引用计数算法是为每一个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加一,任何时刻计数器为0的对象就不可能再被使用。这种算法实现简单,但是它很难解决对象循环引用的问题
    • 可达性分析算法是Java语言正在使用的算法。它的基本思想是通过一系统被称为“GC Root”的对象为起点,从这个起点向下搜索,搜索走过的路径称为引用链,当一个对象不再任何引用链上时,则说明这个对象是不可能再被使用的
  • 在Java语言中,GC Root包括以下几种对象
    • 虚拟机栈中引用的对象
    • 本地方法栈中JNI引用的对象
    • 方法区中类静态成员变量引用的对象
    • 方法区中常量引用的对象
  • 引用
    可以看出分析对象是否存活,都与引用有关,引用分为 强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
    • 强引用
      强引用即为原来意义上的引用,只要强引用存在,被引用的对象就不会被回收
    • 软引用
      SoftReference类表示软引用,对于被软引用关联的对象,在系统将要发生内存溢出时,会把这些对象列入回收范围后,进行二次回收
      软引用也常被用于应用中的内存缓存,不会引起内存溢出,也可以很好的解决查询效率问题
    • 弱引用
      WeakReference类表示弱引用,对于被弱引用关联的对象,只能生存到下一次垃圾回收发生之前
    • 虚引用
      PhantomReference类表示虚引用,虚引用不对关联的对象的生存时间构成影响,也无法取得对象实例,它唯一的作用是在对象被GC回收是收到一条系统通知

- 两类GC

  1. Minor GC
      一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来
  2. Full GC
      对整个堆进行整理,因为需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。
      可能导致Full GC原因:
      1.年老代被写满
      2.永久代被写满
      3.System.gc()被显示调用
      4.上一次GC之后Heap的各域分配策略动态变化
  3. Java常见的内存泄漏
    • 数据库连接,网络连接,IO连接等没有显示调用close关闭,会导致内存泄露
    • 监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露
    • 比较手残的无限循环或无出口嵌套

相关文章: