The Lightweight Java Visualizer (LJV)

Кто мы

Иван

Иван Пономарев:
- Разработчик ПО в KURS
- Преподаватель МФТИ

Олег

Олег Ивченко:
- Системный администратор инфраструктуры ШАД (Яндекс)
- Старший преподаватель АТП ФПМИ МФТИ

Кто мы

Илья

Илья Селиванов:
- Студент CSC и МФТИ

Нурас

Нурас Ногаев:
- Студент МФТИ

Идея

  • В режиме runtime построить внутреннее представление графа объектов памяти.

  • Полученную можно загрузить в онлайн-сервисы

Идея

Diagram

До нас

Инструмент был разработан Джоном Xамером в 2004 году на Java 1.4

John Hamer. “Visualising Java Data Structures as Graphs”

Проблемы

  • Неудобство пользования

  • Отсутствие поддержки инструмента

  • Изменение Graphviz

  • Отсутствие ряда возможностей

Решения

  • Обновление до Java 11

  • Изменение API инструмента (method chaining)

  • Выложили на GitHub

  • Выложили на Maven Central

  • Полностью изменена генерация графа

Зачем он нужен

  • Реверс инжиниринг кода

  • Преподавание

Как использовать

String graph = new LJV().drawGraph("Hello");
Diagram

Исходный код на языке DOT

digraph Java {
	rankdir="TB";
	node[shape=plaintext]
	n1[label=<
		<table border='0' cellborder='1' cellspacing='0'>
			<tr>
				<td rowspan='3'>String</td>
			</tr>
			<tr>
				<td>coder: 0</td>
			</tr>
			<tr>
				<td>hash: 0</td>
			</tr>
		</table>
	>];
	n2[label=<
		<table border='0' cellborder='1' cellspacing='0'>
			<tr>
				<td>72</td>
				<td>101</td>
				<td>108</td>
				<td>108</td>
				<td>111</td>
			</tr>
		</table>
	>];
	n1 -> n2[label="value",fontsize=12];
}

Настройки графа

Базовые типы Java можно представить в более удобном виде

String graph = new LJV()
  .setTreatAsPrimitive(String.class)
  .drawGraph(
     new Object[]{new String[]{"a", "b", "c"}, new int[]{1, 2, 3}}
  );
Diagram

Есть возможность перевернуть структуру

ArrayList<Object> a = new ArrayList<>();
        a.add(new Person("Albert", true, 35));
        a.add(new Person("Betty", false, 20));
        a.add(new java.awt.Point(100, -100));

String graph = new LJV()
        .setTreatAsPrimitive(String.class)
        .setDirection(Direction.LR)
        .drawGraph(a);
Diagram

Можно разукрасить отдельные части графа

Node n1 = new Node("A");
...
n1.right.right = n1;

String graph = new LJV()
    .addFieldAttribute("left", "color=red,fontcolor=red")
    .addFieldAttribute("right", "color=blue,fontcolor=blue")
    .addClassAttribute(Node.class, "color=pink,style=filled")
    .addIgnoreField("level")
    .setTreatAsPrimitive(String.class)
    .setShowFieldNamesInLabels(false).drawGraph(n1);
Diagram

Интернирование строк

String x = "Hello";
String y = x;
String graph = new LJV().drawGraph(new Object[]{x, y});
Diagram
String x = "Hello";
String y = new String(x);
String graph1 = new LJV().drawGraph(new Object[]{x, y});
Diagram
String x = "Hello";
String y = x + "";
String graph1 = new LJV().drawGraph(new Object[]{x, y});
Diagram
String x = "Hello";

String actualGraph = new LJV().drawGraph(
    new String[]{
        x,
        new String(x).intern(),
        new String(x.toCharArray()).intern(),
        (x + "").intern()
    }
);
Diagram

Кеширование примитивов

public class Example {
  private Integer i1 = 42;     private Integer i2 = 42;
  private Integer i3 = 2020;   private Integer i4 = 2020;
  private String s1 = "HelloWorld";
  private String s2 = "HELL O";
  private String s3 = "HelloWorld";
}
//...
String graph = new LJV().drawGraph(new Example());
Diagram

LinkedList

LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(1);
linkedList.add(42);
linkedList.add(21);

String graph = new LJV()
        .setTreatAsPrimitive(Integer.class)
        .addFieldAttribute("next", "color=red,fontcolor=red")
        .addFieldAttribute("prev", "color=blue,fontcolor=blue")
        .addFieldAttribute("first", "color=red,fontcolor=red")
        .addFieldAttribute("last", "color=red,fontcolor=red")
        .drawGraph(linkedList);
Diagram

ArrayDeque

ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(2);

String graph = new LJV().drawGraph(arrayDeque);
Diagram
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(2);
for (int i = 0; i < 4; i++) {
    arrayDeque.addLast(i);
}

String graph = new LJV()
        .setTreatAsPrimitive(Integer.class)
        .drawGraph(arrayDeque);
Diagram
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(2);
for (int i = 0; i < 20; i++) {
    arrayDeque.addLast(i);
}

String graph = new LJV()
        .setTreatAsPrimitive(Integer.class)
        .drawGraph(arrayDeque);
Diagram
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>(2);
for (int i = 0; i < 20; i++) {
    arrayDeque.addLast(i);
}
for (int i = 0; i < 18; i++) {
    arrayDeque.removeFirst();
}

String graph = new LJV()
        .setTreatAsPrimitive(Integer.class)
        .drawGraph(arrayDeque);
Diagram

Treemap

TreeMap<String, Integer> map = new TreeMap<>();
map.put("one", 1);    map.put("two", 2);
map.put("three", 3);  map.put("four", 4);
String actualGraph = new LJV()
    .setTreatAsPrimitive(Integer.class)
    .setTreatAsPrimitive(String.class)
    .setDirection(Direction.LR)
    .drawGraph(map);
Diagram

HashMap

HashMap<String, Integer> map = new HashMap<>();
map.put("one", 1);   map.put("two", 2);
map.put("three", 3); map.put("four", 4);
String graph = new LJV()
///...
    .drawGraph(map);
Diagram

Collision…​

Diagram

Collision with 13 elements and tree map

Diagram

Hash collision for god of hash collision

Diagram

ConcurrentSkipListMap

ConcurrentSkipListMap<String, Integer> map =
    new ConcurrentSkipListMap<>();

map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
map.put("four", 4);

String actualGraph = new LJV()
        .setTreatAsPrimitive(Integer.class)
        .setTreatAsPrimitive(String.class)
        .drawGraph(map);

First start

Diagram

Second start

Diagram

Итоги

github
github