}
}
收到Vsync信号回调,注释1会往主线程MessageQueue
post一个异步消息,保证注释2的doFrame
优先执行。
doFrame
才是View真正开始绘制的地方,会调用ViewRootImpl
的doTraversal
、performTraversals
,
而performTraversals
里面会调用我们熟悉的View的onMeasure
、onLayout
、onDraw
。
这里还可以延伸到vsync信号原理,以及为什么要等vsync信号回调才开始View的绘制流程、掉帧的原理、屏幕的双缓冲、三缓冲,由于文章篇幅关系,不是本文的重点,就不一一分析了~
虽然app无法发送同步屏障消息,但是使用异步消息是允许的
异步消息
首先,SDK中限制了App不能post异步消息到MessageQueue里去的,相关字段被加了UnsupportedAppUsage
注解
-> Message
@UnsupportedAppUsage
/package/ int flags;
/**
-
Returns true if the message is asynchronous, meaning that it is not
-
subject to {@link Looper} synchronization barriers.
-
@return True if the message is asynchronous.
-
@see #setAsynchronous(boolean)
*/
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
不过呢,高版本的Handler的构造方法可以通过传async=true,来使用异步消息
public Handler(@Nullable Callback callback, boolean async) {}
复制代码
然后在Handler
发送消息的时候,都会走到 enqueueMessage
方法,如下代码块所示,每个消息都带了异步属性,有优先处理权
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
…
//如果mAsynchronous为true,就都设置为异步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
对于低版本SDK,想要使用异步消息,可以通过反射调用Handler(@Nullable Callback callback, boolean async)
,参考androidx内部的一段代码如下
->androidx.arch.core.executor.DefaultTaskExecutor
private static Handler createAsync(@NonNull Looper looper) {
if (Build.VERSION.SDK_INT >= 28) {
return Handler.createAsync(looper);
}
if (Build.VERSION.SDK_INT >= 16) {
try {
return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class,
boolean.class)
.newInstance(looper, null, true);
} catch (IllegalAccessException ignored) {
} catch (InstantiationException ignored) {
} catch (NoSuchMethodException ignored) {
} catch (InvocationTargetException e) {
return new Handler(looper);
}
}
return new Handler(looper);
}
需要注意的是,App要谨慎使用异步消息,使用不当的情况下可能会出现主线程假死的问题,排查也比较困难
分析完MessageQueue#next
再回头来看看 Handler
的dispatchMessage
方法
2.1.2 dispatchMessage
上面说到next方法轮循取消息一般情况下是没有问题的,那么只剩下处理消息的逻辑
Handler#dispatchMessage
/**
- Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
dispatchMessage 有三个逻辑,分别对应Handler 使用的三种方式
-
Handler#post(Runnable r)
-
构造方法传CallBack,
public Handler(@Nullable Callback callback, boolean async) {}
-
Handler 重写 handleMessage 方法
所以,应用卡顿,原因一般都可以认为是Handler处理消息太耗时导致的,细分的原因可能是方法本身太耗时、算法效率低、cpu被抢占、内存不足、IPC超时等等。
2.2 卡顿监控
面试中,被问到如何监控App卡顿,统计方法耗时,我们可以从源码开始切入,讲讲如何通过Looper
提供的Printer
接口,计算Handler
处理一个消息的耗时,判断是否出现卡顿。
2.2.1 卡顿监控方案一
看下Looper 循环的注释2和注释4,可以找到一种卡顿监控的方法
Looper#loop
public static void loop() {
for (;😉 {
//1、取消息
Message msg = queue.next(); // might block
…
//2、消息处理前回调
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
…
//3、消息开始处理
msg.target.dispatchMessage(msg);// 分发处理消息
…
//4、消息处理完回调
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
…
}
注释2和注释4的logging.println
是谷歌提供给我们的一个接口,可以监听Handler处理消息耗时,我们只需要调用Looper.getMainLooper().setMessageLogging(printer)
,即可从回调中拿到Handler处理一个消息的前后时间。
需要注意的是,监听到发生卡顿之后,dispatchMessage
早已调用结束,已经出栈,此时再去获取主线程堆栈,堆栈中是不包含卡顿的代码的。
所以需要在后台开一个线程,定时获取主线程堆栈,将时间点作为key,堆栈信息作为value,保存到Map中,在发生卡顿的时候,取出卡顿时间段内的堆栈信息即可。
不过这种方案只适合线下使用,原因如下:
-
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
存在字符串拼接,频繁调用,会创建大量对象,造成内存抖动。 -
后台线程频繁获取主线程堆栈,对性能有一定影响,获取主线程堆栈,会暂停主线程的运行。
2.2.2 卡顿监控方案二
对于线上卡顿监控,需要了解字节码插桩技术。
通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计方法耗时,
伪代码如下
插桩前
fun method(){
run()
}
插桩后
fun method(){
input(1)
run()
output(1)
}
目前微信的Matrix 使用的卡顿监控方案就是字节码插桩,如下图所示
插桩需要注意的问题:
-
避免方法数暴增:在方法的入口和出口应该插入相同的函数,在编译时提前给代码中每个方法分配一个独立的 ID 作为参数。
-
过滤简单的函数:过滤一些类似直接 return、i++ 这样的简单函数,并且支持黑名单配置。对一些调用非常频繁的函数,需要添加到黑名单中来降低整个方案对性能的损耗。
微信Matrix做了大量优化,整体包体积增加1%-2%,帧率下降2帧以内,对性能影响整体可以接受,不过依然只会在灰度包使用。
再来说说ANR~
ANR 的类型和触发ANR的流程
3.1 哪些场景会造成ANR呢
-
Service Timeout:比如前台服务在20s内未执行完成,后台服务是10s;
-
BroadcastQueue Timeout:比如前台广播在10s内未执行完成,后台60s
-
ContentProvider Timeout:内容提供者,在publish过超时10s;
-
InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
相关超时定义可以参考ActivityManagerService
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
3.2 ANR触发流程
来简单分析下源码,ANR触发流程其实可以比喻成埋炸弹和拆炸弹的过程,
以后台Service为例
3.2.1 埋炸弹
Context.startService
调用链如下:
AMS.startService
ActiveServices.startService
ActiveServices.realStartServiceLocked
ActiveServices.realStartServiceLocked
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
…
//1、这里会发送delay消息(SERVICE_TIMEOUT_MSG)
bumpServiceExecutingLocked(r, execInFg, “create”);
try {
…
//2、通知AMS创建服务
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
}
…
}
注释1的bumpServiceExecutingLocked内部调用scheduleServiceTimeoutLocked
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
…
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
// 发送deley消息,前台服务是20s,后台服务是10s
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
注释2通知AMS启动服务之前,注释1处发送Handler延时消息,埋下炸弹,如果10s内(前台服务是20s)没人来拆炸弹,炸弹就会爆炸,即ActiveServices#serviceTimeout
方法会被调用
3.2.2 拆炸弹
启动一个Service,先要经过AMS管理,然后AMS会通知应用进程执行Service的生命周期, ActivityThread
的handleCreateService
方法会被调用
-> ActivityThread#handleCreateService
private void handleCreateService(CreateServiceData data) {
try {
…
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
//1、service onCreate调用
service.onCreate();
mServices.put(data.token, service);
try {
//2、拆炸弹在这里
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
注释1,Service
的onCreate
方法被调用,
注释2,调用AMS的serviceDoneExecuting
方法,最终会调用到ActiveServices. serviceDoneExecutingLocked
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing) {
…
//移除delay消息
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
…
}
可以看到,onCreate
方法调用完之后,就会移除delay消息,炸弹被拆除。
3.2.3 引爆炸弹
假设Service的onCreate执行超过10s,那么炸弹就会引爆,也就是
ActiveServices#serviceTimeout
方法会被调用
void serviceTimeout(ProcessRecord proc) {
…
if (anrMessage != null) {
mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
}
…
}
所有ANR,最终都会调用AppErrors
的appNotResponding
方法
AppErrors #appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, boolean aboveSystem, final String annotation) {
…
//1、写入event log
// Log the ANR to the event log.
EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
app.processName, app.info.flags, annotation);
…
//2、收集需要的log,anr、cpu等,StringBuilder凭借
// Log the ANR to the main log.
StringBuilder info = new StringBuilder();
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (“).append(activity.shortComponentName).append(”)");
}
info.append(“\n”);
info.append(“PID: “).append(app.pid).append(”\n”);
if (annotation != null) {
info.append(“Reason: “).append(annotation).append(”\n”);
}
if (parent != null && parent != activity) {
info.append(“Parent: “).append(parent.shortComponentName).append(”\n”);
}
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
…
// 3、dump堆栈信息,包括java堆栈和native堆栈,保存到文件中
// For background ANRs, don’t pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
File tracesFile = ActivityManagerService.dumpStackTraces(
true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
nativePids);
String cpuInfo = null;
…
//4、输出ANR 日志
Slog.e(TAG, info.toString());
if (tracesFile == null) {
// 5、没有抓到tracesFile,发一个SIGNAL_QUIT信号
// There is no trace file, so dump (only) the alleged culprit’s threads to the log
Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
}
StatsLog.write(StatsLog.ANR_OCCURRED, …)
// 6、输出到drapbox
mService.addErrorToDropBox(“anr”, app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null);
…
synchronized (mService) {
mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
//7、后台ANR,直接杀进程
if (isSilentANR) {
app.kill(“bg anr”, true);
return;
}
//8、错误报告
// Set the app’s notResponding state, and look up the errorReportReceiver
makeAppNotRespondingLocked(app,
activity != null ? activity.shortComponentName : null,
annotation != null ? "ANR " + annotation : “ANR”,
info.toString());
//9、弹出ANR dialog,会调用handleShowAnrUi方法
// Bring up the infamous App Not Responding dialog
Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
mService.mUiHandler.sendMessage(msg);
}
}
主要流程如下:
1、写入event log
2、写入 main log
3、生成tracesFile
4、输出ANR logcat(控制台可以看到)
5、如果没有获取到tracesFile,会发一个SIGNAL_QUIT
信号,这里看注释是会触发收集线程堆栈信息流程,写入traceFile
6、输出到drapbox
7、后台ANR,直接杀进程
8、错误报告
9、弹出ANR dialog,会调用 AppErrors#handleShowAnrUi
方法。
ANR触发流程小结
ANR触发流程,可以比喻为埋炸弹和拆炸弹的过程,
以启动Service为例,Service的onCreate方法调用之前会使用Handler发送延时10s的消息,Service 的onCreate方法执行完,会把这个延时消息移除掉。
假如Service的onCreate方法耗时超过10s,延时消息就会被正常处理,也就是触发ANR,会收集cpu、堆栈等信息,弹ANR Dialog。
service、broadcast、provider 的ANR原理都是埋定时炸弹和拆炸弹原理,
但是input的超时检测机制稍微有点不同,需要等收到下一次input事件,才会去检测上一次input事件是否超时,input事件里埋的炸弹是普通炸弹,需要通过扫雷来排查。
上面已经分析了ANR触发流程,最终会把发生ANR时的线程堆栈、cpu等信息保存起来,我们一般都是分析 /data/anr/traces.txt 文件
4.1 模拟死锁导致ANR
private fun testAnr(){
val lock1 = Object()
val lock2 = Object()
//子线程持有锁1,想要竞争锁2
thread {
synchronized(lock1){
Thread.sleep(100)
synchronized(lock2){
Log.d(TAG, “testAnr: getLock2”)
}
}
}
//主线程持有锁2,想要竞争锁1
synchronized(lock2){
Thread.sleep(100)
synchronized(lock1){
Log.d(TAG, “testAnr: getLock1”)
}
}
}
触发ANR之后,一般我们会拉取anr日志: adb pull /data/traces.txt(文件名可能是anr_xxx.txt)
4.2 分析ANR 文件
首先看主线程,搜索 main
ANR日志中有很多信息,可以看到,主线程id是1(tid=1),在等待一个锁,这个锁一直被id为22的程持有,那么看下22号线程的堆栈
id为22的线程是Blocked状态,正在等待一个锁,这个锁被id为1的线程持有,同时这个22号线程还持有一个锁,这个锁是主线程想要的。
通过ANR日志,可以很清楚分析出这个ANR是死锁导致的,并且有具体堆栈信息。
上面只是举例一种死锁导致ANR的情况,实际项目中,可能有很多情况会导致ANR,例如内存不足、CPU被抢占、系统服务没有及时响应等等。
如果是线上问题,怎么样才能拿到ANR日志呢?
前面已经分析了ANR触发流程,以及常规的线下分析方法,看起来还是有点繁琐的,需要pull出anr日志,然后分析线程堆栈等信息。对于线上ANR,如何搭建一个完善的ANR监控系统呢?
下面将介绍ANR监控的方式
5.1 抓取系统traces.txt 上传
1、当监控线程发现主线程卡死时,主动向系统发送SIGNAL_QUIT信号。
2、等待/data/anr/traces.txt文件生成。
3、文件生成以后进行上报。
看起来好像可行,不过有以下两个问题:
1、traces.txt 里面包含所有线程的信息,上传之后需要人工过滤分析
2、很多高版本系统需要root权限才能读取 /data/anr这个目录
既然这个方案存在问题,那么可还有其它办法?
5.2 ANRWatchDog
ANRWatchDog 是一个自动检测ANR的开源库
5.2.1 ANRWatchDog 原理
其源码只有两个类,核心是ANRWatchDog
这个类,继承自Thread,它的run 方法如下,看注释处
public void run() {
setName(“|ANR-WatchDog|”);
long interval = _timeoutInterval;
// 1、开启循环
while (!isInterrupted()) {
boolean needPost = _tick == 0;
_tick += interval;
if (needPost) {
// 2、往UI线程post 一个Runnable,将_tick 赋值为0,将 _reported 赋值为false
_uiHandler.post(_ticker);
}
try {
// 3、线程睡眠5s
Thread.sleep(interval);
} catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
// If the main thread has not handled _ticker, it is blocked. ANR.
// 4、线程睡眠5s之后,检查 _tick 和 _reported 标志,正常情况下_tick 已经被主线程改为0,_reported改为false,如果不是,说明 2 的主线程Runnable一直没有被执行,主线程卡住了
if (_tick != 0 && !_reported) {
…
if (_namePrefix != null) {
// 5、判断发生ANR了,那就获取堆栈信息,回调onAppNotResponding方法
error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
} else {
error = ANRError.NewMainOnly(_tick);
}
_anrListener.onAppNotResponding(error);
interval = _timeoutInterval;
_reported = true;
}
}
}
ANRWatchDog
的原理是比较简单的,概括为以下几个步骤
-
开启一个线程,死循环,循环中睡眠5s
-
往UI线程post 一个Runnable,将_tick 赋值为0,将 _reported 赋值为false
-
线程睡眠5s之后检查_tick和_reported字段是否被修改
-
如果_tick和_reported没有被修改,说明给主线程post的Runnable一直没有被执行,也就说明主线程卡顿至少5s**(只能说至少,这里存在5s内的误差)**。
-
将线程堆栈信息输出
其中涉及到并发的一个知识点,关于 volatile
关键字的使用,面试中的常客, volatile
的特点是:保证可见性,禁止指令重排,适合在一个线程写,其它线程读的情况。
面试中一般会展开问JMM,工作内存,主内存等,以及为什么要有工作内存,能不能所有字段都用 volatile
关键字修饰等问题。
回到ANRWatchDog本身,细心的同学可能会发现一个问题,使用ANRWatchDog有时候会捕获不到ANR,是什么原因呢?
5.2.2 ANRWatchDog 缺点
ANRWatchDog 会出现漏检测的情况,看图
如上图这种情况,红色表示卡顿,
-
假设主线程卡顿了2s之后,ANRWatchDog这时候刚开始一轮循环,将_tick 赋值为5,并往主线程post一个任务,把_tick修改为0
-
主线程过了3s之后不卡顿了,将_tick赋值为0
-
等到ANRWatchDog睡眠5s之后,发现_tick的值是0,判断为没有发生ANR。而实际上,主线程中间是卡顿了5s,ANRWatchDog误差是在5s之内的(5s是默认的,线程的睡眠时长)
针对这个问题,可以做一下优化。
5.3 ANRMonitor
ANRWatchDog 漏检测的问题,根本原因是因为线程睡眠5s,不知道前一秒主线程是否已经出现卡顿了,如果改成每间隔1秒检测一次,就可以把误差降低到1s内。
接下来通过改造ANRWatchDog ,来做一下优化,命名为ANRMonitor。
我们想让子线程间隔1s执行一次任务,可以通过 HandlerThread
来实现
流程如下:
核心的Runnable代码
@Volatile
var mainHandlerRunEnd = true
//子线程会间隔1s调用一次这个Runnable
private val mThreadRunnable = Runnable {
blockTime++
//1、标志位 mainHandlerRunEnd 没有被主线程修改,说明有卡顿
if (!mainHandlerRunEnd && !isDebugger()) {
logw(TAG, “mThreadRunnable: main thread may be block at least $blockTime s”)
}
//2、卡顿超过5s,触发ANR流程,打印堆栈
if (blockTime >= 5) {
if (!mainHandlerRunEnd && !isDebugger() && !mHadReport) {
mHadReport = true
//5s了,主线程还没更新这个标志,ANR
loge(TAG, "ANR->main thread may be block at least $blockTime s ")
loge(TAG, getMainThreadStack())
//todo 回调出去,这里可以按需把其它线程的堆栈也输出
//todo debug环境可以开一个新进程,弹出堆栈信息
}
}
//3、如果上一秒没有卡顿,那么重置标志位,然后让主线程去修改这个标志位
if (mainHandlerRunEnd) {
mainHandlerRunEnd = false
mMainHandler.post {
mainHandlerRunEnd = true
}
}
//子线程间隔1s调用一次mThreadRunnable
sendDelayThreadMessage()
}
-
子线程每隔1s会执行一次mThreadRunnable,检测标志位 mainHandlerRunEnd 是否被修改
-
假如mainHandlerRunEnd如期被主线程修改为true,那么重置mainHandlerRunEnd标志位为false,然后继续执行步骤1
-
假如mainHandlerRunEnd没有被修改true,说明有卡顿,累计卡顿5s就触发ANR流程
在监控到ANR的时候,除了获取主线程堆栈,还有cpu、内存占用等信息也是比较重要的,demo中省略了这部分内容。
5.3.1 测试ANR
5.3.2 ANR检测结果
logcat打印所示
主线程卡顿超过5s,会打堆栈信息,如果是卡顿1-5s内,会有warning的log 提示,线下可以做成弹窗或者toast提示,
看到这里,大家应该能想到,线下也可以用这种方法检测卡顿,定位到耗时的代码。
此方案可以结合ProcessLifecycleOwner
,应用在前台才开启检测,进入后台则停止检测。
在发生ANR的时候,有时候只有主线程堆栈信息可能还不够,例如发生死锁的情况,需要知道当前线程在等待哪个锁,以及这个锁被哪个线程持有,然后把发生死锁的线程堆栈信息都收集到。
流程如下:
-
获取当前blocked状态的线程
-
获取该线程想要竞争的锁
-
获取该锁被哪个线程持有
-
通过关系链,判断死锁的线程,输出堆栈信息
在Java层并没有相关API可以实现死锁监控,可以从Native层入手。
6.1 获取当前blocked状态的线程
这个比较简单,一个for循环就搞定,不过我们要的线程id是native层的线程id,Thread 内部有一个native线程地址的字段叫 nativePeer
,通过反射可以获取到。
Thread[] threads = getAllThreads();
for (Thread thread : threads) {
if (thread.getState() == Thread.State.BLOCKED) {
long threadAddress = (long) ReflectUtil.getField(thread, “nativePeer”);
// 找不到地址,或者线程已经挂了,此时获取到的可能是0和-1
if (threadAddress <= 0) {
continue;
}
…后续
}
}
有了native层线程地址,还需要找到native层相关函数
6.2 获取当前线程想要竞争的锁
从ART 源码可以找到这个函数 androidxref.com/8.0.0_r4/xr…
函数:Monitor::GetContendedMonitor
从源码和源码的解释可以看出,这个函数是用来获取当前线程等待的Monitor。
顺便说说Monitor以及Java对象结构
Monitor
Monitor是一种并发控制机制,提供多线程环境下的互斥和同步,以支持安全的并发访问。
Monitor由以下3个元素组成:
-
临界区:例如synchronize修饰的代码块
-
条件变量:用来维护因不满足条件而阻塞的线程队列
-
Monitor对象,维护Monitor的入口、临界区互斥量(即锁)、临界区和条件变量,以及条件变量上的阻塞和唤醒
Java的Class对象
Java的Class对象包括三部分组成:
- 对象头:MarkWord和对象指针
MarkWord(标记字段):保存哈希码、分代年龄、锁标志位、偏向线程ID、偏向时间戳等信息 对象指针:即指向当前对象的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
-
实例数据:对象实际的数据
-
对齐填充:按8字节对齐(JVM自动内存管理系统要求对象起始地址必须是8字节的整数倍)。例如Integer对象,对象头MarkWord和对象指针分别占用4字节,实例数据4字节,那么对齐填充就是4字节,Integer占用内存是int的4倍。
回到 GetContendedMonitor
函数,我们可以通过打开动态库libart.so
,然后使用dlsym
获取函数的符号地址,然后就可以进行调用了。
由于Android 7.0开始,系统限制App中调用dlopen
,dlsym
等函数打开系统动态库,我们可以使用 ndk_dlopen这个库来绕过这个限制
//1、初始化
ndk_init(env);
//2、打开动态库libart.so
void *so_addr = ndk_dlopen(“libart.so”, RTLD_NOLOAD);
if (so_addr == NULL) {
return 1;
}
打开动态库之后,会返回动态库的内存地址,接下来就可以通过dlsym
获取GetContendedMonitor
这个函数的符号地址,只不过要注意,c++可以重载,所以它的函数符号比较特殊,需要从libart.so
中搜索匹配找到
//c++跟c不一样,c++可以重载,描述符会变,需要打开libart.so,在里面搜索查找GetContendedMonitor的函数符号
//http://androidxref.com/8.0.0_r4/xref/system/core/libbacktrace/testdata/arm/libart.so
//获取Monitor::GetContendedMonitor函数符号地址
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
对象指针:即指向当前对象的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
-
实例数据:对象实际的数据
-
对齐填充:按8字节对齐(JVM自动内存管理系统要求对象起始地址必须是8字节的整数倍)。例如Integer对象,对象头MarkWord和对象指针分别占用4字节,实例数据4字节,那么对齐填充就是4字节,Integer占用内存是int的4倍。
回到 GetContendedMonitor
函数,我们可以通过打开动态库libart.so
,然后使用dlsym
获取函数的符号地址,然后就可以进行调用了。
由于Android 7.0开始,系统限制App中调用dlopen
,dlsym
等函数打开系统动态库,我们可以使用 ndk_dlopen这个库来绕过这个限制
//1、初始化
ndk_init(env);
//2、打开动态库libart.so
void *so_addr = ndk_dlopen(“libart.so”, RTLD_NOLOAD);
if (so_addr == NULL) {
return 1;
}
打开动态库之后,会返回动态库的内存地址,接下来就可以通过dlsym
获取GetContendedMonitor
这个函数的符号地址,只不过要注意,c++可以重载,所以它的函数符号比较特殊,需要从libart.so
中搜索匹配找到
//c++跟c不一样,c++可以重载,描述符会变,需要打开libart.so,在里面搜索查找GetContendedMonitor的函数符号
//http://androidxref.com/8.0.0_r4/xref/system/core/libbacktrace/testdata/arm/libart.so
//获取Monitor::GetContendedMonitor函数符号地址
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-po8xMdtq-1715861253520)]
[外链图片转存中…(img-2l337nJg-1715861253521)]
[外链图片转存中…(img-aNDK1nuU-1715861253522)]
[外链图片转存中…(img-oOy5VVGZ-1715861253523)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!