堆分区
堆大小 = 新生代 + 老年代。默认下,新生代 ( Young ) = 1/3 的堆空间大小,老年代 ( Old ) = 2/3 的堆空间大小;
新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认的,Edem : from : to = 8 : 1 : 1;
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间;
GC 分为两种:老生代中采用标记-清除算法的Full GC ( 或称为 Major GC )和新生代中采用复制算法的Minor GC。新生代是 GC 收集垃圾的频繁区域;
持久代位于方法区,且仅存在于Hotspot虚拟机中,sun公司计划移除。
**所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区。数据会首先分配到Eden区 当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。),当Eden没有足够空间的时候就会 触发jvm发起一次Minor GC。如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空 间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代 中了,当然晋升老年代的年龄是可以设置的。
其实新生代和老年代就是针对于对象做分区存储,更便于回收等等**
新生代主要存放的是那些很快就会被GC回收掉的或者不是特别大的对象(这个大就要看你是否设置了-XX:PretenureSizeThreshold 参数了)。新生代采用的复制算法,即将新生代分为3个区:较大的Eden和两个较小的Survivor(默认的Eden:Survivor = 8:1)。发生在新生代的GC为Minor GC 。在Minor GC时会将新生代中还存活着的对象复制进一个Survivor中,然后对Eden和另一个Survivor进行清理。所以,平常可用的新生代大小为Eden的大小+一个Survivor的大小年代则是存放那些在程序中经历了好几次回收仍然还活着或者特别大的对象(这个大就要看你是否设置了-XX:PretenureSizeThreshold 参数了)。老年代采用的是标记-清除或者标记-整理算法,这两个算法主要看虚拟机采用的哪个收集器,两种算法的区别是:标记-清除可能会产生大量连续的内存碎片。在老年代中的GC则为Major GC。Major GC和Full GC会造成stop-the-world。-Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256mXms,即为jvm启动时得JVM初始堆大小,Xmx为jvm的最大堆大小,xmn为新生代的大小,permsize为永久代的初始大小,MaxPermSize为永久代的最大空间。老年代和新生代也是和内存相关,虚拟机初始化时已经设定了使用的内存大小,并划分为三部分:新生代– 新创建的对象,旧生代 – 经过多次垃圾回收没有被回收的对象或者大对象持久代– JVM使用的内存,包含类信息等所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区。数据会首先分配到Eden区 当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。),当Eden没有足够空间的时候就会 触发jvm发起一次Minor GC。如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空 间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代 中了,当然晋升老年代的年龄是可以设置的。其实新生代和老年代就是针对于对象做分区存储,更便于回收等等
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代;
Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长;
因为 jvm 每次只是用新生代中的 eden 和 一个 survivor,因此新生代实际的可用内存空间大小为所指定的 90%;
PermGen 即永久代 ( 方法区 ),它还有一个名字,叫非堆,主要用来存储由 jvm 加载的类文件信息、常量、静态变量等;
每次调 System.gc(),是先进行 Minor GC,然后再进行 Full GC;
当 Full GC 进行的时候,默认的方式是尽量清空新生代 ( YoungGen ),因此在调 System.gc() 时,新生代 ( YoungGen ) 中存活的对象会提前进入老年代;
题眼:JVM使用的是分代垃圾回收的方式,可以将Java对象分为"年轻"对象和"年老"对象.JVM将内存堆(Heap)分为两个区域,一个是"年轻"区,另一个是"老"区,Java将这两个区域分别称作是"新生代"和"老生代"。
详细:JVM使用的是分代垃圾回收的方式,主要是因为在程序运行的时候会有如下特点:
◆大多数对象在创建后很快就没有对象使用它了。
◆大多数在一直被使用的对象很少再去引用新创建的对象。因此就将Java对象分为"年轻"对象和"年老"对象,JVM将内存堆(Heap)分为两个区域,一个放年轻对象,一个放年老对象,java对对内存的这两个区域分别成为 新生代,老生代。
新生代区域特点: a) 绝大多数新创建的对象都存放在这个区域b) 此区域一般来说较小而且JVM垃圾回收频率较高c) 采用的算法和存放对象特点使得该区域JVM垃圾回收的效率也非常高
老生代区域特点: a) 将在"新生代"中生存了较长时间的对象转移过来b) 区域一般要大一些而且增长的速度相对于"新生代"要慢一些c) 垃圾回收的执行频率也会低很多
了解这个对于我们的好处是:JVM在JVM垃圾回收处理时会消耗一定的系统资源,如果我们在JVM启动的时候添加相关参数来控制"新生代"区域的大小以达到调整JVM垃圾回收处理的频率,那么我们就会合理利用系统资源。
补充:
1 通过设置-xx:PretenureSizeThreshold 来设置多大的对象直接进入老年代。你把这个值调的大一点,则可以保证大部分对象不会直接进入老年代,老年代对象的gc同长慢,一般内存不满时不会gc的,所以你的大对象一直都在不会被回收。2 (-xx:MaxTenuringThreshold默认15)这个值也可以调调,这个表示在新生代折腾多少次后进入年老代。
安卓系统机制
系统启动过程
加载bootloader
-
Bootloader加载Linux内核
Bootloader作用是:类似于BIOS,初始化基本硬件设备,建立内存空间映射、为加载linux内核准备好运行环境。Linux内核初始化完成后,装载完文件系统,就启动了第一个init进程
-
Linux初始化后启动第一个进程init
init进程解析linux的脚本文件init.rc,根据这个文件内容init进程会装载Android文件系统,创建系统目录,启动Android重要守护进程等。
init进程还会分裂出更多的名为damens的守护进程,如usb deamon等处理底层硬件相关
-
init进程初始化结束后会启动Zygote进程。
- Zygote进程
Zygote进程fork出应用进程,是所有进程的父进程。
Zygote进程初始化的时候回创建Dalvik虚拟机,预装载系统资源文件和java类,所有从Zygote fork出的进程继承和共享这些资源不用浪费时间重新加载。
Zygote初始化完后,也将变成守护进程,负责响应启动apk应用的启动请求。
-
Zygote进程fork第一个进程SystemServer进程
SystemServer进程是Zygote fork出的第一个进程,也是整个Android系统的核心进程。在SystemServer主要运行的是Binder服务。
SystemServer首先启动本地服务SensorService接着启动ActivityManagerService、WindowsManagerService、PackageManagerService等所有java服务。
-
SystemService加载完所有java服务后调用AMS的SystemReady()方法
其实是为了打开Launcher。还要去PMS取一下所有类型为
ACTION_MAIN
的intent决定打开的Launcher。
App启动流程
-
点击app图标后,click事件会调用startActivity(intent)会通过Binder IPC机制最终调用到AMS。该服务会执行以下操作:
通过PackageManager的resolveIntent()获得该intent对象的指向信息
通过PMS获得用户是否有权限调用该intent指向的Activity
-
如果有权限,AMS会检查并在新的task中启动目标activity
检查该进程的ProcessRecord是否存在,如果ProcessRecord为null,AMS会创建新的进程并实例化目标activity
-
创建进程
AMS调用startProcessLocked()创建新的进程,该方法会通过Socket通道传参给Zygote进程,Zygote孵化自己并调用ZYgoteinit.main()方法来实例化ActivityThread对象并最终返回新进程的PID。
ActivityThread随后依次调用Looper.prepare() 和Looper.loop()开启消息循环
-
绑定进程到Application
通过ActivityThread调用bindApplication()完成。
该方法发送一个BIND_APPLICATION消息到消息队列,最终通过handleBindApplication()处理该消息,最终调用makeApplication()方法来加载App的classes到内存。
-
启动Activity
到这里,系统已经拥有该Application的进程。后面调用的顺序就是普通的从一个已经存在的进程启动一个新进程的Activity
实际步骤是:执行realStartActivity(),他会调用application线程的sheduleLauncherActivity()发生一个LAUNCHER_ACTIVITY消息到消息队列中,通过handleLauncherActivity()处理该消息。
安装软件过程
apk文件就是一个资源和组件的压缩包,安装的过程就是复制和解析的过程。
-
复制
安卓机有内部存储和SD卡两部分,很多安卓机的内存并不大,需要把apk安装到SD卡上节省内存空间,所以程序目录/data/app/实际上也是在内部存储和SD卡上各一个。
系统自带的App是安装在/system/app/目录下的,这个目录只有root权限才能访问,所以系统App在root之前是无法删除和修改的,也就是说,系统App升级时,实际上是在/data/app/里重新安装了一个App,这个路径会重新注册到系统那里,系统再打开App时,就会指向新App的地址。当然,这个新的App是可以卸载的,不过新的App卸载后,系统会把 /system/app/里那个旧的App提供给你,所以是卸掉新的,还你旧的。
还是系统App,在root后,我们可以操作/system/app/目录,但是系统安装Apk仍然会装到/data/app/里,所以如果想修改/system/app/目录里的app,必须自己手动push新的apk文件进去,这个新的apk文件不会自动被安装,需要重启设备,系统在重启时检查到apk被更新,才会去安装apk。
系统目录有个/system/priv-app/目录,这里面放的是权限更高的系统核心应用,如开机launcher、系统UI、系统设置等,这个目录我们最好不要动,保持系统干净简洁。
-
安装
具体的apk安装过程,就是由这个PMS操作的。PMS会监控/data/app/这个目录,在上一步中,系统安装程序向这个目录复制了一个apk,PMS自己就会定期扫描这个目录,找到后缀为apk的文件,如果这个apk没有被安装过,它就会自动开始安装,安装时会做这么几件事:
创建应用目录,路径为/data/data/your package(你的应用包名),App中使用的数据库、so库、xml文件等,都会放在这个目录下。
提取dex文件,dex是App的可执行文件,系统解压apk就能得到dex文件,然后把dex文件放到/data/dalvik-cache,这样可以提前缓存dex到内存中,能加快启动速度。系统还会把dex优化为odex,进一步加快启动速度。
判断是否可以安装apk,如检查apk签名等。
为应用分配并保存一个UID,UID是来自Linux的用户账户体系,不过在Android这种单用户系统里,UID被用来与App对应,这也是安全机制的一部分,每个App都有自己对应的UID,这种对应关系是持久化保存的,App更新或卸载重装后,系统还会给它分配原来那个UID。用adb pull /data/system/packages.list可以查看所有App的UID。GID(用户组)一般等于UID。
利用AndroidManifest文件,注册Apk的各项信息,包括但不限于:****. 根据installLocation属性(internalOnly、auto、preferExternal),选择安装在内部存储器还是SD卡上。. 根据sharedUserId属性,为App分配UID,如果两个App使用同一个UID,打包时又使用了相同的签名,它们就被视为同一个用户,可以共享数据,甚至运行在同一个进程上。. 向/data/system/packages.xml文件中,记录App的包名、权限、版本号、安装路径等;同时在/data/system/packages.list中,更新所有已安装的app列表。. 注册App中的的四大组件(Activity、Service、Broadcast Receiver和Content Provider),包括组件的intent-filter和permission等。. 在桌面上添加App的快捷方式,如果AndroidManifest文件中有多个Activity被标注为<action android:name="android.intent.action.MAIN" />和<category android:name="android.intent.category.LAUNCHER" />,系统就会向桌面添加多个App快捷方式,所以有时候在安装一个App后,用户可能会感觉安装了多个App。
-
通知
apk安装完成后,PMS会发一个ACTION_PACKAGE_ADDED广播,如果是卸载,会发ACTION_PACKAGE_REMOVED广播。
安卓进程机制
前台进程
可见进程
服务进程
即Service服务
- 后台进程
即当前App按Home键后成为后台进程
Binder IPC机制
Android 从下而上分了内核层、硬件抽象层、系统服务层、Binder IPC 层、应用程序框架层
Android 中「应用程序框架层」以 SDK 的形式开放给开发者使用,「系统服务层」中的核心服务随系统启动而运行,通过应用层序框架层提供的 Manager 实时为应用程序提供服务调用。系统服务层中每一个服务运行在自己独立的进程空间中,应用程序框架层中的 Manager 通过 Binder IPC 的方式调用系统服务层中的服务。
小结
Binder IPC 属于 C/S 架构,包括 Client、Driver、Server 三个部分
Client 可以手动调用 Driver 的 transact 接口,也可以通过 AIDL 生成的 Proxy 调用
Server 中会启动一个「线程池」来处理 Client 的调用请求,处理完成后将结果返回给 Driver,Driver 再返回给 Client
AIDL
一个继承了 Binder,一个继承了 AIDL 自动生成的 Stub 对象
AIDL 自动生成了 Stub 类
在 Service 端继承 Stub 类,Stub 类中实现了 onTransact 方法实现了「解包」的功能
在 Client 端使用 Stub 类的 Proxy 对象,该对象实现了「组包」并且调用 transact 的功能
Activity与Service几种通信
1.Binder2.intent3.广播
如何保证服务常驻
-
黑色保活
利用不同进程之间广播唤起
保证APP不杀死方法
-
白色保活
- 显示通知
-
灰色保活
启动一个不显示通知的前台服务
启动前台服务:只需要在服务启动的时候,调用startForeground()方法,在其中传入一个待显示的Notification即可。停止前台服务,需要调用stopForeground()方法。
去掉前台服务的通知,行之有效的方法是:
- 需要两个前台服务,共享同一个Notification ID。
- 一个服务启动完毕之后,马上停止自己,会去掉通知栏的通知。
- 而之前已经借助这个ID保持前台的服务,依然会处于前台的状态不变。