业精于勤荒于嬉,写文章练习表达能力,写代码练习基本工。
OOM和内存优化总结
什么是OOM?
OOM 即 (java.lang.OutOfMemoryError), JVM没有足够内存给对象分配空间,超过jvm的堆空间最大值(-Xmx参数),此异常就会被触发,导致应用强制被杀死。
OOM原因?
对于java程序员来说,我们一般只管创建对象,而对象的回收,我们很少操心,是因为JVM有垃圾回收器来定期执行GC,负责回收已经引用不到的无用对象来释放内存。不过,我们的应用还是出现内存泄露或者内存溢出,这也是导致oom的两大因素。
内存泄露和内存溢出?
- 内存泄露,是指之前分配的内存空间,不再使用,但也无法被垃圾回收器释放,这种情况如果一直堆积,会导致内存溢出。
- 内存溢出,没有足够的内存来分配空间了,空间不够了。
OOM发生在哪个区域?
我们先来认识一些jvm内存模型:
按照JVM的规范,除了程序计数器区,其他区域都可能出现OOM。
jvm 内存分配与回收机制
我们平时写java代码,绝大部分的OOM都发生在堆区,因此着重了解一下堆空间对象的内存模型和回收机制。
- 堆空间划分为,新生代(eden + S0 + S1), 老年代(Tenured)和永久代(Permanent),永久代java1.8已经取消,改为元空间。
- 新创建的对象基本都分配在新生代eden区,大对象直接分配到老年代。
- 一次GC后,eden区仍然存活的对象被移到S0;S0内存活的对象移到S1;S1内存活的对象被移到Tenured
- 每一次GC,对象年龄增长一岁,到达15岁(默认,可设置),晋升到老年代。
对象被回收的判断算法
- 可达性分析 :以 GC Root 为分析的起点 , 查找对象的引用 , 如果找到一个对象 , 无法被 GC Root 直接或间接引用到 , 那么该对象就可以被回收了 。
- 引用计数法 :给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1。
垃圾回收机制(GC)
垃圾回收算法:
- 标记-清除
- 标记-整理
- 复制
垃圾回收主要在新生代和老年代工作,在分代收集模型中(上图),不同的分代采用不同的收集算法:
- eden和Survivor:通常是复制算法
- Tenured:通常是标记整理算法,android是CMS
Android App 的内存限制
-
Java Heap :Java申请的内存,受 vm.heapsize 大小限制。
-
native Heap : c/c++层申请的内存,不受 vm.heapsize 大小限制。
如何修改:
代码位置 /frameworks/base/core/jni/AndroidRuntime.cpp
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool primary_zygote)
{
/*
* The default starting and maximum size of the heap. Larger
* values should be specified in a product property override.
*/
parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");
parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m");
parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
}
Android 抛出OOM的源头
堆分配失败
代码位置:/art/runtime/gc/heap.cc
void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) {
// If we're in a stack overflow, do not create a new exception. It would require running the
// constructor, which will of course still be in a stack overflow.
if (self->IsHandlingStackOverflow()) {
self->SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError());
return;
}
std::ostringstream oss;
size_t total_bytes_free = GetFreeMemory();
oss << "Failed to allocate a " << byte_count << " byte allocation with " << total_bytes_free
<< " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << " until OOM";
// If the allocation failed due to fragmentation, print out the largest continuous allocation.
if (total_bytes_free >= byte_count) {
space::AllocSpace* space = nullptr;
if (allocator_type == kAllocatorTypeNonMoving) {
space = non_moving_space_;
} else if (allocator_type == kAllocatorTypeRosAlloc ||
allocator_type == kAllocatorTypeDlMalloc) {
space = main_space_;
} else if (allocator_type == kAllocatorTypeBumpPointer ||
allocator_type == kAllocatorTypeTLAB) {
space = bump_pointer_space_;
} else if (allocator_type == kAllocatorTypeRegion ||
allocator_type == kAllocatorTypeRegionTLAB) {
space = region_space_;
}
if (space != nullptr) {
space->LogFragmentationAllocFailure(oss, byte_count);
}
}
self->ThrowOutOfMemoryError(oss.str().c_str());
}
常用的内存分析命令
adb shell dumpsys meminfo [package name]
adb shell procrank
内存占用排行榜,没试成功,需要自己上传sh。
adb shell cat /proc/meminfo
adb shell free
total = used + free
adb shell top
内存泄露分析工具
Memory Analyzer Tool
1,抓取内存申请快照hprof文件
adb shell am dumpheap com.xxx.xxx /data/local/tmp/14_54.hprof
2,转换为标准hprof
hprof-conv 14_54.hprof 14_54_R.hprof
hprof-conv在sdk的platform-tools目录中
3,使用mat打开
首页会有当前内存泄露的猜想,大对象,数量多的对象会被置为怀疑目标。
分析内存泄露
大对象分析
点击 Overview - Dominator ,对象按照占用空间大小排序
重点排查排在上面的大对象,下图中,很明显竟然有多个相同activity,且保持引用了大对象,无法释放,造成内存泄露。
GC Root 引用链查看
上图中,选择一项,右键 -> Paths to GC Roots–>exclude all phantom/weak/soft etc reference ,重点分析强引用。
可以看到context引用链,定位到自己的代码里面,本例是一个类静态持有了Activity的context,Activity销毁后,context无法释放造成内存泄露。
直方图比较
如果问题是内存随着时间缓慢增长,单个hprof看不出来,那么就隔一段时间再去一次快照,对两个快照文件进行比较,看那个对象一直在增长。
之前分析问题的hprof找不到了,临时找了两个,上面这个图片仅供步骤参考,具体以实际操作为准。
Android Profile
打开Profile页面,选择监控的程序,点击MEMORY–>dump按钮。
同样在android studio上可以查看hprof进行分析。
LeakCanary
引用
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}
调用
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
例子
public class MainActivity extends AppCompatActivity {
private static int k = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(runnable).start();
}
public Runnable runnable = new Runnable() {
@Override
public void run() {
while (true){
MainActivity.k++;
Log.d("TAG",""+k);
}
}
};
}
发生内存泄露,进入这个页面直接查看。
这个图片尺寸怎么缩小?哭
内存泄露,内存溢出常见场景和解决方案总结
以下摘抄自他人总结的,在这里做备份。
1、资源未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap
等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。
2、注册未注销
例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。
3、类的静态变量持有大数据对象
尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。
4、单例造成的内存泄漏
优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封
装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。
5、非静态内部类的静态实例
该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源
不能正常回收。如果需要使用Context,尽量使用Application Context。
6、Handler临时性内存泄漏
Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引
用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,
则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息,
当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message
持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回
收,引发内存泄漏。解决方案如下所示:
1、使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这
样在回收时,也可以回收Handler持有的对象。
2、在Activity的Destroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中
有待处理的消息需要处理。
需要注意的是,AsyncTask内部也是Handler机制,同样存在内存泄漏风险,但其一般是临时性的。对于
类似AsyncTask或是线程造成的内存泄漏,我们也可以将AsyncTask和Runnable类独立出来或者使用静
态内部类。
7、容器中的对象没清理造成的内存泄漏
在退出程序之前,将集合里的东西clear,然后置为null,再退出程序
8、WebView
WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为
WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业
务的需要选择合适的时机进行销毁,达到正常释放内存的目的。
9、使用ListView时造成的内存泄漏
在构造Adapter时,使用缓存的convertView。
如有错误,请帮忙指出。
参考
用户指南 - android app性能优化大汇总(内存性能优化)
【Android 内存优化】内存抖动 ( 垃圾回收算法总结 | 分代收集算法补充 | 内存抖动排查 | 内存抖动操作 | 集合选择 )