对暑期大创的回忆总结。
暑期任务
首先以线上的形式确定了任务目标,任务安排与管理方式。
任务目标:
- 实现一个可视化jvm垃圾回收的小Demo,要拥有基本功能。
- 针对普遍使用的jdk1.8
管理方式:
- 一周开一次组会,介绍一周的工作内容,遇到的问题,和下周的工作目标,并将以上三项内容整理成文档提交。
任务安排:
- 首先学习jvm的基本知识,参考《深入理解Java虚拟机》或其他资料。耗时3~4周。
- 前后端分工,并行完成任务。耗时3~4周。
- 前端需求:
- 用户可以上传代码
- 拿到后端分析结果后进行动画演示。
- 动画详细到Eden,S0,S1,Old四个代。包括对象的创建、移动、销毁。
- 后端需求:
- 分析出代码执行时,堆的信息。包括:对象的创建,GC的发生过程,堆的各代占用空间的大小。
- 对象的创建事件要包含如下信息:对象的名称、大小、类型、分配在哪个代。
- GC的发生过程要包含如下信息:回收了哪些对象,存活的对象的移动。
- 前端需求:
工作汇报
后端实现
实现思路:将用户的代码运行一遍,当发生如:实例创建、垃圾回收时,将需要的信息输出,整理加工后返回给前端。
第一个想法
下载openjdk的源代码,修改相关事件的代码,将信息输出。在ubuntu系统的虚拟机上成功配置了openjdk环境,经过测试验证了这种方式在理论上是可行的,后来因为能力有限,源代码阅读困难而放弃。
第二个想法
了解到了jvmti这个工具,打算通过agent的方式,实现一个jvm虚拟机的代理。通过使用jvmti来设置虚拟机一些事件的监听,来完成所需信息的输出。通过阅读Jvmti的官方文档和翻译文档,以及阅读openjdk中提供的jvmti demo的代码,这个想法逐渐被实现。
想法二的实现过程记录
需要解决的问题有两个:获取实例的创建信息,获取GC的相关信息。因为项目为垃圾回收可视化,故将垃圾回收的信息作为重点问题。为了显示GC的信息,我们需要拿到GC之前有哪些对象,他们分别在哪里,他们在GC后有哪些依旧存活,哪些被销毁,GC后都在哪个代中。
方案一
预期使用
FollowReferences
和IterateThroughHeap
来获取堆中有哪些对象,并获取对象的一些信息。并监听GarbageCollectionStart
和GarbageCollectionFinish
来确定信息的输出时机。
因为JVMTI的规定,这两个GC事件比较特殊,可以在函数体内调用的函数有限,其中并不包括FollowReferences
和IterateThroughHeap
函数,只能启动新的线程来完成该任务。但是在测试时,windows系统中可以运行,而在ubuntu16系统中会发生fatal error
,寻找错误原因将近两周也没有找到,最终放弃。
方案二
- 关于jvmti agent
将可视化需要的数据进行分类,每类作为一种事件,在agent运行时将事件输出,而后对事件信息进行分析处理返回给前端。以下是agent输出的几种事件:
对象的创建
借鉴于openjdk中的jvmti demo,heapTracker,决定使用native函数结合字节码增强的方式,每次创建对象都会调用native函数,将每次对象创建时的一些信息传入到jvmti agent中,并通过jvmti的
setTag
函数,将一些信息标记到对象上。- 获取对象的相关信息
通过jvmti提供的
GetObjectClass
,GetClassSignature
,GetObjectSize
函数分别获取对象的类签名、对象大小。借鉴Unsafe
类获取对象地址的源码,实现获取对象地址的方法,从而结合GC日志判断对象所处的分代。对象的回收
通过设置事件
ObjectFree
的监听,并通过tag来获取对象信息,从而实现创建的对象和销毁的对象的对应关系。GC事件的发生
只输出事件开始的标记,结合GCDetails日志进行分析。
GC事件的结束
只输出事件结束的标记,结合GCDetails日志进行分析。
关于插桩方案
使用javassist工具库进行插桩。找到所有含有
new
关键字的代码行,并在其后插入写好的静态native函数。为了防止插桩影响GC的发生时机,将函数声明为静态函数。在jdk1.8环境下并不会影响堆内存。
后端程序
后端程序使用springboot来完成。项目中存有插桩程序的jar文件,和jvmti的agent扩展程序文件。
服务器提供两个接口:
POST /analysis/codes
上传代码接口。
服务器接受到代码后将代码写入文件。随后使用命令行调用插桩程序,对用户的代码进行插桩。然后再通过命令行运行插桩后的代码并连接jvmti agent,最后得到事件文件和GCDetails日志文件。
GET /analysis/{id}
获取分析结果接口。
对日志进行分析,将分析结果返回给前端。