多线程部分八股文
1、单核环境下多线程编程是否还有意义
A:有意义,单核环境下线程A运行过程中陷入IO态,此时其他线程是可以继续获取到时间片running的
2、synchronized修饰静态方法和普通方法的区别
A:修饰静态方法锁的是类 修饰普通方法锁的是当前对象
3、synchronized实现原理:
A:上层运行指令被翻译为monitorenter和monitorexit指令
monitorenter执行对应三种锁状态 无锁 轻量级锁 重量级锁
锁信息和状态存储在lockword当中(这是一个uint_32类型)
无锁状态:NA
thinLock:十六位存储线程信息 接着12位存储锁深度 高二位存储状态 01
FatLock:30位存储monitor信息,高二位存储状态10
HashCode:
GC:
monitorEnter进入 当前如果处于无锁状态,则升级为轻量级锁(本质上也只是CAS)设置持锁线程 锁深度信息即可,如果当前是轻量级锁,需要判断是否锁升级(当前锁深度大于2^12-1,等锁线程自旋50次仍然无法得到锁)
锁升级:从monitorpool分配monitor对象,记录持锁线程,缓存当前锁深度,(monitor锁是通过mutex实现)
轻量级锁持锁与释放锁关键是对持锁线程和锁深度的参数更新
重量级锁:执行tryLock自旋等锁,等锁失败则进入阻塞状态
释放锁:
重量级锁:执行unlock函数,如果锁深度大于1,仅递减锁深度,锁深度为0则释放锁更新关键参数
4、CAS原理
compareAndSwap,关键三个值:内存值,当前值,修改值
内存值与当前值一致,则设置修改值,内存值与当前值不一致,则自旋再次尝试设置
有什么问题:
ABA:如果内存值被反复修改,经历ABA的过程,实际上对于修改方是不可感知的
存在什么问题:线程AB同时操作栈X,线程A意图操作栈顶数据,线程B此时执行pop 然后将栈下元素弹出,接着将Apop回去 CAS修改依旧会成功,但实际上栈的结构已经改变了,后续操作由不可知风险
怎么解决:加版本号,每次操作都会检查值和版本号(每次操作版本号递增)
5、volatile原理
内存可见 禁止指令重排
内存可见:被volatile修饰的变量 读写将直接从主存中读写,不走cpu cache,保证变量实时更新
禁止指令重排:非原子关联性操作,指令重排会导致不可预知灾难,例如懒汉式写法new instance,需要散步,分配内存,初始化对象,将对象指向分配的内存空间,23不强制依赖 因此会有指令重排 但是指令重排则会引发问题 volatile通过添加storestore storeload屏障实现禁止指令重排
5、锁的分类
公平锁/非公平锁:通过等待队列实现先到先得,非公平锁中锁的分配是随机的
乐观锁/悲观锁:乐观锁在得不到锁会持续申请锁,直到得到,悲观锁是得不到锁就会阻塞
独占锁/共享锁:锁的读写均在一个线程中进行就是独占锁,锁的读和写分离就是共享锁
6、ThreadLocal原理
建threadLocal,此时执行set,会先获取当前thread(key),然后获取到对应的threadlocalmap对象(这个是缓存在线程中的),set的话就是将threadlocal对象作为可以,set的数据作为value存储
7、threadLocal的内存泄漏问题
threadLocalMap的key是threadLocal对象 这个是一个弱引用,但是threadLocalMap却强引用entry,GC仅回收key entry(entry中的key被回收了)与value是不会被回收的
怎么解决:
记得revmove管理好生命周期
8、线程的生命周期
自己去想,关注几个函数 wait sleep yelid join
其中上层线程runnable与对应了art中的knative与krunnable
9、sleep wait yelid join的区别
sleep: 线程静态函数 不会释放锁,sleep到期后持锁继续运行
wait:object函数,调用释放锁,加入等待池,需要notify notifyAll唤醒
yelid:释放cpu时间片
join:线程A在run函数调用线程B的join函数,则线程B会在线程A运行完成后执行
10、sleep是否可中断
A:是,intercupt可以中断sleep
泛型
1、泛型是什么
多个不同类型数据执行相同逻辑函数,因此就有了泛型,支持不同参数类型执行相同逻辑函数
2、泛型的用法
可作用于类 接口 还有方法
泛型的实现原理:
泛型擦除,编译期间生命的类型在运行期间都会被转成object类型,因此 无法创建泛型数组,也无法instanceof比较 泛型异常也无法cache
3、通配符的用法
在使用泛型数据结构时候在菱形操作符中 extends super的要求遵循pecs原则,生产者(add put操作)菱形内部使用super(添加目标类型及其子类型)消费者使用extends(目标类型及其父类型)
4、桥接方法
对于运行期泛型擦除导致的定义的泛型类型被翻译为object类型导致的重写冲突,通过编译桥接进行类型转换,实现了多态性
反射
1、双亲委派机制:
类加载先从子类加载器逐层上传给父加载器,父加载器存在则返回结果 不存在则交给子类加载器去做
双亲委派的优点:避免一个类被重复加载,核心api防止被篡改(顶层类加载器进行加载)
2、如何打破双亲委派机制
自定义classloader,重写loadClass方法 不走父类loadClass
还有很多别的方法,自己不清楚
3、类加载流程
加载:将class文件加载到内存,在堆中创建一个class对象 作为入口
验证:验证语义等以及class文件是否安全等信息
准备:static变量赋予默认值 final变量赋予自定义值
解析:符号引用转直接引用
初始化:static信息赋初始值
4、反射应用场景有哪些
activity对象创建使用反射创建实现,对于其他类的private函数或者属性的调用或者读取通过反射实现(访问系统api或者隐藏api)
5、反射为什么比较慢
动态解析:运行时查找类信息 方法信息 new对象的话 直接从字节码文件中加载构造方法地址即可
安全检查:每次调用都要检查权限,调用setAccessible也只是允许私有构造函数被调用 但是在这之前会做判断该函数构造函数类型 public private等
参数处理:拆箱装箱,数组创建等
JIT优化受限:无法内联 无静态优化
6、dex odex vdex art oat这些的区别是什么
虚拟机
1、哪些对象可以被当做垃圾
可达性分析法
2、哪些对象可以当做GC root
static引用的对象
虚拟机栈引用对象
jni引用对象
常量引用对象(final关键字)
3、GC算法有哪些
标记清理算法
问题是内存碎片
标记整理算法
多了一步将存活对象放一起
复制算法
将存活对象放到另一个半区,前一个半区垃圾清理
分代
分为新生代和老年带,分区根据不同对象的特性避免了全局gc带来的性能损耗
新生代的算法一般为复制算法 老年代一般为标记整理
4、art中GC流程
内存区域类型
堆空间分为以下几种类型:
系统堆空间:image zygote spece 普通对象malloc region堆空间 再就是large object spec
分代的gc算法实际上就是在普通对象的region堆空间进行的
GC类型
sticky(仅回收上次gc后分配的对象) full(包括zygote一起回收) partial(仅回收非zygote堆空间)
GC触发条件
分配对象,内存不足 触发gc
5、ART中GC和jvm中的GC的区别(ART的定制化改动)
基本上一致,不同之处主要在于art对于堆空间做了定制化分区改造
6、jvm内存模型
程序计数器:记录程序运行位置
虚拟机栈:java层方法记录,栈帧包含方法出口等信息
本地方法栈:native方法记录栈,一个栈帧就是一个native方法
堆:对象保存区
元空间(方法区):已经加载类信息 常量池在这里
7、new对象流程
class是否被加载过,如果没加载过,则需要先加载类
然后将分配内存,判断是否内存工整(内存不工整则会维护一个列表保存各个内存块大小),并且进行指针碰撞,接着是分配对象头,最后是执行init构造函数
类加载时机:
new关键字 static final变量初始化等 主类入口函数 父类未加载 则初始化子类时先加载父类·
8、java中强弱软虚四种引用
强引用: =操作符,GC永不回收
软引用:softRefrence,get获取,内存不足回收
弱引用:weakRefrence,get获取,GC回收
虚引用:配合queue一起使用,GC时加入队列,此时可以触发其他清理工作
为什么有finalize还要有虚引用(1、重写finalize两次gc才会回收,第一次标记,第二次清除 2、finalize异常不会传播 会被吞没 根因是finalize自身会clearException)
9、除了虚引用 还有哪些办法实现动态清理
finalize方法的重写 和cleaner的使用
framework
WMS
1、焦点切换流程
·概述:应用状态变化触发刷新 刷新完成触发焦点更新,wms根据窗口的z轴顺序和可见性设置焦点窗口 并通过inputMonitor走ims通过inputDispatcher设置在inPutResolver当中,当input事件到来 查找焦点窗口,并且将事件进行分发
相关问题:
存在focusApp但不存在focusWindow是什么场景:
一般都是activity生命周期完成 但是窗口显示内容未准备好 比如窗口显示内容复杂 或者重IO操作 一般都会有上述场景问题
2、surface创建与管理
简单来说app relayout就会创建surfacecontrol并返回给app侧
3、系统窗口的创建与管理
4、动画管理
通过transitiController管理动画流程,与shell进行交互
5、窗口状态维护与更新
6、刷新流程
UI更新是一切起点,UI更新最终执行到scheduleTraversal 编舞者会触发回调,并申请vsync信号回调,vsync信号到来时此时会执行doFrame,严格按照input annimation traversal顺序执行 traversal执行则是传统得到measure layout draw,其中draw就是交由rt线程执行绘制与渲染 最终buffer交给sf进行合成
view绘制流程(参考刷新流程):
ui更新->scheduleTraversal->编舞者的postCallback与vsync回调注册(trvaersal类型执行measure测量 layout确定坐标 draw交由rt硬件加速绘制)最终等到vsync信号到来 提交buffer交给sf进行合成
Binder
1、原理
内核空间和用户空间:内核空间1G 用户空间3G,server进程用户空间与内核空间建立映射,client写入内核空间,server感知到更新值
2、流程:
a、 server进程向sm发送注册请求,sm想binder驱动发送注册请求(传递了binder对象和服务名称),并缓存在sm中的映射表中
b、client通过service名称查询引用(client->binder驱动->servicemanager->binder驱动返回proxy-client),并返回proxy对象
c、client调用proxy对象方法写入数据,并进入阻塞状态,binder驱动映射数据到server侧,并唤醒binder线程进行处理,完成后返回结果给client
3、相比其他ipc通信 binder优势是什么
性能优势:一次拷贝
架构优势:清晰的C/S架构
安全性:uid校验进行鉴权 安全性较高
4、aidl怎么用
服务端定义aidl接口
自动生成stub对象
服务端实现service
客户端bind服务端service
5、binder线程池创建流程
随进程创建触发,进程创建的时候创建binder主线程
后续随需要动态创建其他线程,一共16个(包括binder主线程)
binder线程空闲不会销毁
6、binder的死亡通知机制
服务端crash或者binder对象死亡都会通过引用计数方式感知回调给客户端
7、oneway binder是什么
异步binder,被oneway修饰的方法 在binder驱动中会交给其他binder线程异步处理,server端不需要返回结果值
8、zygote与system_server为什么使用socket
zygote使用socket本身也是因为binder驱动的初始化是晚于zygote
父进程中的子线程等锁 fork出子进程 复制父进程状态 此时就完蛋了 子进程死锁了
zygote是单线程的 binder多线程 因此使用socket
9、input通信为什么使用zygote
input时间多且频繁 对于实时性要求很高,且本地socket性能和binder差距不大 因此选择socket
Handler
ANR&&Crash&&卡顿
android基础
1、activity生命周期
2、service生命周期
3、四种启动模式与onNewIntent
4、Service与IntentService
ART
编译模式
编译流程
线程实现
锁
GC