java对象内存分配

  • 指针碰撞(内存规整)—用过的内存放一边,空闲的内存放另外一边,中间放一个指针作为分界指示器,分配内存就是指针向空闲的一边偏移相对应大小
  • 空闲列表( 内存不规整)—维护一个列表,记录哪块内存空闲

java对象的内存布局

  • 对象头 header
  • 实例数据 Instance Data
  • 对齐填充 padding

GC

  • 引用计数 — 有引用就加一,反之减1, 引用为0则可以回收
    • 难以解决对象间相互引用的问题
  • 可达性分析算法 — 有向图, 不可达的对象则可以回收

引用

  • 强引用 (Strong Reference)— 普通我们使用的引用
  • 软引用(soft Reference) — 系统将要发生内存溢出之前会将这些对象列进回收范围之中进行第二次回收
  • 弱引用(weak Reference) — 被弱引用的内存只能活到下一次垃圾收集发生之前
  • 虚引用(phantom Reference)— 为对象设置虚引用关联唯一目的就是能在这个对象被回收器回收之前收到一个系统通知

final finally finalize

  • final is a keyword
  • finally is a block
    • The finally block always executes when the try block exits
    • finally中不要使用return
      • 导致其他的return无法正常调用返回
      • 导致try中的exception无法正常抛出
  • finalize is a method
    • Before an object is garbage collected, the runtime system calls its finalize() method.
    • 建议不要使用,是java刚刚诞生是为了c/c++程序猿更好接受的一个妥协

GC Roots

两个栈 + 方法区

  • 虚拟机栈(栈帧的本地变量表)中引用的对象
  • 方法区中
    • 类静态属性引用的对象
    • 常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

垃圾回收算法

  • 标记-清除算法
    • 先标记出需要回收的对象
    • 统一回收被标记的对象
  • 复制算法
    • 将对象分为两块,每次只使用一块,内存使用完了就将还存活的对象拷贝到另外一块
    • 新生代:1个Eden空间 + 2个survivor空间, 8:1:1. 回收是将1eden + 1survivor的存活对象一次性复制到另外一个survivor空间, 再清理掉Eden和刚使用的survivor空间
      • 为什么有两个survivor?方便,复制的时候直接赋值过去另外一个survivor,直接清零原来survivor即可
    • 另外一个survivor空间也不能足够分配内存时,通过分配担保机制进入老年代
      • 能担保就直接进入老年代
      • 不能担保就需要fullGC
  • 标记-整理算法
    • 标记过程和”标记-清除”算法一样
    • 后续是让所有存活的对象向一端移动,直接清理掉端边界以外的内存
  • 分代收集算法
    • 在新生代,使用复制算法—-因为每次都有大批量对象死亡
    • 在老年代,使用标记-整理 或者 标记-清除算法, 因为老年代存活率高

新生代,老年代

javaheap.png

  • 从图中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。
  • 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。

Minor GC vs Full GC

  • 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具 备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
  • 老年代 GC (Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常 会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里 就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10 倍以上

内存分配和回收策略

  • 大多数情况下对象优先在Eden分配,当Eden没有足够空间分配时,会进行Minor GC
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代
    • 每次Minor GC,进入Survivor后年龄会增加1岁
    • 当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数XX:MaxTenuringThreshold设置
  • 动态对象年龄判断
    • Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄
      • 为了适应不同程序的内存情况
  • 空间分配担保—MinorGC执行前判断是否可以只MinorGC还是要升级为FullGC
    • HandlePromotionFailure设置值来判断是否允许使用分配担保机制[true / false]
      • HandlePromotionFailure == true
        • 老年代最大可用的连续空间 > 新生代所有对象总空间,继续MinorGC,survivor空间也不能足够分配内存时,直接进入老年代
        • 老年代最大可用的连续空间 < 新生代所有对象总空间, 进行Full GC
      • HandlePromotionFailure == false
        • 进行Full GC
    • why?
      • 虚拟机刚刚启动,老年代的空间是非常大的,不需要频繁full GC

GC触发条件

  • Minor GC触发条件

    • 当Eden区满时,触发Minor GC。
  • Full GC触发条件:

    • 调用System.gc时,系统建议执行Full GC,但是不必然执行
    • 老年代空间不足
    • 方法区空间不足
      • JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    • 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小