Java对象内存分配策略和垃圾收集器
Java的内存管理主要是针对于对内存中的对象的分配和回收(堆和方法区)
垃圾收集算法
判断哪些对象需要回收
- 引用计数法:每个对象都有一个计数器,当这个对象被一个变量或者另一个对象引用的时候,计数器加一;若该引用失效便计数器减一。当计数器为0的时候,就认为该对象是无效对象
- 缺点:很难解决对象之间互相循环引用的问题
- 可达性分析(主流):所有和GC Roots直接或间接相连的对象都是有效对象,和GC Roots没有引用链相连的对象就是无效对象
可作为GC Roots对象的类别
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈JNI(Native 方法)引用的对象
引用强弱
强引用>软引用>弱引用>虚引用
- 强引用:只要引用还在,那么垃圾收集器永远不会回收掉被引用的对象(
Object object = new Object()
) - 软引用:描述一些还有用但不是必需的对象。当第一次OOM之前,将会讲这些对象列进回收范围之中进行第二次回收。第二次内存还不足的话才会报OOM
- 弱引用:描述非必需对象。生存至下一次垃圾回收之前,也就是垃圾回收时,必回收弱引用关联的对象
- 虚引用:被回收时收到一个系统通知
回收无效对象的过程
- 判断对象是否覆盖了finalize()方法
- 覆盖,且该方法的finalize()方法没有执行,就会将finalize()方法扔到F-Queue队列中
- 没有覆盖,直接释放对象内存
- 执行F-Queue队列中的finalize()方法。如果出现耗时操作就会直接停止执行,将该对象清除
- 对象重生或者死亡,在执行finalize的过程中,若有引用则重生,没有则清除
垃圾收集算法
- 标记-清除算法
- 标记需要被清除的对象
- 标记完成后统一回收被标记的对象
- 缺点:效率低、空间碎片多
- 复制算法(新生代)
- 可用区域分为大小相等的两块
- 一块用完,存活对象放置另一块,清理已使用的一块
- 优点:简单、高效。缺点:代价过高(只能每次使用原来的一半)
- 标记-整理算法(老年代)
- 先标记需要清理的对象
- 所有存货对象向一端移动
- 清理掉端边界以外的区域
- 分代收集算法(结合起来使用)
垃圾回收器
- 新生代
- Serial、ParNew、Parallel Scavenge
- 老年代
- CMS、Serial Old(MSC)、Parallel Old
- G1
- Serial收集器
- 单线程收集器(只会使用一个CPU或者一条线程去完成垃圾收集器工作,更重要的是在进行垃圾收集工作的时候,必须暂停其他所有的工作线程,直至收集结束)
- 复制算法
- ParNew收集器
- Serial收集器的多线程版本
- 复制算法
- Parallel Scavenge收集器
- 并行多线程收集器
- 复制算法
- 可控制的吞吐量
- 适合后台计算不需要太多交互任务
- Serial Old收集器
- 单线程收集器
- 标记-整理算法
- Parallel Old收集器
- 多线程收集器
- 标记-整理算法
- 注重吞吐量和CPU资源敏感
- CMS收集器
- 标记-清除算法
- 互联网或者B/S系统的服务端,重视响应速度
- G1收集器
对象优先在Eden区中分配
- 堆中内存空间分为新生代和老年代
- 新生代(复制算法)进一步分为Eden区域 survior from区域 survior to区域
- 每次创建对象的时候,首先在Eden区进行分配。Eden区满则在 Survior from中分配,如果Survior from也满了,则启动分配担保,将这两区域的对象转移到老年代,重新再Eden区中分配新的对象
大对象直接进入老年代
大对象:占用大量连续存储空间的对象
生命周期较长的对象进入老年代
相同年龄的对象内存超过Survior内存一半的对象进入老年代
Minor GC
当Eden内存区域满了的时候,将Eden区域的内存碎片和Survior from区域的转移到Survior to 中
Full GC
老年代的内存区域满了之后,全部的内存区域都进行GC