在Android系统中,每个线程都至少会申请1MB的虚拟空间来作为栈空间。笔者先带着大家了解线程的创建流程,以对这一点进行证实。
当我们使用线程执行任务时,通常会先调用new Thread(Runnable runnable)来创建一个Thread.java对象的实例,Thread的构造函数会将stackSize这个变量设置为0,这个stackSize变量决定了线程栈的大小,接着我们便会使用Thread实例提供的start方法运行这个线程,start方法会调用nativeCreate这个Native函数在系统层创建一个线程并运行,代码流程如下。
通过上面start函数的源码可以看到,nativeCreate会传入stackSize,但是它默认的值为0,那为什么线程还会默认有1MB大小的栈空间呢?我们需要接着看nativeCreate函数的源码,它的实现类是java_lang_Thread.cc,源码如下。
nativeCreate会执行Thread::CreateNativeThread函数,这个函数才是最终创建线程的地方,它在Thread.cc这个对象中,并且这个函数会调用FixStackSize方法将stack_size调整为1MB,所以前面那个疑问在这里就解决了,即使我们将stack_size设置为0,到这里依然会被调整,简化后的代码逻辑如下。
在上面简化后的代码中可以看到,CreateNativeThread的源码最终调用的是pthread_create函数,它是一个Linux函数,而pthread_create函数最终会调用clone这个内核函数,该函数的主要作用是创建一个新进程,该进程与调用进程共享指定的资源。clone函数会根据传入的stack大小,通过mmap函数申请一块对应大小的虚拟内存,并且创建一个进程。所以对于Linux系统来说,线程实际上是可以共享资源的轻量级进程。
理解了一个线程会占用1MB大小的虚拟内存,我们自然而然地也能想到减少线程的数量和减少每个线程所占用的虚拟内存的大小这两种优化方案了。