一、App内存组成以及管理

Android 给每个 App 分配一个 VM ,让App运行在 dalvik 上,这样即使 App 崩溃也不会影响到系统。系统给 VM 分配了一定的内存大小, App 可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余,如果应用超出 VM 最大内存,就会出现内存溢出 crash,即OOM。 

由程序控制操作的内存空间在 heap 上,分 java heapsize 和 native heapsize 。 

1)Java申请的内存在 vm heap 上,所以如果 java 申请的内存大小超过 VM 的逻辑内存限制,就会出现内存溢出的异常。

2)native层内存申请不受其限制, native 层受 native process 对内存大小的限制。 

二、Android设备对App的内存限制 

如何查看APP的内存大小限制?

1. 主要查看系统配置文件 build.prop ,我们可以通过 adb shell 在 命令行窗口查看 
adb shell cat /system/build.prop

android code占内存_Android

2. 通过代码获取
ActivityManager activityManager =
(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
activityManager.getMemoryClass();//以m为单位
3.修改内存限制

修改 \frameworks\base\core\jni\AndroidRuntime.cpp 

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool 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");//修改这里
}

修改 platform/dalvik/+/eclair-release/vm/Init.c

gDvm.heapSizeStart = 2 * 1024 * 1024; // Spec says 16MB; too big for us.
gDvm.heapSizeMax = 16 * 1024 * 1024; // Spec says 75% physical mem

三、Android内存分配与回收机制

1、内存分配 

Android的Heap空间是一个 Generational Heap Memory 的模型,最近分配的对象会存放在 Young
Generation 区域,当一个对象在这个区域停留的时间达到一定程度,它会被移动到 Old Generation ,最后累积一定时间再移动到 Permanent Generation 区域。

1)Young Generation

由一个Eden区和两个Survivor区组成,程序中生成的大部分新的对象都在Eden区中,当Eden区满时,还存活的对象将被复制到其中一个Survivor区,当次Survivor区满时,此区存活的对象又被复制到另一个Survivor区,当这个Survivor区也满时,会将其中存活的对象复制到年老代。

2)Old Generation

一般情况下,年老代中的对象生命周期都比较长。

3)Permanent Generation

用于存放静态的类和方法,持久代对垃圾回收没有显著影响。

总结:内存对象的处理过程如下:

1. 对象创建后在Eden区。
2. 执行GC后,如果对象仍然存活,则复制到S0区。
3. 当S0区满时,该区域存活对象将复制到S1区,然后S0清空,接下来S0和S1角色互换。
4. 当第3步达到一定次数(系统版本不同会有差异)后,存活对象将被复制到Old Generation。
5. 当这个对象在Young Generation区域停留的时间达到一定程度时,它会被移动到Old
Generation,最后累积一定时间再移动到Permanent Generation区域。

2、Generation GC

系统在Young Generation、Old Generation上采用不同的回收机制。每一个Generation的内存区域都有固定的大小。随着新的对象陆续被分配到此区域,当对象总的大小临近这一级别内存区域的阈值时,会触发GC操作,以便腾出空间来存放其他新的对象。

1)执行GC占用的时间与Generation和Generation中的对象数量有关:

1. Young Generation < Old Generation < Permanent Generation
2. Gener中的对象数量与执行时间成正比。

2)Young Generation GC

由于其对象存活时间短,因此基于Copying算法(扫描出存活的对象,并复制到一块新的完全未使用的控件中)来回收。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在YoungGeneration区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。

3)Old Generation GC

由于其对象存活时间较长,比较稳定,因此采用Mark(标记)算法(扫描出存活的对象,然后再回收未被标记的对象,回收后对空出的空间要么合并,要么标记出来便于下次分配,以减少内存碎片带来的效率损耗)来回收。 

3、Java内存分配模型 

android code占内存_Android_02

4、可达性分析与GCRoots 

通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。 

android code占内存_android code占内存_03

GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。 

android code占内存_性能优化_04

5、GC 回收算法
1)标记清除算法:

1 位置不连续,产生碎片;
2 效率略低;
3 扫描两遍。

2)复制算法:

1 实现简单、运行高效;
2 没有内存碎片;
3 利用率只有一半 。

3)标记整理算法:

1 没有内存碎片;
2 效率偏低;
3 扫描两遍、指针需要调整 。

四、内存三大问题 

1、内存抖动

每创建一个对象就会分配一个内存,可用内存就少用了一块,当程序占用的内存达到一定的临界值
就会出发GC 内存频繁分配和回收导致内存不稳定瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。触发GC时会停止用户线程,导致APP卡顿,甚至有可能出现OOM。通过Memory Profiler检测,内存波动图形呈锯齿张。常出现于:循环里创建对象,自定义onDraw创建对象等。

2、内存泄漏

在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小;Android内存泄漏分析工具:Memory Profiler和LeakCanary(查看文章,LeakCanary内存泄漏检测框架分析:);

Android内存泄漏常见场景以及解决方案:

1. 资源性对象未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如文件、数据库和Bitmap等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。

2. 注册对象未注销
例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。

3 .类的静态变量持有大数据对象
尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。

4 .单例造成的内存泄漏
优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。

5. 非静态内部类的静态实例
该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置空让GC可以回收,否则还是会内存泄漏。

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。

3、内存溢出

即OOM,OOM时会导致程序异常。Android设备出厂以后,java虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会OOM。

1)OOM原因分类

android code占内存_android code占内存_05

2)OOM代码分析

Android 虚拟机最终抛出OutOfMemoryError的地方:/art/runtime/thread.cc

void Thread::ThrowOutOfMemoryError(const char* msg) {
	LOG(WARNING) << StringPrintf("Throwing OutOfMemoryError \"%s\"%s",
	msg, (tls32_.throwing_OutOfMemoryError ? " (recursive case)" : ""));
	if (!tls32_.throwing_OutOfMemoryError) {
		tls32_.throwing_OutOfMemoryError = true;
		ThrowNewException("Ljava/lang/OutOfMemoryError;", msg);
		tls32_.throwing_OutOfMemoryError = false;
	} else {
		Dump(LOG_STREAM(WARNING)); // The pre-allocated OOME has no stack, so
		help out and log one.
		SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError());
	}
}

堆内存分配失败:/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()-
		>GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
		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,"
	<< " target footprint " <<
	target_footprint_.load(std::memory_order_relaxed)
	<< ", growth limit "
	<< growth_limit_;
	//没有足够大小的连续地址空间
	// There is no fragmentation info to log for large-object space.
	if (allocator_type != kAllocatorTypeLOS) {
		CHECK(space != nullptr) << "allocator_type:" << allocator_type
		<< " byte_count:" << byte_count
		<< " total_bytes_free:" << total_bytes_free;
		space->LogFragmentationAllocFailure(oss, byte_count);
	}
}

创建线程失败:/art/runtime/thread.cc

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t
stack_size, bool is_daemon) {
	CHECK(java_peer != nullptr);
	Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf();
	// TODO: remove from thread group?
	env->SetLongField(java_peer,
	WellKnownClasses::java_lang_Thread_nativePeer, 0);
	{
		std::string msg(child_jni_env_ext.get() == nullptr ?
		StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
		StringPrintf("pthread_create (%s stack) failed: %s",
		PrettySize(stack_size).c_str(),
		strerror(pthread_create_result)));
		ScopedObjectAccess soa(env);
		soa.Self()->ThrowOutOfMemoryError(msg.c_str());
	}
}

OOM问题比较常出现在:Bitmap没有进行任何压缩处理,直接加载原图到APP上面,而导致OOM;长时间的内存泄漏也是导致OOM问题的原因之一。