赞
踩
1、概述:虚拟机把描述类的数据从Class文件加载到内存,并对数据 进行校验、转换解析和初始化,最终形成可以被虚拟直接使用的java类型,这就是虚拟机的类加载机制。
2、类加载的过程:加 载、验 证、准 备、解 析、初始化、使 用、卸载
3、关于初始化:
5种情况会触发类的初始化
1)遇到new,getstatic,putstatic,invokesstatic这四个字节码指令
时,如果类没有被初始化
2)使用java.lang.reflect包的方法对类进行反射时,如果类没
有被初始化,则先触发其初始化
3)当初始化一个类时,其父类没有被初始化,则需要父类先初始化
4)虚拟机启动时,用户需要制定一个执行的主类,虚拟机会先初始化这个类
5)JDK 1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方 法句柄,并且这个方法句柄所对应的类没有被初始化
通过一个类的全限名来获取定义此类的二进制字节流,将字节流所代表的静态存储结构转化为方法区的运行时
数据结构在内存中生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据的访问入口
JVM:是Java Virtual Machine的缩写,其并不是指某个特定的虚拟机实现,而指任何能够运行Java字节码(class文件)的虚拟机实现,比如oracle的HotspotVM
Dalvik:是Google写的一个用于Android的虚拟机,但其严格来说并不算JVM(没有遵守Java虚拟机规范,比
如其字节码格式是dex而非class)该虚拟机在5.0时被ART替代
ART:是Android Runtime的缩写,严格来说并不是一个虚拟机,在4.4~6.0时采用安装时全部编译为机器码的 方式实
现,7.0时默认不全部编译,采用解释执行+JIT+空闲时AOT以改善安装耗时ART在安卓4.4时加入,5.0取代dalvik作为唯一实现直到现在
内存分为栈(stack)和堆(heap)两部分:
栈记录了方法调用,每个线程拥有一个栈,栈的每一帧中保存有该方法调用的参数、局部变量、返回地址栈中被调用方法运行结束时,相应的帧会删除,参数和局部变量占用的空间也会释放,堆是用来存储对象的,堆区由所有线程共享
垃圾回收机制有:引用计数算法、可达性分析算法、标记清除算法、复制算法、标记整理算法、分代回收算法。
就是Java内存模型。存在的问题就是多线程对共享数据的读写一致性问题,可以通过synchronized、cas、并发安全的数据结构、aqs 组件来解决
- 线程是CPU调度的最小单元,同时线程是一种有限的系统资源;
- 进程一般指一个执行单元,在PC和移动设备上一个程序或则一个应用,
- 一般来说,一个App程序至少有一个进程,一个进程至少有一个线程(包含与被包含的关系), 通俗来讲就是,在App这个工厂里面有一个进程,线程就是里面的生产线,但主线程(主生产线)只有一条,而子线程(副生产线)可以有多个。
- 进程有自己独立的地址空间,而进程中的线程共享此地址空间,都可以并发执行。
- 在AndroidMenifest中给四大组件指定属性android:process开启多进程模式
- 在内存允许的条件下可以开启N个进程
- 所有运行在不同进程的四大组件(Activity、Service、Receiver、ContentProvider)共享数据都会失
败,这是由于Android为每个应用分配了独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这会导致在不同的虚拟机中访问同一个类的对象会产生多份副本。比如常用例子(通过开启多进程获取更大内存空间、两个或则多个应用之间共享数据、微信全家桶)- 一般来说,使用多进程通信会造成如下几方面的问题
1)静态成员和单例模式完全失效:独立的虚拟机造成
2)线程同步机制完全实效:独立的虚拟机造成
3)SharedPreferences的可靠性下降:这是因为Sp不支持两个进程并发进行读写,有一定几率导致数据丢失
4)Application会多次创建:Android系统在创建新的进程会分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程,自然也会创建新的Application
Linux系统将一个进程分为用户空间和内核空间。对于进程之间来说,用户空间的数据不可共享,内核空间的数据可共享,为了保证安全性和独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的,这就需要跨进程之间的数据通信方式。
一次完整的 Binder IPC 通信过程通常是这样:
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
- 因为bundle传递数据时只支持基本数据类型,所以在传递对象时需要序列化转换成可存储或可传输的
本质状态(字节流)。序列化后的对象可以在网络、IPC(比如启动另一个进程的Activity、Service和
Reciver)之间进行传输,也可以存储到本地。- 序列化实现的两种方式:实现Serializable/Parcelable接口。不同点如图:
AIDL(Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。
AIDL的本质是系统提供了一套可快速实现Binder的工具。关键类和方法:
1)AIDL接口:继承IInterface。
2)Stub类:Binder的实现类,服务端通过这个类来提供服务。
3)Proxy类:服务器的本地代理,客户端通过这个类调用服务器的方法。
4)asInterface():客户端调用,将服务端的返回的Binder对象,转换成客户端所需要的AIDL接口类型对象。如果客户端和服务端位于统一进程,则直接返回Stub对象本身,否则返回系统封装后的Stub.proxy对象
5)asBinder():根据当前调用情况返回代理Proxy的Binder对象。
6)onTransact():运行服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
7)transact():运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行。
- 当有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的
Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。解决办法是建立Binder连
接池,即将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建
Service。- 工作原理:每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对象,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了
在该Activity的实例已经存在于Task和Back stack中(或者通俗的说可以通过按返回键返回到该Activity )时,当使用
intent来再次启动该Activity的时候,如果此次启动不创建该Activity的新实例,则系统会调用原有实例onNewIntent()
方法来处理此intent.
且在下面情况下系统不会创建该Activity的新实例:
1、如果该Activity在Manifest中的android:launchMode定义为singleTask或者singleInstance.
2、如果该Activity在Manifest中的android:launchMode定义为singleTop且该实例位于Backstack的栈顶.
3、如果该Activity在Manifest中的android:launchMode定义为singleTop,且上述intent包含Intent.FLAG_ACTIVITY_CLEAR_TOP标志
4、如果上述intent中包含Intent.FLAG_ACTIVITY_CLEAR_TOP标志和且包含Intent.FLAG_ACTIVITY_SINGLE_TOP标志.
5、如果上述intent中包含Intent.FLAG_ACTIVITY_SINGLE_TOP 标志且该实例位于Back stack的栈顶。
上述情况满足其一,则系统将不会创建该Activity的新实例.
Intent 中的 Bundle 是使用 Binder 机制进行数据传送的, 数据会写到内核空间, Binder 缓冲区域;
Binder 的缓冲区是有大小限制的, 有些 ROM 是 1M, 有些ROM 是 2M;这个限制定义在frameworks/native/libs/binder/processState.cpp 类 中 , 如果超过这个限制, 系统就会报错;#define BINDER_VM_SIZE ((1*1024*1024) - (4096*2)) ;
- 1
因为 Binder 本身就是为了进程间频繁-灵活的通信所设计的, 并不是为了拷贝大量数据;如果非 ipc 就很简单了, static 变量, eventBus 之类的都可以;如果是 ipc, 一定要一次性传大文件, 可以用 file 或者socket;
ContentProvider:内容提供者, 用于对外提供数据,比如联系人应用中就是用了
ContentProvider:一个应用可以实现ContentProvider来提供给别的应用操作, 通过ContentResolver来操作别的应用数据
ContentResolver:内容解析者, 用于获取内容提供者提供的数据
ContentResolver.notifyChange(uri)发出消息
ContentObserver:内容监听者,可以监听数据的改变状态,观察(捕捉)特定的Uri引起的数据库的变化
ContentResolver.registerContentObserver()监听消息
概括:
使用ContentResolver来获取ContentProvider提供的数据, 同时注册ContentObserver监听数据的变化
App 启动流程(基于Android8.0):
点击桌面 App图标,Launcher进程采用 BinderIPC(具体为ActivityManager.getService 获取 AMS 实例) 向
system_server的AMS发起 startActivity请求,system_server 进程收到请求后,向 Zygote 进程发送创建进程的请求;
Zygote 进程 fork 出新的子进程,即 App 进程App 进程创建即初始化 ActivityThread,然后通过BinderIPC 向 system_server 进程的 AMS 发起attachApplication 请求,system_server 进程的 AMS 在收到attachApplication 请求后,做一系列操作后,通知 ApplicationThread bindApplication,然后发送H.BIND_APPLICATION 消息。
主线程收到 H.BIND_APPLICATION 消息,调用handleBindApplication 处理后做一系列的初始化操作,
初始化 Application 等,system_server 进程的 AMS 在 bindApplication 后,会调用 ActivityStackSupervisor.attachApplicationLocked, 之 后经过一系列操作,在 realStartActivityLocked 方法通过Binder IPC 向 App 进程发送scheduleLaunchActivity 请求;
App进程的 binder 线程(ApplicationThread)在收到请求后,通过 handler 向主线程发送LAUNCH_ACTIVITY 消息;主线程收到 message后经过 handleLaunchActivity,performLaunchActivity 方法,然后通过反射机制创建目标Activity;
通过 Activityattach方法创建 window并且和 Activity 关联,然后设置 WindowManager 用来管理 window, 然后通知 Activity 已创建,即调用 onCreate,然后调用 handleResumeActivity,Activity可见
- 例如handler 在界面销毁的时候消息还未发送
- file没有关流
- 查询到结果还没有停止
- 内部类持有外部类引用得不到释放
- 不用的对象最好null 让GC回收一下
- 图片资源什么的最好加一个软引用
- 资源对象没关闭造成的内存泄漏(如 : Cursor、File 等)
- ListView 的 Adapter 中没有使用缓存的ConvertView
- Bitmap对象不在使用时调用recycle()释放内存
- 集合中对象没清理造成的内存泄漏(特别是 static 修饰的集合)
- 接收器、监听器注册没取消造成的内存泄漏
- Activity 的 Context 造成的泄漏,可以使用ApplicationContext
- Handler 造成的内存泄漏问题(一般由于 Handler 生命周期比其外部类的生命周期长引起的)
在Android中,应用的响应性被活动管理器(Activity Manager)和窗口管理器(Window Manager)这两个系统服
务所监视。当用户触发了输入事件(如键盘输入,点 击按钮等),如果应用5秒内没有响应用户的输入事件,那么,
Android会认为该应用无响应,便弹出ANR对话框。而弹出ANR异常,也主要是为了提升用户体验。解决方案是对于耗时的操作,比如访问网络、访问数据库等操作,需要开辟子线程,在子线程处理耗时的操作,主线程主要实现UI的操作
关于内存泄漏,一般像单例模式的使用不当啊、集合的操作不当啊、资源的缺乏有效的回收机制啊、Handler、线程 的使
用不当等等都有可能引发内存泄漏。
- 单例模式引发的内存泄漏:
原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用
优化:改为持有Application的引用,或者不持有使用的时候传递。
- 集合操作不当引发的内存泄漏:
原因:集合只增不减
优化:有对应的删除或卸载操作
- 线程的操作不当引发的内存泄漏:
原因:线程持有对象的引用在后台执行,与对象的生命周期不一致
优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致
- 匿名内部类/非静态内部类操作不当引发的内存泄漏:
原因:内部类持有对象引用,导致无法释放,比如各种回调
优化:保持生命周期一致,改为静态实例+对象的弱引 用方式(WeakReference)
- 常用的资源未关闭回收引发的内存泄漏:
原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭
优化:使用后有对应的关闭和卸载机制
- Handler使用不当造成的内存泄漏:
原因:Handler持有Activity的引用,其发送的Message 中持有Handler的引用,当队列处理Message的时间过长会导致
Handler无法被回收优化:静态实例+弱引用(WeakReference)方式内存
- 降低Overdraw(过度绘制),减少不必要的背景绘制
- 减少嵌套层次及控件个数
- 使用Canvas的clipRect和clipPath方法限制View的绘制区域
- 通过imageDrawable方法进行设置避免ImageView的background和imageDrawable重叠
- 借助ViewStub按需延迟加载
- 选择合适的布局类型
- 熟悉API尽量借助系统现有的属性来实现一些UI效果
注:因为实际开发与参考答案会有所不同,再者怕误导大家,所以这些面试题答案还是自己去理解!为了方便大家阅读们这里剩下的面试题全部整理成PDF手册。
话不多说,先给大家看看,手册的内容结构:
手册目前共有Android部分,Java部分,Android Framework部分,Flutter,数据结构与算法,音视频等六个章节,共有 1000 多页,可以说基本涵盖了面试的必问考点
第一节 Dart部分
第二节 Flutter 部分
由于知识点涉及较广,因此将题目整理成PDF格式,需要的下方获取
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。