一、结论
1、Android中进程的优先级与垃圾回收机制相关,优先级越低被垃圾回收的机会越大。当内存不足的时候,总是低优先级的进程被最先回收;
2、Android中线程的优先级与调用顺序有关,优先级越高被调用的可能性越高(注意,是可能性更高),也就是说即使线程A的优先级大于线程B,同等情况下线程A不一定先于线程B被调用。
二、进程与线程
1、什么是进程、线程
如果你想要一个程序运行得快,那么可以将其断开为多个片段,在单独的处理器上运行每个片段,这是并发编程最主要的考虑,简单理解:
- 进程(process):是一块包含了某些资源的内存区域,是操作系统的最小可执行单元。操作系统利用进程把它的工作划分为一些功能单元。一个程序至少对应一个进程。
- 线程(thread):是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程至少对应一个线程。
在Android中,每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。大多数情况一个App就运行在一个进程中,除非:
(1)配置Android:process属性:
a.进程名为com.goodong.com.process1:
<activity
android:name=".MyFirstActivity"
android:process="com.goodong.com.process1"
>
<intent-filter>
……
</intent-filter>
</activity>
b.配置进程名为”:process2”(包名+process2):
<activity
android:name=".MyFirstActivity"
android:process=":process2"
>
<intent-filter>……
</intent-filter>
</activity>
注意:第2种命名进程的方式与第1中命名方式的区别在于,后者不能做到:让不同应用程序的组件运行在同一个进程中。
(2)通过native代码fork进程:
应用程序的进程是由Zygote创建的,在ActivityManagerService中的startProcessLocked中调用了Process.start()方法。并通过连接调用Zygote的native方法forkAndSpecialize,执行fork任务。
2、线程的调度
线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别。
Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去执行它的任务。
CPU轮流为每个任务分配其占用时间。每个任务都觉得自己在一直占用CPU,但事实上CPU时间是划分成片段分配给了所有任务。使用线程机制是一种建立透明的、可扩展的程序的方法,如果程序运行太慢,为机器添加一个CPU就能很容易地加快程序的运行速度。
请注意,尽管多任务和多线程往往是使用多处理器系统的最合理方式,但多线程编程并不是仅仅针对多处理器,即使在单处理上,并发编程也是有用武之地的。这是因为,如果一个程序包含了多个顺序执行的任务(不是并发执行),因为每个任务都可能被阻塞,一旦任务被阻塞,程序就停止执行。但在并发编程下,多个任务并发执行,单任务阻塞并不会直接导致程序停止执行。
三、Android进程的优先级
1、优先级意味着什么
优先级的意思是:针对多个对象的某种操作的执行顺序。上面我们说过,进程是一块内存区域,因为对进程而言,优先级意味着何时释放资源:
- 在释放进程资源的时候,让优先级低的进程先释放释放资源;
- 如果即将被运行的进程的优先级比正在运行的进程的优先级高,则系统可以强行剥夺正在运行的进程的资源,让优先级高的进程先运行。
2、有多少个优先级
在Android 中,进程的优先级就是oom_adj的值,而oom_adj被定义在init.rc中:
· Define the memory thresholds at which the above process classes will
· be killed. These numbers are in pages (4k).
setprop ro.FOREGROUND_APP_ADJ 0
setprop ro.VISIBLE_APP_ADJ 1
setprop ro.SECONDARY_SERVER_ADJ 2
setprop ro.HIDDEN_APP_MIN_ADJ 7
setprop ro.CONTENT_PROVIDER_ADJ 14
setprop ro.EMPTY_APP_ADJ 15
名称 oom_adj 解释:
3、oom_adj值会随着进程的状态变化而变化
adb连接手机后,笔者从桌面上启动了知乎,用adb shell dumpsys activity查看activitys的分布,可以看到activity的次序如下:
这时候另开一个命令行窗口查看进程的进程号:
这个时候知乎有两个进程,分别是15345和15725,使用cat /proc//oom_adj 命令查看它们的oom_adj值:
它们的值分别是0和11(oom_adj值是可以修改);
将知乎退到后台再查询它们的oom_adj值:
两个进程的值分别是9和13(退后台前是0和11)。
4、如何根据oom_adj的值判断回收时机
init.rc中定义垃圾回收的阈值:
· Write value must be consistent with the above properties.
· Note that the driver only supports 6 slots, so we have combined some of
· the classes into the same memory level; the associated processes of higher
· classes will still be killed first.
·写入的值必须符合上面的属性。注意设备只支持6个等级,所以某些
·类会被合并到同一个等级中。拥有更高等级的进程将被优先杀死。
setprop ro.FOREGROUND_APP_MEM 1536(6M)
setprop ro.VISIBLE_APP_MEM 2048(8M)
setprop ro.SECONDARY_SERVER_MEM 4096(16M)
setprop ro.HIDDEN_APP_MEM 5120(20M)
setprop ro.CONTENT_PROVIDER_MEM 5632(22M)
setprop ro.EMPTY_APP_MEM 6144(24M)
这些数字也就是对应的内存阈值,一旦低于该值,Android便开始按顺序关闭相应的进程 。具体的回收实现在ActivityManagerService.java中的函数trimApplications():
- 首先移除package被移走的无用进程;
基于进程当前状态,更新oom_adj值,然后进行以下操作:
- 移除没有activity在运行的进程。如果APP已经保存了所有的activity状态,结束这个APP;
- 最后,如果目前还是有很多activities 在运行,那么移除那些activity状态已经保存好的activity。
当系统内存短缺时Android的Low Memory Killer根据需要杀死进程释放其内存,简单说就是寻找一个最合适的进程杀死,从而释放它占用的内存,最合适指的是:
- oom_adj越大
- 占用物理内存越多
四、Android线程的优先级
1、线程的简单示范
从CPU的角度看,Android线程就是Java线程。因而Android线程的优先级就是Java线程的优先级(但在Android开发中,笔者似乎从未操心过线程的优先级)。
Java中使用线程最常用的方法是:
- 实现Runnable接口,覆写run()方法;
继承Thread类,覆写run()方法(实际也是实现Runnable接口)。
先实现实现Runnable接口:public class RunabbleImp implements Runnable{ @Override public void run() { doSomeThing(); } private void doSomeThing() { System.out.println(Thread.currentThread().getName()+" is running!"); } }
测试一下:
public class ThreadTest {
public static void main(String[] args) {
RunabbleImp runabbleImp = new RunabbleImp();
runabbleImp.run();
Thread thread = new Thread(new RunabbleImp());
thread.start();
}
}
打印结果:
main is running!
Thread-0 is running!
可以看出runabbleImp.run();实际上是在主线程中运行,而thread.start();已经开启了一个新线程。
2、线程的优先级
线程的优先级将线程的重要性传递给调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先级高的线程先执行。然而,这并不意味着优先权低的线程将得不到执行(也就是说,优先级不会导致死锁)。优先级较低的线程仅仅是执行的频率较低。
可以用getPriority()获取当前线程的优先级,并且在任何时刻都可以通过setPriority()来修改它:
public class PriorityThread implements Runnable {
private int timeCount = 5;
private int priority;
public PriorityThread(int priorityIn) {
priority = priorityIn;
}
@Override
public String toString() {
return Thread.currentThread() +":"+timeCount;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority);
while(true){
for (int i = 0; i < 100000; i++) {
double d = (Math.PI + Math.E)/(double)i;
if(i % 1000 == 0){
Thread.yield();
}
}
System.out.println(this);
if(--timeCount ==0){
break;
}
}
}
public class ThreadTest {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new PriorityThread(Thread.MIN_PRIORITY + i));
}
}
exec.execute(new PriorityThread(Thread.MAX_PRIORITY));
exec.shutdown();
}
部分打印结果如下:
……
Thread[pool-1-thread-6,10,main]:1
……
Thread[pool-1-thread-5,5,main]:1
……
Thread[pool-1-thread-3,3,main]:1
Thread[pool-1-thread-1,1,main]:1
……
Thread[pool-1-thread-2,2,main]:1
Thread[pool-1-thread-4,4,main]:1
笔者执行多次后结果显示Thread[pool-1-thread-6,10,main]总是最先执行完毕,其他线程执行完毕顺序大致上是优先级高的先执行完毕,但是无法保证。