八股文

22 阅读9分钟

多线程部分八股文

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

C++八股文