一、内存模型

Java的内存模型大致可以分为5个部分,分别是堆、方法区、虚拟机栈、本地方法栈、程序计数器。其中堆里主要存放创建的对象;方法区里存放加载的类信息、定义的常量以及静态变量信息;虚拟机栈主要是一个个的帧帧;本地方法栈与虚拟机栈差不多,也是存放栈帧,但它存放的是本地方法的栈帧;程序计数器,用来存放机器码执行的行数。Java内存模型如下图所示,其中虚拟机栈、本地方法栈、程序计数器为线程私有的内存区域,而堆与方法区是线程共享的内存区域
image.png

虚拟机栈中的栈帧,主要存放一个方法的局部变量表、操作数、动态链接和返回地址。
image.png

新生代

新生代的对象在创建不久后就不再使用。

老年代

老年代的对象会存活很长时间。

二、内存回收

对象是否存活

引用计数法

当对象的引用数量为0时,该对象就可以被JVM回收了。引用计数法一般在不商用的JVM中使用,其无法解决循环引用场景

可达性分析

分析对象是否有一条到达GcRoots的链路,如果没有可达路径时,对象可以被JVM回收。

对象自我拯救

当使用GC-Roots分析发现一个对象不可达时,会进行一次标记,并进行一次筛选,筛选的条件为对象是否有必要执行finalize方法。当前对象没有重写finalize方法或其finalze方法已经被执行过这两种情况被虚拟机视为“没有必要执行”。
对于要执行finalize方法的对象,会放置到一个叫做F-Queue的队列,并在稍等由虚拟机自动建立一个低优先级的线程Finalizer来执行这些finalize方法。这里的执行是批虚拟机会触发这些方法,但不保证要等待它执行完毕,原因是一个对象的finalize方法里执行了耗时工作、或发现了死循环,会导致整个Finalizer线程阻塞。
对象要自我拯救,可以在finalize方法里将this赋值到一个GC-Roots关联着的对象之上,此时在GC-Roots链路上对象是可达的,对象就会从可回收区域移出,但当对象再次不可达时,自我拯救失效,因为finalize方法已经执行过一次,不会再被执行。

对象引用

对象引用有4种,分别是强引用、软引用、弱引用和虚引用,引用的强度依次递减。

强引用(Strong Reference)

正常使用对象的方式都是强引用,如将对象赋值给一个变量。

软引用(Soft Reference)

软引用的引用强度为强引用稍弱一些。对于软引用关联的对象,在系统发生内存溢出之前,会将这些对象列进回收范围之中进行第二次回收。
使用SoftReferece<Object>

弱引用(Weak Reference)

弱引用比软引用的引用强度还要弱一些,关联的对象只能生存到下一次系统回收之前。当垃圾收集器工作时,无论当前内存中否足够,都会回收掉只被弱引用关联的对象。使用方式WeakReference<Object>

虚引用(Phantom Reference)

虚引用的对对象引用最弱。一对象是否有虚引用对象关联,完全不会影响其生存时间,也无法通过虚引用来取得一个对象实例。为对象设置虚引用关联的唯一目的就是能在这个对象被系统回收时收到一个系统通知。

回收算法

标记-清除算法

此算法分为“标记”和“清除”两个阶段:首先标记出哪些对象需要进行回收,在标记完成后统一进行回收。

  • 标记、清除两个过程效率都不高(为什么会不高呢?)
  • 会产生大量内存碎片

复制算法

将两个平分成两个部分,一部分内存被用完时,将还存活的对象依次复制到另一块内存,并清除当前内存区域。

  • 执行效率高,不用考虑内存碎片问题
  • 存活对象多时,效率降低
  • 成本高,将内存缩小了一半

标记-整理算法

标记-整理算法与标记-清除算法一样,也分两个阶段,首先标记出哪些对象需要回收,然后再对要回收的对象进行整理,将所有存活的对象都移向一端,然后直接清理掉边界以外的内存。

分代集算法

分代收集算法并不是一种新的收集算法,而根据内存区域的分类,使用不同的收集算法。根据对象存活周期不同,将内存划分为几块。一般把Java内存划分为新生代和老年代,这样就可以根据各个代的特点使用不同的收集算法。在新生代每次垃圾回收时发现有大量对象死去,只有少量存活,那就选择复制算法,只需要少量的复制操成本即可完成收集。在老年代,因对象存活率较高,就使用标记-清除或标记整理算法来进行回收。

三、内存泄漏

对象生命周期结束后,依赖被其他对象持有,导致对象不能被JVM回收,造成内存泄漏。

泄漏场景

  1. 静态的Activity变量
  2. 全局持有Activity的Context对象
  3. 动画循环播放,但在Activity销毁时没有取消播放
  4. Activity中内部类被异步线程持有
  5. 资源对象未关闭
  6. 容器中对象不使用时未及时清理
  7. 广播监听器等注册后未取消注册

解决方法

  1. 对象不再使用时要及时释放。如资源使用后要及时关闭、监听器不再使用时及时关闭、动画及时销毁等
  2. 对象销毁时,及时取消关联的异步线程执行
  3. 静态对象使用要合理

检测工具

  • profiler
  • LeakCannary

检测原理

四、内存溢出

  • OutMemory
    系统没有足够的内存空间给分配给对象。

内存溢出场景

  • 内存泄漏
  • 一次性分配大内存
  • 未压缩直接加载图片

排查方法

内存溢出与内存泄漏的关系

内存泄漏的最终结果是内存溢出,而内存溢出并不一定是由于内存泄漏导致。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐