Contents
  1. 1. 暑期任务
  2. 2. 工作汇报
    1. 2.1. 后端实现
      1. 2.1.1. 第一个想法
      2. 2.1.2. 第二个想法
      3. 2.1.3. 想法二的实现过程记录

对暑期大创的回忆总结。

暑期任务

首先以线上的形式确定了任务目标,任务安排与管理方式。

任务目标:

  • 实现一个可视化jvm垃圾回收的小Demo,要拥有基本功能。
  • 针对普遍使用的jdk1.8

管理方式:

  • 一周开一次组会,介绍一周的工作内容,遇到的问题,和下周的工作目标,并将以上三项内容整理成文档提交。

任务安排:

  • 首先学习jvm的基本知识,参考《深入理解Java虚拟机》或其他资料。耗时3~4周。
  • 前后端分工,并行完成任务。耗时3~4周。
    • 前端需求:
      1. 用户可以上传代码
      2. 拿到后端分析结果后进行动画演示。
      3. 动画详细到Eden,S0,S1,Old四个代。包括对象的创建、移动、销毁。
    • 后端需求:
      1. 分析出代码执行时,堆的信息。包括:对象的创建,GC的发生过程,堆的各代占用空间的大小。
      2. 对象的创建事件要包含如下信息:对象的名称、大小、类型、分配在哪个代。
      3. GC的发生过程要包含如下信息:回收了哪些对象,存活的对象的移动。

工作汇报

后端实现

​ 实现思路:将用户的代码运行一遍,当发生如:实例创建、垃圾回收时,将需要的信息输出,整理加工后返回给前端。

第一个想法

​ 下载openjdk的源代码,修改相关事件的代码,将信息输出。在ubuntu系统的虚拟机上成功配置了openjdk环境,经过测试验证了这种方式在理论上是可行的,后来因为能力有限,源代码阅读困难而放弃。

第二个想法

​ 了解到了jvmti这个工具,打算通过agent的方式,实现一个jvm虚拟机的代理。通过使用jvmti来设置虚拟机一些事件的监听,来完成所需信息的输出。通过阅读Jvmti的官方文档和翻译文档,以及阅读openjdk中提供的jvmti demo的代码,这个想法逐渐被实现。

想法二的实现过程记录

​ 需要解决的问题有两个:获取实例的创建信息,获取GC的相关信息。因为项目为垃圾回收可视化,故将垃圾回收的信息作为重点问题。为了显示GC的信息,我们需要拿到GC之前有哪些对象,他们分别在哪里,他们在GC后有哪些依旧存活,哪些被销毁,GC后都在哪个代中。

  • 方案一

    预期使用FollowReferencesIterateThroughHeap来获取堆中有哪些对象,并获取对象的一些信息。并监听GarbageCollectionStartGarbageCollectionFinish来确定信息的输出时机。

​ 因为JVMTI的规定,这两个GC事件比较特殊,可以在函数体内调用的函数有限,其中并不包括FollowReferencesIterateThroughHeap函数,只能启动新的线程来完成该任务。但是在测试时,windows系统中可以运行,而在ubuntu16系统中会发生fatal error,寻找错误原因将近两周也没有找到,最终放弃。

  • 方案二

    • 关于jvmti agent

    ​ 将可视化需要的数据进行分类,每类作为一种事件,在agent运行时将事件输出,而后对事件信息进行分析处理返回给前端。以下是agent输出的几种事件:

    1. 对象的创建

      ​ 借鉴于openjdk中的jvmti demo,heapTracker,决定使用native函数结合字节码增强的方式,每次创建对象都会调用native函数,将每次对象创建时的一些信息传入到jvmti agent中,并通过jvmti的setTag函数,将一些信息标记到对象上。

      • 获取对象的相关信息

      ​ 通过jvmti提供的GetObjectClassGetClassSignatureGetObjectSize函数分别获取对象的类签名、对象大小。借鉴Unsafe类获取对象地址的源码,实现获取对象地址的方法,从而结合GC日志判断对象所处的分代。

    2. 对象的回收

      ​ 通过设置事件ObjectFree的监听,并通过tag来获取对象信息,从而实现创建的对象和销毁的对象的对应关系。

    3. GC事件的发生

      ​ 只输出事件开始的标记,结合GCDetails日志进行分析。

    4. GC事件的结束

      ​ 只输出事件结束的标记,结合GCDetails日志进行分析。

    • 关于插桩方案

      ​ 使用javassist工具库进行插桩。找到所有含有new关键字的代码行,并在其后插入写好的静态native函数。

      为了防止插桩影响GC的发生时机,将函数声明为静态函数。在jdk1.8环境下并不会影响堆内存。

    • 后端程序

      ​ 后端程序使用springboot来完成。项目中存有插桩程序的jar文件,和jvmti的agent扩展程序文件。

      ​ 服务器提供两个接口:

      • POST /analysis/codes

        上传代码接口。

        ​ 服务器接受到代码后将代码写入文件。随后使用命令行调用插桩程序,对用户的代码进行插桩。然后再通过命令行运行插桩后的代码并连接jvmti agent,最后得到事件文件和GCDetails日志文件。

      • GET /analysis/{id}

        获取分析结果接口。

        ​ 对日志进行分析,将分析结果返回给前端。

Contents
  1. 1. 暑期任务
  2. 2. 工作汇报
    1. 2.1. 后端实现
      1. 2.1.1. 第一个想法
      2. 2.1.2. 第二个想法
      3. 2.1.3. 想法二的实现过程记录