一、OOM
out of memory:简称OOM,内存溢出,申请的内存大于剩余的内存而抛出的异常。
对于Android平台,广义的OOM主要是以下几种类型
- Java
- Native
- Thread
线程数的上限默认为32768,部分华为设备的限制是500通常1000左右就会触发VSS OOM。因此Thread 00M其实也是VSS OOM的一种表现形式。
引起线程OOM原因:1.文件描述符不够 2.地址空间不足
- File Descriptor
Android 9.0以前fd也是比较宝贵的资源,每个进程的上限只有1024,9.0开始增加到了32768。
- JNI Reference
JNl Reference 0OM可以通过logcat初步定位。
常用工具:
1、LeakCanary
val refWatcher: RefWatcher? = TestApp.getRefWatcher(activity)
refWatcher?.watch(activity);//检测是否有泄露,即触发GC回收,看activity是否被回收,没有被回收就是泄露了。
二、常见的几种内存泄漏
1、长生命周期引用短生命周期2、资源没有释放
单例造成的内存泄露
非静态内部类创建静态实例造成的内存泄露
Handler造成的内存泄露
线程造成的内存泄露
Webview造成的内存泄露
三、OOM的原因,以及如何定位OOM
OOM产生的原因:
1、一次性申请的太多
更改申请对象数量
2.内存资源耗尽未释放
找到未释放的对象进行释放
3.本身资源不够
jmap -heap 查看堆信息
如何优化内存
1、减小对象的内存占用
2、内存对象的复用
3、避免对象的内存泄露
如何定位OOM
如何通过dump定位
系统已经OOM挂了
提前设置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=
系统运行中还未OOM
导出dump文件:jmap -dump:format=b,file=xushu.hprof 14660Arthas
结合ivisualvm 进行调试
查看最多跟业务有关对象->找到GCRoot ->查看线程栈
四、GC回收原理
GCRoot------对象可达性分析
可回收对象的判定
通过一系列称为"GC Roots"的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
五、Java内存分配模型
GCRoot是:方法区Method Area、虚拟机栈VM Stack(即本地变量)、本地方法栈Native Method(即静态变量和常量) Stack中的变量。如GCRoot中变量有对堆中对象的引用,gc则不回收堆中的该对象,如堆中对象无来自GCRoot的引用,则回收该对象。例如,B b = new B();对象b为引用存于虚拟机栈VM Stack中,new出的B对象存于堆中。
六、Android profiler------dump内存快照分析
双击打开设备Downloads文件夹下hprof文件,文件信息在profiler中被显示。
shallow size浅堆和Retain size深堆的区别
shallow size(浅堆)是指对象本身占用内存的情况
Retain size(深堆)是指对象以及对象关联其他引用占用内存的总和。即,对象本身被GC回收后,导致其他对象同时也被GC回收,这些被回收的所有对象占用内存总和。
如下举例:
1、对象A,B,C内存占用情况如下,A的shallow size是10M,Retain size是30M
1、对象A,B,C,D内存占用情况如下
A的shallow size是10M,Retain size是20M
B的shallow size是10M,Retain size是10M
C的shallow size是10M,Retain size是10M
D的shallow size是10M,Retain size是10M
七、Java的四种引用
1.强引用 (Strong Reference)在代码中普遍使用的,类似Person person=new Person();如果一个对象具有强引用,则无论在什么情况下,GC都不会回收被引用的对象。当内存空间不足时,JAVA虚拟机宁可抛出outOfMemoryError终止应用程序也不会回收具有强引用的对象。
2.软引用(Soft Reference)表示一个对象处在有用但非必须的状态。如果一个对象具有软引用,在内存空间充足时,GC就不会回收该对象;当内存空间不足时,GC会回收该对象的内存(回收发生在0utofMemoryError之前)。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被GC回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中,以便在恰当的时候将该软引用回收。但是由于GC线程的优先级较低,通常手动调用system.gc()并不能立即执行GC,因此弱引用所引用的对象并不一定会被马上回收。
3.弱引用(Weak Reference)用来描述非必须的对象。它类似软引用,但是强度比软引用更弱一些:弱引用具有更短的生命,GC在扫描的过程中,一旦发现只具有被弱引用关联的对象,都会回收掉被弱引用关联的对象。换言之,无论当前内存是否紧缺,GC都将回收被弱引用关联的对象。
4.虚引用(Phantom Reference)虚引等同于没有引用,这意味着在任何时候都可能被GC回收,设置虚引用的目的是为了被虚引用关联的对象在被垃圾回收器回收时,能够收到一个系统通知。(被用来跟踪对象被GC回收的活动)虚引用和弱引用的区别在于:虚引用在使用时必须和引用队列(ReferenceQueue)联合使用,其在GC回收期间的活动如下:ReferenceQueue queue=new ReferenceQueue();PhantomReference pr=new PhantomReference(obiect.queue)也即是GC在回收一个对象时,如果发现该对象具有虚引用,那么在回收之前会首先该对象的虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入虚引用来了解被引用的对象是否被GC回收。
软引用和弱引用的区别如下:
软引用:gc扫到,不一定回收,内存不足时才回收
弱引用:gc扫到,就回收。
弱引用和引用队列的关系如下:
泄露了:弱引用没有加入引用队列,故引用队列为空
没有泄露:弱引用加入引用队列,故引用队列不为空
八、Java新生代,老年代和永久代的区别
在Java虚拟机(JVM)的内存管理中,堆内存通常被划分为几个不同的区域,以便更有效地管理和回收内存。以下是新生代(Young Generation)、老年代(Old Generation)和永久代/元空间(PermGen/Metaspace)的具体含义:
- 新生代 (Young Generation)
-
新生代是Java堆内存的一部分,主要用于存储新创建的对象。对象在首次分配内存时会被放入新生代。
-
新生代的特点是大量对象在此快速生成并很快消亡,因此这里的垃圾回收(Garbage Collection, GC)非常频繁,通常称为Minor GC或Young GC。
-
新生代进一步细分为 Eden 区、两个 Survivor 区(例如:From 和 To 区),每次GC后,存活下来的对象会被复制到另一个Survivor区,若对象经历了一定次数的GC还存活,则晋升至老年代。
- 老年代 (Old Generation)
-
老年代也是Java堆内存的一部分,主要存放经过一定时间周期仍然存活下来的对象,即经历过多次新生代GC仍然没有被回收的对象。
-
对象一旦晋升到老年代,意味着它们生命周期较长,老年代的垃圾回收相对较少,但是一旦发生,通常会是Major GC或Full GC,这类GC会比Minor GC更加耗时且影响更大。
- 永久代 (PermGen)
-
在JDK 8及之前版本的HotSpot JVM中,永久代是方法区的一个实现,用于存储类信息、常量池、静态变量、方法字节码以及其他运行时常驻数据结构。
-
永久代的空间有限,如果加载了大量的类或者反射操作过于频繁,可能导致永久代空间不足,从而抛出`java.lang.OutOfMemoryError: PermGen space`异常。
- 元空间 (Metaspace)
:
-
自JDK 8开始,HotSpot JVM取消了永久代的概念,并引入了一个新的内存区域叫做元空间(Metaspace)。
-
元空间同样用于存储类元数据信息,但它位于本地内存(Native Memory)而非堆内存中,这意味着它的大小不再受到JVM堆大小的限制,而受限于系统的实际可用内存。
-
当类元数据的总量超过了指定阈值或者系统内存限制时,将会触发类卸载机制,并可能出现`java.lang.OutOfMemoryError: Metaspace`异常。
总结起来,新生代和老年代是用来区分对象生命周期长短进而进行高效内存回收的堆内存区域,而永久代在旧版JVM中是方法区的一种实现方式,存储类和方法级别的元数据;在新版JDK中,这部分功能由元空间替代。
九、JVM出现OOM异常会导致进程挂掉吗?
当一个线程在执行代码的过程中,大概率需要创建对象,而创建对象就需要分配内存,如果VM可用内存不够时会进行垃圾回收,如果垃圾回收完了之后内存还是不够就会地出OutOfMemoryEror,如果没有捕获OutOfMemoryError,那么就像抛出一个普通异常一样会导致线程停掉,如果捕获了OutOfMemoryEror,那么线程可能就不会亭掉,其实不管当前线程会不会停掉,跟进程会不会挂掉没有直接关系,也就是出现,了OutOfMemgrvEror最多只会导致线程停掉,如果一个讲程里面的所有非守护线程都亭掉了,那么进程才会停掉,或者进程占了操作系统的过多内存,那么这个进程可能会被操作系统关闭掉。
参考:https://blog.csdn.net/pengweid/article/details/137125832
参考:彻底解决Android开发中的OOM问题,竟然一节课就够了_哔哩哔哩_bilibili
参考:美团面试题:一个线程 OOM 后,其他线程还能运行吗?_哔哩哔哩_bilibili