本文正在参加「金石计划 . 分割6万现金大奖」

前语

本系列文章首要是汇总了一下大佬们的技术文章,归于Android根底部分,作为一名合格的安卓开发工程师,咱们肯定要熟练掌握java和android,本期就来说说这些~

[非商业用途,如有侵权,请告知我,我会删除]

DD一下: Android进阶开发各类文档,也可重视大众号<Android苦做舟>获取。

1.Android高档开发工程师必备根底技能
2.Android功用优化中心常识笔记
3.Android+音视频进阶开发面试题冲刺合集
4.Android 音视频开发入门到实战学习手册
5.Android Framework精编内核解析
6.Flutter实战进阶技术手册
7.近百个Android录播视频+音视频视频dome
.......

四、Android虚拟机废物收回

1.废物搜集算法

检查默许废物搜集算法

java -XX:+PrintCommandLineFlags -version

1.1 方针存活的判别

也便是说GC收回的时分,假如判别哪些方针要收回,那些方针不需求收回。

  • 引证计数法 引证计数算法是这样的,给方针添加一个引证计数器,每逢有一个地方调用他,就给计数器加1,每逢引证失效,计数器就减1;任何时刻计数器为0的方针就不能够再被运用。引证计数算法简略高效可是Java虚拟机没有选用引证计数算法来办理内存,首要是由于很难处理方针之间相互循环引证的问题可是Python言语有运用。
  • 可达性剖析算法(Java选用可达性剖析算法) Java和C#选用可达性剖析算法,根本思路便是经过一系列称为”GC Roots”的方针作为起点,从这个点开端向下查找,查找所走过的途径便是引证链(Reference Chain),当一个方针到GC Roots没有任何引证链相连时,则证明此方针是不可用的

在明晰了方针是否存活的算法后,那么Java程序中是怎么判别这个方针是不是‘非死不可’呢。其实即便在可达性剖析算法中不可达的方针,也并非是‘非死不可’的,这时分他们仅仅暂时处于‘缓行’阶段,而要真实宣告一个方针逝世要至少阅历两次符号进程。假如方针在阅历可达性剖析后发现没有与GC Roots相衔接的引证,那么它将被第一次符号而且进行挑选,挑选的条件是此方针是否有必要履行finalize()办法,或许finalize()现已被虚拟机调用过,虚拟机将这两种状况都看作是‘没必要履行挑选’。

假如方针被断定是有必要履行挑选的,那么这个方针将被放在一个叫做F-Queue的行列中,并在稍后由一个虚拟机自己创建的低优先级的线程去履行它。假如一个方针想要逃离逝世的命运,那么这时分是最终的一次机会,只需方针能够和引证链上的任何一个方针树立联系,那么在第2次符号的时分它将被移出“行将收回调集”;假如方针此刻还没有逃脱,那么稍后GC将对F-Queue中的方针进行第2次符号,被第2次符号上的方针根本上就被真的收回了。

重学Android基础系列篇(四):Android虚拟机垃圾回收

一般能作为GC Root:类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引证、本地办法栈 的变量等

1.2 什么时分会进行废物收回

GC收回一般是JVM自己操控的,咱们也能够经过System.gc()来让JVM进行废物收回,由于GC的时分耗费的资源比较大,不主张手动进行。

  • 当Eden区或许S区不行用了
  • 老时代空间不行用了
  • 办法区空间不行用了
  • System.gc()

2.废物搜集算法分类

  • 符号-铲除算法

    • 特色:先符号处需求收回的方针,然后在铲除需求被收回的方针

    • 缺点

      • 1.会产生许多内存碎片,空间不接连后面有大方针时无法运用,或许会引起一次废物收回
      • 2.符号和铲除两个进程都比较耗时,功率不高
  • 符号-仿制算法

    • 特色:将内存分为持平的两部分,每次只用一块当内存块用完的时分,将存活的仿制到另一块空间,然后把当时这块收拾掉,由于要预留一半空间,因而不合适老时代

    • 缺点

      • 1.处理了符号-铲除算法中铲除慢的问题,可是空间利用率低
      • 2.在方针存活率较高时就要进行较多的仿制操作,功率将会变低。更要害的是假如不想糟蹋50%的空间,就需求有额定的空间进行分配担保,以应对被运用的内存中一切方针都有100%存活的极点状况
  • 符号-收拾算法

    • 特色:符号进程和符号-铲除算法相同,可是接下来不是直接收回方针,而是让一切存活的方针都向一端移动,然后收拾掉鸿沟以外的内存
  • 分代搜集算法

    • 特色:

      • 1.当时商业虚拟机根本都采纳分代收回算法
      • 2.Young区选用仿制算法(Young区方针生命周期短,仿制功率更高)
      • 3.Old区一般是符号-铲除或许符号-收拾

符号-铲除算法

“符号-铲除”(Mark-Sweep)算法,如同名字相同,算法分为两个阶段,符号和铲除阶段。首要符号出一切需求收回的方针(可达性剖析),在符号完结后统一收回一切被符号的方针。这是最根底的算法,可是有两个问题,功率问题和空间问题,符号和铲除两个进程功率都不高;别的收回完结之后会产生许多不接连的内存空间,会导致在后面假如有大方针的时分,有或许找不到满足的接连内存然后不得不进行一次废物搜集动作

收回前:

重学Android基础系列篇(四):Android虚拟机垃圾回收

收回后 :

重学Android基础系列篇(四):Android虚拟机垃圾回收

符号-仿制算法

为了处理“符号-铲除”算法的功率问题,出现了仿制算法,它将内存分为巨细持平的两块,每次只运用一块。当这一块内存运用完了,就将还存活的的方针仿制到另一块上面,然后再把另一块内存彻底收拾掉。这样就不用考虑内存碎片的问题,可是每次只能运用一般内存,代价太高。这种算法用来收回新生代,新生代中的方针大多都是‘朝生夕死’,所以并不用按照1比1来区分,而是将内存分为一块大的Eden空间和两块较小的Survivor空间,当收回回时,将Eden和1个Survior中存活的方针一次copy到另一个Survior中,也便是说最多就10%的糟蹋。HotSpon默许的Eden和Survior的份额是8:1。加入收回时1个Survior中的空间不行,那么需求依靠老时代来进行分配担保。

重学Android基础系列篇(四):Android虚拟机垃圾回收

符号-收拾算法

“符号-收拾”(Mark-Compact),符号进程和符号-铲除相同,可是接下来不是直接对可收回方针进行收拾,而是让一切存活方针都想一端移动,然后收拾掉端鸿沟以外的部分。

重学Android基础系列篇(四):Android虚拟机垃圾回收

分代搜集算法

当时商业虚拟机的的废物搜集都选用"分代搜集"(Generational Collection)算法。便是依据方针存活周期将内存分为几块,一般Java堆是分为新生代和老时代,这样各个代就能够依据自己的特色选择对应的算法。在新生代方针只要少数存活就选择仿制算法;而老时代方针存活率高,而且没有而外空间来担保,就有必要选用“符号-铲除”或许“符号-收拾”算法来进行收回

3.废物搜集器

假如说算法是废物收回的办法论,那么废物搜集器便是内存收回的具体完结了。虚拟机中并没有明晰规定,因而不同的厂商,不同版别都有自己的完结,首要为一下几种:

  • Serial搜集器:单线程搜集进程需求暂停一切线程,选用仿制算法合适用于新生代(Young)
  • Serial Old搜集器:Serial的老时代版别,也是单线程的,采纳符号-收拾算法
  • ParNew搜集器:Serial的多线程版别,选用仿制算法适用于新生代,多CPU时分比Serial功率高,单CPU时分效果不如Serial
  • Parallel Scavenge搜集器:新生代(Young)搜集器选用仿制算法,并行的多线程搜集器,和ParNew比较跟重视吞吐量
  • Parallel Old搜集器:Parallel Scavenge的老时代版别选用符号-收拾算法
  • CMS搜集器:并发搜集、低中止,选用符号-铲除算法产生许多空间碎片,并发阶段会下降吞吐量
  • G1搜集器:将对区分为巨细持平的Region,保留了新生代和老时代的概念可是不再是物理隔离了,选用符号-收拾算法不会产生空间碎片,能够让运用者指定在M毫秒的时刻内,花费在废物搜集的时刻不超越N毫秒
  • ZGC:没有碎片问题,不存在新老时代的概念

Serial搜集器

Serial是一个历史悠久的搜集器,这个搜集器是单线程的搜集器。这里的单线程不是说只能有一个CPU或许1条搜集线程来完结废物搜集作业,而是说它在进行废物收回的时分,有必要暂停其他作业线程,直到它搜集完毕。“Stop the world”实际用起来咱们并不能承受,假定程序正在跑,可是隔一会就要程序停下来等候废物搜集,例如等候5分钟,那么这种咱们是肯定不能承受的。

重学Android基础系列篇(四):Android虚拟机垃圾回收

Serial Old搜集器

是Serial的老时代版别,也是单线程搜集器,运用“符号-收拾”算法。

重学Android基础系列篇(四):Android虚拟机垃圾回收

ParNew搜集器

ParNew其实便是Serial的多线程版别,其他和Serial比起来没太大不同,可是ParNew在单CPU的环境下不会比Serial效果好,在两个CPU的环境中ParNew也不能说会稳超Serial,可是多核变得很常见,ParNew在一些CPU比较多的环境下,效果仍是比较好的。咱们能够经过参数-XX:ParallelGCThreads来约束参加废物搜集的线程数。默许状况下敞开的线程数和CPU数量相同。许多时分咱们并不期待运用悉数CPU资源。

重学Android基础系列篇(四):Android虚拟机垃圾回收

Parallel Scavenge搜集器

Parallel Scavenge搜集器是一个新生代搜集器,选用仿制算法也是多线程的搜集器。它和ParNew的区别是,其他搜集器重视的是搜集是尽或许缩短用户线程的中止时刻,而Parallel Scavenge是为了到达一个可控的吞吐量。

所谓吞吐量=运转用户代码时刻/(废物搜集时刻+用户代码运转时刻)

/**
*设置最大废物搜集中止时刻,可是有必要是一个大于0的毫秒数,可是并不是说这个值设置到十分小,就能使废物搜集速度快。
*/
-XX:MaxGCPauseMillis
/**
*设置吞吐量巨细是一个大于0且小于100的整数,也便是废物搜集时刻占总时刻的占比
*/
-XX:GCTimeRatio:
//手艺指定新生代巨细
-Xmn
//Eden和Survivor的占比
-XX:SurvivorRatio
//GC自适应调理战略,设置了该参数就不需求手动指定新生代,也不需求指定Eden和Survivor占比等参数
-XX:+UseAdaptiveSizePolicy

Parallel Old搜集器

Parallel Scavenge的老时代版别,运用多线程和“符号-收拾”算法。在一些注重吞吐量和CPU灵敏的场合能够运用Parallel Scavenge+Parallel Old(Java8默许废物搜集器)。

CMS搜集器

CMS(Concurrent Mark Sweep)是一种以获取最短中止时刻的废物搜集器,是“符号-铲除”算法完结的。他的运转进程杂乱点,有四个进程:

  • 初始符号:需求“Stop the world”,初始符号仅仅仅仅符号一下GC Roots能直接相关到的方针,速度很快。

  • 并发符号:是进行GC Roots追踪(GC Roots Tracing)的进程.这个进程和用户线程并发履行。

  • 从头符号:需求“Stop the world”,是为了批改并发符号期间由于用户程序持续运转而导致符号产生变动的那一部分的方针的符号记载,这个中止时刻大于初始符号,可是远小于并发符号进程。

  • 并发铲除:和用户程序并发履行。 CMS的优点是,并发搜集,低中止。有一个显着缺点便是由于选用了“符号-铲除”算法,最终会出现许多碎片,有或许会出现在某一个时刻,当有大方针生成,不得不进行一次Full GC来处理这个问题。为了处理该问题CMS有一个参数-XX:UseCmsCompactAtFullCollection来处理由于空间缺乏进行Full GC。这个参数默许敞开,用于在CMS搜集器顶不住要进行Full GC是敞开内存碎片兼并收拾的进程,内存收拾进程是无法并发的,因而就会耗时。一起还有一个参数是-XX:CMSFullGCsBeforeCompaction,这个参数是用于履行屡次不紧缩的GC后,跟着来一次紧缩的(默许是0,表明每次进入Full GC都进行碎片收拾)。

重学Android基础系列篇(四):Android虚拟机垃圾回收

G1搜集器

G1(Garbage-First)搜集器:并行和并发,分代搜集,空间整合,可预测的挺顿。 G1和其他搜集器很大的不同是,其他搜集器搜集范围都是整个新生代和老时代,而G1不再是这样。运用G1搜集器的时分,Java堆分为多个巨细持平的区域(Region),尽管也保留了新生代和老时代的概念,可是不再是物理隔离了,他们都是一部分Region的调集(这些Region纷歧定是接连的)。G1保留了Eden和Survivor的份额也是8:1:1。

作业进程

  • 初始符号(Initial Marking) 符号以下GC Roots能够相关的方针,而且修正TAMS的值,需求暂停用户线程

  • 并发符号(Concurrent Marking) 从GC Roots进行可达性剖析,找出存活的方针,与用户线程并发履行

  • 终究符号(Final Marking) 批改在并发符号阶段由于用户程序的并发履行导致变动的数据,需暂停用户线程

  • 挑选收回(Live Data Counting and Evacuation) 对各个Region的收回价值和成本进行排序,依据用户所期望的GC中止时刻制定收回计划

重学Android基础系列篇(四):Android虚拟机垃圾回收

HumongousObject:一个巨细到达甚至超越分区Region 50%以上的方针称为巨型方针(Humongous Object),巨型方针会独占一个或多个接连分区。HumongousObject直接分配在老时代。

重学Android基础系列篇(四):Android虚拟机垃圾回收

ZGC

JDK11引入的ZGC搜集器,在物理和逻辑上现已没有新/老时代的概念了,会分为一个个page,当进行GC操作时分会对page进行紧缩,因而没有碎片问题,只能在64位linux上运用。

  • 能够到达10ms以内的中止要求
  • 支撑TB等级的内存
  • 堆内存变大后中止时刻仍是在10ms以内

4.废物搜集器分类

  • 串行搜集器:只能有一个废物收回线程履行,用户线程暂停,适用于内存小的嵌入式设备 Serial和Serial Old
  • 并行搜集器吞吐量优先,多个废物搜集线程一起作业,可是这时分用户线程处于等候状况 Parallel Scanvenge、Parallel Old
  • 并发搜集器中止时刻优先,用户线程和废物搜集线程一起履行(但并纷歧定是并行的,或许是交替履行的),废物搜集线程在履行的时分不会中止用户线程的运转,合适web场景 CMS、G1

5.附录

常用JVM调试指令

jstack:检查栈信息,可用于多线程时剖析和检查线程运转状况。

jstack pid
#检查pid=443进程的栈信息
jstack 443

jmap:检查堆内存信息和生成dump文件

//检查进程的内存映像信息
1.jmap pid
//显现Java堆具体信息,打印一个堆的摘要信息,包括运用的GC算法、堆装备信息和各内存区域内存运用信息
2.jmap heap pid
//显现堆中方针的核算信息,其间包括每个Java类、方针数量、内存巨细(单位:字节)、彻底限定的类名。打印的虚拟机内部的类名称将会带有一个’*’前缀。假如指定了live子选项,则只核算活动的方针
3.jmap -histo:live pid
//打印类加载器信息,-clstats是-permstat的代替计划,在JDK8之前,-permstat用来打印类加载器的数据
打印Java堆内存的永久保存区域的类加载器的智能核算信息。对于每个类加载器而言,它的名称、活泼度、地址、父类加载器、它所加载的类的数量和巨细都会被打印。此外,包括的字符串数量和巨细也会被打印
4.jmap -clstats pid
//打印等候完结的方针信息,Number of objects pending for finalization: 0 阐明当时F-QUEUE行列中并没有等候Fializer线程履行final
5.jmap -finalizerinfo pid
//生成堆转储快照dump文件,以hprof二进制格局转储Java堆到指定filename的文件中。live子选项是可选的。假如指定了live子选项,堆中只要活动的方针会被转储。想要浏览heap dump,你能够运用jhat(Java堆剖析东西)读取生成的文件
6.jmap -dump:format=b,file=heapdump.phrof pid

jmap -dump:format=b,file=heapdump.phrof pid,这个指令履行,JVM会将整个heap的信息dump写入到一个文件,heap假如比较大的话,就会导致这个进程比较耗时,而且履行的进程中为了保证dump的信息是可靠的,所以会暂停运用, 线上体系慎用

jhat:能够在浏览器解析和检查dump文件

//检查dumpfile的内存具体
jhat dumpfile
q@troyMac oom % jhat java_pid3874.hprof
Reading from java_pid3874.hprof...
Dump file created Sat May 09 11:45:32 CST 2020
Snapshot read, resolving...
Resolving 814571 objects...
Chasing references, expect 162 dots..................................................................................................................................................................
Eliminating duplicate references..................................................................................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

6.物理内存与虚拟内存

物理内存

物理内存指的是内存条上的内存,早期一个进程的数据是悉数加载在物理内存上,CPU直接经过物理内存地址来拜访进程数据。这种办法会产生以下几个问题:

  • 内存不行用:发动的运用过多,悉数加载会导致内存条的空间不行用。
  • 内存占用糟蹋:当运用越来越大的时分,用户或许只用到部分功用,此刻假如悉数加载到内存,会导致内存占用糟蹋。
  • 内存数据的安全问题:经过拜访物理地址,能够直接修正物理内存上的数据。

为了处理物理内存的这几个问题,CPU拜访进程数据就不能直接经过物理内存地址,而是经过虚拟内存来直接拜访。

虚拟内存

虚拟内存是处于进程和物理内存之间的一个中间层,由体系生成,内部作分页办理,结构如下图所示:

重学Android基础系列篇(四):Android虚拟机垃圾回收

一个虚拟内存对应一个进程,巨细为4GB,虚拟内存里会分为许多页(page),每页的巨细在iOS中为16kb,其他体系中为4kbPage里的每一格对应进程中的某一项数据,会记载该数据的虚拟内存地址和物理内存地址,因而虚拟内存实质上是一张相关进程各项数据的虚拟内存地址和物理内存地址的映射表官方文档 后续验证

选用虚拟内存后,CPU拜访进程数据的状况如下:

  • 进程发动后,体系会为进程树立一个对应的虚拟内存,里边记载了进程每项数据的虚拟内存地址,此刻进程还未加载到物理内存中,所以page记载的各项数据的物理内存地址为0x00000…。
  • 当进程的某部分活泼后,CPU依据这部分数据的虚拟内存地址找到其对应的物理内存地址,再经过物理地址拜访到物理内存上的数据。
  • 假如在page上没有找到对应的物理地址时,阐明此page上所相关的进程数据没被加载到物理内存中,此刻会触发缺页反常(Page Fault),中止当时进程,先将当时页所对应的进程数据加载到物理内存中,然后page会记载每项数据的物理地址,CPU再经过物理地址来拜访内存上的数据。

因而,比较直接拜访物理内存,虚拟内存的优势如下:

  • 内存运用更高效:进程的数据经过分页办理后,只将活泼的page所相关的数据加载在物理内存中,当物理内存都被占用的时分,此刻会覆盖掉不活泼的内存,加载当时活泼的page数据,这样就能进步对内存的运用功率。
  • 内存数据更安全:每次发动进程,体系都会从头树立对应的虚拟内存,并为虚拟内存分配一个ASLR随机值(Address Space Layout Randomization),数据的虚拟地址即为:ASLR随机值+偏移值,这样数据的虚拟地址每次都会变,而且CPU是经过虚拟内存来直接拜访物理内存的,在这个进程中物理内存地址没有露出出来,所以就能保证内存数据的安全性。

7.GC root算法

7.1 可达性剖析算法

  • JVM中的废物收回器经过可达性剖析来探究一切存活的方针
  • 具体是这样的:扫描堆中的方针,看能否沿着GC Root方针为起点的引证链找到该方针,假如找不到,则表明能够收回

7.2 哪些方针能够作为 GC Roots 呢?

  • 虚拟机栈(栈帧中的本地变量表)中引证的方针。
  • 办法区中类静态特点引证的方针
  • 办法区中常量引证的方针
  • 本地办法栈中JNI(即一般说的Native办法)引证的方针
  • 一切被同步锁持有的方针

7.3 为什么选用可达性剖析而不是引证记数?

  • 引证计数尽管简略,但或许产生方针间相互引证而无法被GC的状况,会形成内存走漏

    引证记数法有一个严峻的问题,即无法处理循环引证的状况。一个简略的循环引证问题的描述如下:有方针A和方针B,方针A中含有方针B的引证,方针B中含有 方针A的引证。此刻,方针A和方针B的引证计数器都不为0,可是在体系中却不存在任何第3个方针引证了A或B。也便是说,A和B是应该被收回的废物方针,可是由于 废物方针之间相互引证,然后使废物收回器无法识别,引起内存走漏。

重学Android基础系列篇(四):Android虚拟机垃圾回收

将最终一个元素的next特点指向第一个元素,即引证第一个元素,然后构成循环引证。这个时分假如将列表的头head赋值为null,此刻列表的各个元素的计数器都不为0,一起也失去了对列表的引证 操控,然后导致列表元素不能被收回。
引证计数器拥有一些特性,首要它需求独自的字段存储计数器,这样的做法添加了存储空间的开支。其次,每次赋值都需求更新计数器,这添加了时刻开支。再者,废物方针便于辨识,只需计数器为0,就可作为废物收回 ,接下来它能便利及时的收回废物,没有延迟性最终不能处理循环引证的问题,正是由于最终一条知名缺点,导致在java的废物收回器中没有运用这类算法。

7.4 四种引证是什么?

  • 强引证:曾经咱们运用的大部分引证实际上都是强引证,这是运用最遍及的引证。假如一个方针具有强引证,废物收回器不会收回它。
  • 软引证:当触发GC时,假如内存空间满足,废物收回器就不会收回被软引证(例如softReference)所指的方针,只要当废物收回后内存空间缺乏了才会收回软引证所指的内存。
  • 弱引证:当触发Full GC时,无论内存空间足缺乏够,弱引证所指的方针都会被收回
  • 虚引证:假如一个方针仅持有虚引证,在任何时分都或许被废物收回。虚引证首要与ByteBuffer运用,当虚引证进入引证行列时,由一个Handler调用虚引证相关办法开释ByteBuffer的直接内存

ps:

  • 软引证和弱引证能够和一个引证行列(ReferenceQueue)联合运用,假如软引证弱引证所指的方针被废物收回,JAVA 虚拟机就会把这个软引证弱引证本身加入到与之相关的引证行列中,由一个优先级很低的Cleaner进行收回。
  • 虚引证有必要合作引证行列运用

8. 内存泄露的了解与分类

8.1 何为内存走漏(memory leak)

可达性剖析算法来判别方针是否是不再运用的方针,实质都是判别一个方针是否还被引证。那么对于这种状况下,由于代码的完结不同就会出现许多内存走漏问题(让JVM误以为此方针还在引证中,无法收回,形成内存走漏)。

首要重视两点:

  1. 是否还被运用?是
  2. 是否还被需求?否

8.2 内存走漏(memory leak)的了解

严厉来说,只要方针不会再被程序用到了,可是GC又不能收回他们的状况,才叫内存走漏。但实际状况许多时分一些不太好的实践(或疏忽)才导致方针的生命周期变得很长甚至导致OOM,也能够叫做宽泛意义上的“内存走漏”。

8.3 内存走漏与内存溢出的联系

  1. 内存走漏(memory leak)请求了内存用完了不开释,比方一共有1024M的内存,分配了512M的内存一向不收回,那么能够用的内存只要512M了,仿佛走漏掉了一部分;浅显一点来讲,内存走漏便是【占着茅坑不拉屎】。
  2. 内存溢出(out of memory)请求内存时,没有满足的内存能够运用;浅显一点儿讲,一个厕所就三个坑,有两个站着茅坑不走的(内存走漏),剩余最终一个坑,厕所表明招待压力很大,这时分一会儿来了两个人,坑位(内存)就不行了,内存走漏变成了内存溢出了。

8.4 内存走漏与内存溢出的联系

内存走漏的增多,终究会导致内存溢出。

8.5 走漏的分类

  1. 常常产生:产生内存走漏的代码会被屡次履行,每次履行,走漏一块内存;
  2. 偶尔产生:在某些特定状况下才会产生;
  3. 一次性:产生内存走漏的办法只会履行一次;
  4. 隐式走漏:一向占着内存不开释,直到履行完毕;严厉的说这个不算内存走漏,由于终究开释掉了,

图例

重学Android基础系列篇(四):Android虚拟机垃圾回收

8.6 Java中内存走漏的8种状况

8.6.1 静态调集类

静态调集类,如HashMap、LinkedList等等。假如这些容器为静态的,那么它们的声明周期与JVM程序一致,则容器中的方针在程序完毕之前将不能被开释,然后形成内存走漏。简略而言,长生命周期的方针有短生命周期方针的引证,尽管短生命周期的方针不再运用,可是由于长生命周期方针持有它的引证而导致不能被收回。

public class MemoryLeak {
  static List list = new ArrayList();
  
  public void oomTests() {
    Object obj = new Object();// 局部变量
    list.add(obj);
  }
8.6.2 单例形式

单例形式,和静态调集导致内存走漏的原因类似,由于单例的静态特性,它的生命周期和JVM的生命周期相同长,所以假如单例方针假如持有外部方针的引证,那么这个外部方针也不会被收回,那么就会形成内存走漏。

8.6.3 内部类持有外部类

内部类持有外部类,假如一个外部类的实例方针的办法回来了一个内部类的实例方针。这个内部类方针被长时刻引证了,即便那个外部类实例方针不再被运用,但由于内部类持有外部类的实例方针,这个外部类方针将不会被废物收回,这也会形成内存走漏。

8.6.4 各种衔接,如数据库衔接、网络衔接和IO衔接等

各种衔接,如数据库衔接、网络衔接和IO衔接等。在对数据库进行操作的进程中,首要需求树立与数据库的衔接,当不再运用时,需求调用close办法来开释与数据库的衔接。只要衔接被封闭后,废物收回器才会收回对应的方针。否则,假如在拜访数据库的进程中,对Connection、Statement或ResultSet不显性的封闭,将会形成许多的方针无法被收回,然后引起内存走漏。

8.6.5 变量不合理的效果域

变量不合理的效果域。一般而言,一个变量的定义的效果范围大于其运用范围,很或许会形成内存走漏。另一方面,假如没有及时地把方针设置为null,很有或许导致内存走漏的产生。

8.6.6 改动哈希值

改动哈希值,当一个方针被存储进HashSet调集中今后,就不能修正这个方针中的那些参加核算哈希值的字段了。否则方针修正后的哈希值与开始存储进HashSet调集中时的哈希值就不同了,在这种状况下,即便在contains办法运用该方针的当时引证作为的参数去HashSet调集中检索方针,也将回来找不到方针的成果,这也会导致无法从HashSet调集中独自删除当时方针,形成内存走漏。这也是String为什么被设置成了不可变类型,咱们能够放心的把String存如HashSet,或许把String作为HashMap的key值;

8.6.7 缓存走漏

内存走漏的另一个常见来历是缓存,一旦你把方针引证放入到缓存中,他就很简单遗忘。比方:之前项目在一次上线的时分,运用发动奇慢知道夯死,便是由于代码中加载一个表中的数据到缓存(内存)中,测试环境只要几百条数据,可是生产环境有几百万的数据。

对于这个问题,能够运用WeakHashMap代表缓存,此种Map的特色,当除了本身有对key的引证外,此key没有其他引证那么此map会主动丢弃此值。

8.6.8 监听器和回调

内存走漏另一个常见来历是监控器和其他回调,假如客户端在你完结的API中注册回调,却没有显现的取消,那么就会积累。

需求保证回调立即被当作废物收回的最佳办法是只保存它的弱引证,例如将他们保存为WeakHashMap中的键。

9.内存走漏事例剖析

事例代码

public class Stack {
  private Object[] elements;
  private int size = 0;
  private static final int DEFAULT_INITIAL_CAPACITY = 16;
  public Stack() {
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
   }
  public void push(Object e) {
    elements[size++] = e;
   }
  // 存在内存走漏问题
  public Object pop() {
    if (size == 0)
      throw new EmptyStackException();
    return elements[--size];
   }
  private void ensureCapacity() {
    if (size == elements.length)
      elements = Arrays.copyOf(elements, size * 2 + 1);
   }
}

剖析

上述程序并没有显着的错误,可是这段程序有一个内存走漏,随着GC活动的添加,或许内存占用的不断添加,程序功用的下降就会表现出来,严峻时可导致内存走漏,可是这种失败状况相对较少。代码的首要问题在pop函数,下面经过这张图示展示,假定这个栈一向增加,增加后如下图所示。

重学Android基础系列篇(四):Android虚拟机垃圾回收

当进行许多的pop操作时,由于引证未进行置空,gc是不会开释的,如下图所示

重学Android基础系列篇(四):Android虚拟机垃圾回收

从上图中能够看出,假如栈先增加,在收缩,那么从栈中弹出的方针将不会被当作废物收回,即便程序不再运用栈中的这些方针,他们也不会收回,由于栈中任然保存着这些方针的引证,俗称过期引证,这个内存走漏很荫蔽。

处理办法

  public Object pop() {
    if (size == 0)
      throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;
    return result;
   }

一旦引证过期,清空这些引证,将引证置空。

重学Android基础系列篇(四):Android虚拟机垃圾回收

10.MAT

1. MAT 东西简介

MAT(全名:Memory Analyzer Tool),是一款快速便捷且功用强大丰厚的 JVM 堆内存离线剖析东西。其经过展示 JVM 反常时所记载的运转时堆转储快照(Heap dump)状况(正常运转时也能够做堆转储剖析),协助定位内存走漏问题或优化大内存耗费逻辑。

1.1 MAT 运用场景及首要处理问题

场景一:内存溢出,JVM堆区或办法区放不下存活及待请求的方针。如:高峰期体系出现 OOM(Out of Memory)反常,需定位内存瓶颈点来辅导优化。

场景二:内存走漏,不会再运用的方针无法被废物收回器收回。如:体系运转一段时刻后出现 Full GC,甚至周期性 OOM 后需人工重启处理。

场景三:内存占用高。如:体系频繁 GC ,需定位影响服务实时性、稳定性、吞吐才能的原因。

1.2 根底概念

1.2.1 Heap Dump

Heap Dump 是 Java 进程堆内存在一个时刻点的快照,支撑 HPROF 及 DTFJ 格局,前者由 Oracle 系列 JVM 生成,后者是 IBM 系列 JVM 生成。其内容首要包括以下几类:

  • 一切方针的实例信息:方针所属类名、根底类型和引证类型的特点等。
  • 一切类信息:类加载器、类名、承继联系、静态特点等。
  • GC Root:GC Root 代表经过可达性剖析来断定 JVM 方针是否存活的开始调集。JVM 选用追踪式废物收回(Tracing GC)形式,从一切 GC Roots 动身经过引证联系能够相关的方针便是存活的(且不可收回),其他的不可达的方针(Unreachable object:假如无法从 GC Root 找到一条引证途径能到达某方针,则该方针为Unreachable object)能够收回。
  • 线程栈及局部变量:快照生成时刻的一切线程的线程栈帧,以及每个线程栈的局部变量。

1.2.2 Shallow Heap

Shallow Heap 代表一个方针结构本身所占用的内存巨细,不包括其特点引证方针所占的内存。如 java.util.ArrayList 方针的 Shallow Heap 包括8字节的方针头、8字节的方针数组特点 elementData 引证 、 4字节的 size 特点、4字节的 modCount 特点(从 AbstractList 承继及方针头占用内存巨细),有的方针或许需求加对齐填充但 ArrayList 本身已对齐不需弥补,留意不包括 elementData 具体数据占用的内存巨细。

1.2.3 Retained Set

一个方针的 Retained Set,指的是该方针被 GC 收回后,一切能被收回的方针调集(如下图所示,G的 Retain Set 只要 G 并不包括 H,原因是尽管 H 也被 G 引证,但由于 H 也被 F 引证 ,G 被废物收回时无法开释 H);别的,当该方针无法被 GC 收回,则其 Retained set 也必定无法被 GC 收回。

1.2.4 Retained Heap

Retained Heap 是一个方针被 GC 收回后,可开释的内存巨细,等于开释方针的 Retained Heap 中一切方针的 Shallow Heap 的和(如下图所示,E 的 Retain Heap 便是 G 与 E 的 Shallow Heap 总和,同理不包括 H)。

1.2.5 Dominator tree

假如一切指向方针 Y 的途径都经过方针 X,则 X 分配(dominate) Y(如下图中,C、D 均分配 F,但 G 并不分配 H)。Dominator tree 是依据方针引证及分配联系生成的全体树状图,分配树明晰描述了方针间的依靠联系,下图左的 Dominator tree 如下图右下方分配树示意图所示。分配联系还有如下联系:

  • Dominator tree 中任一节点的子树便是被该节点分配的节点调集,也便是其 Retain Set。
  • 假如 X 直接分配 Y,则 X 的一切分配节点均分配 Y。

重学Android基础系列篇(四):Android虚拟机垃圾回收

1.2.6 OQL

OQL 是类似于 SQL 的 MAT 专用统一查询言语,能够依据杂乱的查询条件对 dump 文件中的类或许方针等数据进行查询挑选。

1.2.7 references

outgoing references、incoming references 能够直击方针间依靠联系,MAT 也供给了链式快速操作。

  • outgoing references:方针引证的外部方针(留意不包括方针的根本类型特点。根本特点内容可在 inspector 检查)。
  • incoming references:直接引证了当时方针的方针,每个方针的 incoming references 或许有 0 到多个。
2. MAT 功用概述及比照
2.1 MAT 功用概述

注:MAT 的产品才能十分丰厚,本文简要总结产品特性帮我们了解全貌,鄙人一篇文章《JVM 内存剖析实战进阶篇——中心功用及运用场景》中,会具体翻开介绍各项中心功用的场景、事例、最佳实践等。

MAT 的作业原理是对 dump 文件树立多种索引,并依据索引来完结 [1]内存散布、[2]方针间依靠(如实体方针引证联系、线程引证联系、ClassLoader引证联系等)、[3]方针状况(内存占用量、字段特点值等)、[4]条件检索(OQL、正则匹配查询等)这四大中心功用,并经过可视化展示辅佐 Developer 精细化了解 JVM 堆内存全貌。

2.1.1 内存散布

  • 大局概览信息:堆内存巨细、方针个数、类的个数、类加载器的个数、GC root 个数、线程概略等大局核算信息。
  • Dominator tree:按方针的 Retain Heap 排序,也支撑按多个维度聚类核算,最常用的功用之一。
  • Histogram:罗列每个类实例的内存占比,包括本身内存占用量(Shallow Heap)及分配方针的内存占用量(Retain Heap),支撑按 package、class loader、super class、class 聚类核算,最常用的功用之一。
  • Leak Suspects:直击引证链条上占用内存较多的可疑方针,可处理一些根底问题,但杂乱的问题往往协助有限。
  • Top Consumers:展示哪些类、哪些 class loader、哪些 package 占用最高份额的内存。

2.1.2 方针间依靠

  • References:供给方针的外部引证联系、被引证联系。经过任一方针的直接引证及直接引证概况(首要是特点值及内存占用),从而供给完善的依靠链路概况。
  • Dominator tree:支撑按方针的 Retain Heap 排序,并供给具体的分配联系,结合 references 能够完结大方针快速相关剖析;
  • Thread overview:展示转储 dump 文件时线程栈帧等具体状况,也供给各线程的 Retain Heap 等相关内存信息。
  • Path To GC Roots:供给任一方针到GC Root的链路概况,协助了解不能被 GC 收回的原因。

2.1.3 方针状况

  • 最中心的是经过 inspector 面板供给方针的特点信息、类承继联系信息等数据,协助剖析内存占用高与事务逻辑的联系。
  • 调集状况的检测,如:经过 ArrayList 或数组的填充率定位空调集空数组形成的内存糟蹋、经过 HashMap 抵触率断定 hash 战略是否合理等。

2.1.4 按条件检索方针

  • OQL:供给一种类似于SQL的方针(类)等级统一结构化查询言语。如:查找 size=0 且未运用过的 ArrayList: select * from java.util.ArrayList where size=0 and modCount=0;查找一切的String的length特点的: select s.length from instanceof String s。
  • 内存散布及方针间依靠的众多功用,均支撑按字符串检索、按正则检索等操作。
  • 按虚拟内存地址寻址,依据方针的十六进制地址查找方针。

此外,为了便于回忆与回顾,收拾了如下脑图:

重学Android基础系列篇(四):Android虚拟机垃圾回收

2.2 常见内存剖析东西比照

下图中 Y 表明支撑,N 表明不支撑,时刻截至发稿前。

产品功用 MAT JProfiler Visual VM jhat jmap hprof
方针相关剖析、深浅堆、GC ROOT、内存走漏检测、线程剖析、供给自定义程序扩展扩展 Y N N N N N
离线大局剖析 Y N Y Y N N
内存实时分配状况 N Y Y Y Y Y
OQL Y N Y N N N
内存分配堆栈、热门份额 N Y N N N N
堆外内存剖析 N N N N N N

注 1:Dump 文件包括快照被转储时刻的 Java 方针 在堆内存中的散布状况,但快照仅仅瞬间的记载,所以不包括方针在何时、在哪个办法中被分配这类信息。

注 2:一般堆外内存溢出排查可结合 gperftools 与 btrace 排查,此类文章较多不翻开介绍。

3. Quick Start 及运用技巧
3.1 Quick Start

注:Quick Start 文章较多,本文着重介绍装置流程及运用技巧。

1、装置 MAT:戳【下载链接】;也可直接集成到 Eclipse IDE中(途径:Eclipse → Help → Eclipse Marketplace → 搜 “MAT”)。

2、调理 MAT 堆内存巨细:MAT 剖析时也作为 Java 进程运转,假如有满足的内存,主张至少分配 dump 文件巨细*1.2 倍的内存给 MAT,这样剖析速度会比较快。办法是修正MemoryAnalyer.ini文件,调整Xmx参数(Windows 可用查找神器 everything 软件查找并修正、MAC OS 一般在 /Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini,如找不到可用 Alfred 软件查询修正)。

3、获取堆快照 dump 文件(堆转储需求先履行 Full GC,线上服务运用时请留意影响),一般用三种办法:

  • 运用 JDK 供给的 jmap 东西,指令是 jmap -dump:format=b,file=文件名 进程号。当进程接近僵死时,能够添加 -F 参数强制转储:jmap -F -dump:format=b,file=文件名 进程号。
  • 本地运转的 Java 进程,直接在 MAT 运用 File → accquire heap dump 功用获取。
  • 发动 Java 进程时装备JVM参数:-XX:-HeapDumpOnOutOfMemoryError,当产生 OOM 时无需人工干预会主动生成 dump文件。指定目录用 -XX:HeapDumpPath=文件途径 来设置。

4、剖析 dump 文件:途径是 File → Open Heap Dump ,然后 MAT 会树立索引并剖析,dump 文件较大时耗时会很长。剖析后 dump 文件所在目录会有后缀为 index 的索引文件,也会有包括 HTML 格局的后缀为 zip 的文件。

5、完结索引核算后,MAT 出现概要视图(Overview),包括三个部分:

  • 大局概览信息,堆内存巨细、类数量、实例数量、Class Loader数量。
  • Unreachable Object Histogram,展示转储快照时可被收回的方针信息(一般不需求重视,除非 GC 频繁影响实时性的场景剖析才用到)
  • Biggest Objects by Retained Size,展示经过核算过的哪几个实例所相关的方针占内存总和较高,以及具体占用的内存巨细,一般相关代码比较简略状况下,往往能够直接剖析具体的引证联系反常,如内存走漏等。此外也包括了最大方针和链接支撑持续深入剖析。
    重学Android基础系列篇(四):Android虚拟机垃圾回收

6、假如代码比较杂乱,需求持续运用 MAT 各种东西并结合事务代码进一步剖析内存反常的原因。最常用的几项如下(具体事例、场景、运用办法在《JVM 内存剖析东西 MAT 的深度解说与实践——进阶篇》具体介绍):

  • 检查堆全体状况的:Histogram、Dominator tree、Thread details等(各功用进口收拾如下)
    重学Android基础系列篇(四):Android虚拟机垃圾回收
  • MAT 剖析过的 Top Consumers 、Leak Suspects等
    重学Android基础系列篇(四):Android虚拟机垃圾回收
3.2 运用技巧及留意事项

1、留意对运转进程的功用影响:Heap dump 时会先进行 Full GC,别的为保证方针数据视图一致,需求在安全点 Stop The World 暂停呼应,线上服务进行必须留意功用影响。能够采纳以下技巧削减影响:

  • 先禁用进口流量,再履行 dump 动作。
  • 选择影响较小时 dump 内存。
  • 运用脚本捕获指定事情时 dump 内存。

2、Dump 文件及树立的索引文件或许较大,假如开发机装备缺乏无法剖析,可在服务器先履行剖析后,依据剖析后的索引文件直接检查成果,别的也需求留意磁盘占用问题:

  • 大文件剖析办法:一般 dump 文件不高于剖析机主存 1.2 倍可直接在开发机剖析;若 dump 文件过大,能够运用 MAT 供给的脚本在装备高的高配机器先树立索引再直接展示索引剖析成果(一般是 Linux 机器,能够运用 MAT 供给的脚本:./ParseHeapDump.sh $HEAPDUMP,堆信息有 unreachable 符号的废物方针,在 dump 时也保存了下来,默许不剖析此部分数据,如需求在发动脚本 ParseHeapDump.sh 中加入:-keep_unreachable_objects)。
  • 假如不重视堆中不可达方针,运用“live”参数能够减小文件巨细,指令是 jmap -dump:live,format=b,file=
  • Dump 前主动手动履行一次 FULL GC ,去除无效方针进一步削减 dump 堆转储及树立索引的时刻。
  • Dump文件巨大,树立索引后发现主视图中方针占用内存均较小,这是由于绝大部分方针未被 GC Roots 引证可开释。
  • Dump 时留意指定到空间较大的磁盘方位,避免打满分区影响服务。
  • 树立 dump 索引机器的磁盘空间需求满足大,一般至少是 dump 文件的两倍,由于生成的中间索引文件也较大,如下图:
    重学Android基础系列篇(四):Android虚拟机垃圾回收

3、其他

  • JDK 版别问题:如遇“VMVersionMismatchException”,运用发动方针进程的 JDK 版别即可。

  • 部分中心功用主界面未展示,问题满足杂乱时需翻开,如 MAT 默许不翻开 inspector,如需依据方针数据值做事务剖析,主张翻开该视图。

  • 装备了 HeapDumpOnOutOfMemoryError 参数,但 OutOfMemoryError 时但没有主动生成 dump 文件,或许原因有三个:

    • 运用程序自行创建并抛出 OutOfMemoryError
    • 进程的其他资源(如线程)已竭尽
    • C 代码(如 JVM 源码)中堆耗尽,这种或许由于不同的原因而出现,例如在交流空间缺乏的状况下,进程约束竭尽或仅地址空间的约束,此刻 dump 文件剖析并无实质性协助。