前面的系列文章介绍了android应用如何分析native内存。
接下来就是android应用如何分析java内存。同native一样,我们也希望能够看到
ART的堆和栈的情况,以及锁的情况,方法的本地变量,以及栈帧等
因此ART的内存分析就变成两个部分:
注意:在Android中,有几个特殊的堆内存,如存储dex文件的image heap和存储共有资源的zygote heap。他们属于Framework应该关心的内容,因此不在此系列中做介绍。
如何查看ART的栈情况呢?还是跟native一样,可以使用调试器进行查看。
java的调试器,称为jdb。
跟native的gdb和lldb的远程调试一样,jdb也分成两个部分:
这两个部分要进行通信,他们的通信协议叫做JDWP(java debug wire protocol)
因为ART属于标准java虚拟机之一,所以任何一个jdb即可调试Android的java应用。
需要在编译的时候,加上-g选项。如果使用的是Android studio,则在编译器中配置如下
如果能自定义ROM,在编译的时候加上-g选项,则可以调试整个Frmawork代码,此处不涉及Framework因此,不过多介绍。
使用javap命令,查看对应的xxx.class是否包含:LineNumberTable和LocalVariableTable
举例截图如下:
javap -v ./MainActivity.class | grep -E "LineNumberTable|LocalVariableTable"
安装应用,并运行adb jdwp其中adb jdwp用于查看满足jdwp协议的应有有哪些。列出的数字为进程pid
将本地调试的端口转发给Android应用,运行如下命令:
## 此处我们再次选择了5039端口用于jdwp的连接。(在gdb和lldb中也是使用了5039见:http://t.csdn.cn/QkkH3和http://t.csdn.cn/JWgcF)
adb forward tcp:5039 jdwp:6405
jdb -attach localhost:5039
则输出如下,表示连接成功:
设置未捕获的java.lang.Throwable
设置延迟的未捕获的java.lang.Throwable
正在初始化jdb...
>
注意:在运行jdb之前,一定要先停止Android studio的运行,否则连接不上
注意:在本文中,井号开头的行表示注释.而右尖括号,表示键入的命令
## suspend [thread id(s)] -- 挂起线程 (默认值: all)
> suspend
#threads [threadgroup] -- 列出线程
>threads
#where [ | all] -- 转储线程的堆栈
> where 0x4e81
同时将main线程选为了当前线程
#locals 打印本地变量
>locals
# up [n frames] -- 上移线程的堆栈
# down [n frames] -- 下移线程的堆栈
# dump 变量名
> dump view.mTextClassifierHelper
# 在jdb启动的时候,添加选项-sourcepath 路径1:路径2 即可添加源代码的路径
# 这样在调试AOSP代码的时候,会变得异常方便
# 还可以在jdb内部,使用use命令,修改源码路径
# 如下面使用jdb调试Android原生代码里面的MessageQueue
注意:在后面会有一个AMS的源码分析,将会非常频繁的使用本章介绍的jdb内容
# list [line number|method] -- 输出源代码
# 见上图
# stop in .[(argument_type,...)] 在方法处设置一个断点
# stop at : 在某行设置一个断点
# clear .[(argument_type,...)] 清除断点
# clear : 清除断点
# clear 列出断点
# stop 列出断点
>stop in com.example.test_malloc.MainActivity.click
触发断点如下
其中bci表示:字节码索引
# step -- 执行当前行,可理解为单步进入
# step up -- 执行直到当前方法返回到它的调用者
# stepi -- 执行当前指令
# next -- 步进一行,可以理解为单步over
# cont -- 从断点处继续执行
# set = 赋值字段,变量,数组元素新值
# trace [go] methods [thread]
-- 跟踪方法进入和退出。
-- 除非指定 'go', 否则挂起所有线程
# trace [go] method exit | exits [thread]
-- 跟踪当前方法的退出, 或者所有方法的退出
-- 除非指定 'go', 否则挂起所有线程
untrace [methods] -- 停止跟踪方法进入和/或退出
trace method 命令在调试大型程序或复杂的方法调用关系时特别有用。它可以帮助你定位问题,理解代码的执行流程,并找到潜在的错误或异常。
# catch [uncaught|caught|all] | 当指定的异常发生时,暂停,如下图
# ignore [uncaught|caught|all] | 取消捕获某个异常
# lock -- 打印某个对象上面的锁信息
# threadlocks [thread id] --打印线程上面的锁信息
# eval 表达式——计算表达式的值
# 计算变量x和y的值
>eval x+y
同native一样,即时调试分成两部分
android 提供了一个命令行选项,告诉app,启动之后,等待debugger的接入。命令如下:
adb shell am set-debug-app -w com.example.test_malloc
说明:set-debug-app [-w] [--persistent]
-w: 应用开始,就等待debugger接入
--persistent: 这个值一直存在,不会因为应用消失之后被清除掉
suspend
注意:除了通过上面的操作等待jdb的连接以外,还可以通过代码的方法。如线程的挂起API或者一个循环。因为较简单,不再做过多介绍
问题1:如果jdb连上之后,app依然在运行
解决:漏掉了上面的第2步
问题2:app启动之后,没有出现wait for debugger
解决:需要先查看自己所在的系统是否支持第1步中的命令。使用如下命令查看
adb shell am -h
根据上面的输出,设置成对应的命令即可
因为java程序崩溃时,会输出一段详细的调用栈。而不像native程序那样情况复杂。所以对于程序的崩溃很好分析。因此这部分内容无。
注意:Android应用中,可能因为复杂的线程和锁关系,导致ANR,从而引起的崩溃。此时通过Android的ANR文件,即可分析出相应的原因。这不属于本文要介绍的范围。后续有时间再写一个怎么分析ANR
至此,java的jdb大部分功能已经介绍完毕,它比AS提供的功能更加丰富,多出来的功能有:
上面多出来的功能,有助于:分析大型Android项目,分析AOSP系统级代码,分析Android漏洞,分析多线程编程
在完成jdb的命令行介绍之前,先对jdb能观察的内存内容做一个小结:
本文完。
在进行java堆内容查看之前。先引入对应的UI界面,下一小节将介绍VSCode调试Android应用