Skip to content

Commit feeb996

Browse files
committed
memviz
1 parent a4ea9e7 commit feeb996

11 files changed

Lines changed: 2709 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(mvn:*)",
5+
"WebFetch(domain:javadoc.io)",
6+
"WebFetch(domain:github.com)",
7+
"Bash(timeout:*)",
8+
"Bash(ping:*)",
9+
"Bash(curl:*)"
10+
],
11+
"deny": []
12+
}
13+
}

springboot-memviz/pom.xml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
5+
http://maven.apache.org/xsd/maven-4.0.0.xsd">
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<groupId>com.example</groupId>
9+
<artifactId>springtboot-memviz</artifactId>
10+
<version>0.0.1-SNAPSHOT</version>
11+
12+
<properties>
13+
<java.version>17</java.version>
14+
<spring-boot.version>3.3.2</spring-boot.version>
15+
</properties>
16+
17+
<dependencyManagement>
18+
<dependencies>
19+
<dependency>
20+
<groupId>org.springframework.boot</groupId>
21+
<artifactId>spring-boot-dependencies</artifactId>
22+
<version>${spring-boot.version}</version>
23+
<type>pom</type>
24+
<scope>import</scope>
25+
</dependency>
26+
</dependencies>
27+
</dependencyManagement>
28+
29+
<dependencies>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-web</artifactId>
33+
</dependency>
34+
35+
<!-- 轻量 HPROF 解析器(GridKit jvmtool) -->
36+
<dependency>
37+
<groupId>org.gridkit.jvmtool</groupId>
38+
<artifactId>hprof-heap</artifactId>
39+
<version>0.16</version>
40+
</dependency>
41+
42+
<!-- 可选:更漂亮的 JSON(日志/调试用) -->
43+
<dependency>
44+
<groupId>com.fasterxml.jackson.core</groupId>
45+
<artifactId>jackson-databind</artifactId>
46+
</dependency>
47+
48+
<dependency>
49+
<groupId>cn.hutool</groupId>
50+
<artifactId>hutool-all</artifactId>
51+
<version>5.8.16</version>
52+
</dependency>
53+
</dependencies>
54+
55+
<build>
56+
<plugins>
57+
<plugin>
58+
<groupId>org.apache.maven.plugins</groupId>
59+
<artifactId>maven-compiler-plugin</artifactId>
60+
<version>3.11.0</version>
61+
<configuration>
62+
<source>${java.version}</source>
63+
<target>${java.version}</target>
64+
<parameters>true</parameters>
65+
</configuration>
66+
</plugin>
67+
68+
<plugin>
69+
<groupId>org.springframework.boot</groupId>
70+
<artifactId>spring-boot-maven-plugin</artifactId>
71+
<version>${spring-boot.version}</version>
72+
<executions>
73+
<execution>
74+
<goals>
75+
<goal>repackage</goal>
76+
</goals>
77+
</execution>
78+
</executions>
79+
</plugin>
80+
</plugins>
81+
</build>
82+
</project>
83+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.example.memviz;
2+
3+
import cn.hutool.core.util.RandomUtil;
4+
import com.example.memviz.model.GraphModel;
5+
import com.example.memviz.model.User;
6+
import org.springframework.boot.SpringApplication;
7+
import org.springframework.boot.autoconfigure.SpringBootApplication;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
@SpringBootApplication
13+
public class MemvizApplication {
14+
// 用于测试
15+
public static List<String> largeStrings = new ArrayList<>();
16+
17+
public static void main(String[] args) {
18+
// Create 100 1MB strings and store them properly
19+
for(int i = 0; i < 100; i++){
20+
String largeString = RandomUtil.randomString(1024 * 1024); // 1MB each
21+
largeStrings.add(largeString); // Store so they won't be GC'd
22+
}
23+
24+
// 创建测试User对象,验证深度大小计算
25+
User.createTestUsers();
26+
27+
SpringApplication.run(MemvizApplication.class, args);
28+
}
29+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.example.memviz.controller;
2+
3+
import com.example.memviz.model.GraphModel;
4+
import com.example.memviz.service.HeapDumpService;
5+
import com.example.memviz.service.HprofParseService;
6+
import org.springframework.http.MediaType;
7+
import org.springframework.util.StringUtils;
8+
import org.springframework.web.bind.annotation.*;
9+
10+
import java.io.File;
11+
import java.nio.file.Path;
12+
import java.util.Map;
13+
import java.util.function.Predicate;
14+
15+
@RestController
16+
@RequestMapping("/memviz")
17+
public class MemvizController {
18+
19+
private final HeapDumpService dumpService;
20+
private final HprofParseService parseService;
21+
22+
public MemvizController(HeapDumpService dumpService, HprofParseService parseService) {
23+
this.dumpService = dumpService;
24+
this.parseService = parseService;
25+
}
26+
27+
/**
28+
* 触发一次快照,返回文件名(安全:默认 live=false)
29+
*/
30+
@PostMapping("/snapshot")
31+
public Map<String, String> snapshot(@RequestParam(defaultValue = "false") boolean live,
32+
@RequestParam(defaultValue = "/tmp/memviz") String dir) throws Exception {
33+
File f = dumpService.dump(live, new File(dir));
34+
return Map.of("file", f.getAbsolutePath());
35+
}
36+
37+
/**
38+
* 解析指定快照 → 图模型(支持过滤&折叠)
39+
*/
40+
@GetMapping(value = "/graph", produces = MediaType.APPLICATION_JSON_VALUE)
41+
public GraphModel graph(@RequestParam String file,
42+
@RequestParam(required = false) String include, // 例如: com.myapp.,java.util.HashMap
43+
@RequestParam(defaultValue = "true") boolean collapseCollections) throws Exception {
44+
45+
Predicate<String> filter = null;
46+
if (StringUtils.hasText(include)) {
47+
String[] prefixes = include.split(",");
48+
filter = fqcn -> {
49+
// 总是包含重要的基础类,以便显示大对象
50+
if (fqcn.equals("java.lang.String") || fqcn.equals("byte[]") ||
51+
fqcn.startsWith("java.lang.String[") || fqcn.startsWith("java.util.ArrayList")) {
52+
return true;
53+
}
54+
// 检查用户指定的前缀
55+
for (String p : prefixes) if (fqcn.startsWith(p.trim())) return true;
56+
return false;
57+
};
58+
}
59+
return parseService.parseToGraph(new File(file), filter, collapseCollections);
60+
}
61+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package com.example.memviz.model;
2+
3+
import cn.hutool.core.util.RandomUtil;
4+
5+
import java.util.*;
6+
7+
public class GraphModel {
8+
public static class Node {
9+
public String id; // objectId 或 class@id
10+
public String label; // 类名(短)
11+
public String className; // 类名(全)
12+
public long shallowSize; // 浅表大小
13+
public long deepSize; // 深度大小
14+
public String category; // JDK/第三方/业务
15+
public int instanceCount; // 该类的实例总数
16+
public String formattedSize; // 格式化的浅表大小显示
17+
public String formattedDeepSize; // 格式化的深度大小显示
18+
public String packageName; // 包名
19+
public boolean isArray; // 是否为数组类型
20+
public String objectType; // 对象类型描述
21+
public List<FieldInfo> fields; // 对象的字段信息
22+
23+
public Node(String id, String label, String className, long shallowSize, String category) {
24+
this.id = id;
25+
this.label = label;
26+
this.className = className;
27+
this.shallowSize = shallowSize;
28+
this.category = category;
29+
this.fields = new ArrayList<>();
30+
}
31+
32+
// 增强构造函数
33+
public Node(String id, String label, String className, long shallowSize, String category,
34+
int instanceCount, String formattedSize, String packageName, boolean isArray, String objectType,
35+
long deepSize, String formattedDeepSize) {
36+
this.id = id;
37+
this.label = label;
38+
this.className = className;
39+
this.shallowSize = shallowSize;
40+
this.deepSize = deepSize;
41+
this.category = category;
42+
this.instanceCount = instanceCount;
43+
this.formattedSize = formattedSize;
44+
this.formattedDeepSize = formattedDeepSize;
45+
this.packageName = packageName;
46+
this.isArray = isArray;
47+
this.objectType = objectType;
48+
this.fields = new ArrayList<>();
49+
}
50+
}
51+
52+
public static class Link {
53+
public String source;
54+
public String target;
55+
public String field; // 通过哪个字段/元素引用
56+
57+
public Link(String s, String t, String field) {
58+
this.source = s;
59+
this.target = t;
60+
this.field = field;
61+
}
62+
}
63+
64+
// Top100类统计信息
65+
public static class TopClassStat {
66+
public String className;
67+
public String shortName;
68+
public String packageName;
69+
public String category;
70+
public int instanceCount; // 实例数量
71+
public long totalSize; // 该类所有实例的总内存(浅表大小)
72+
public String formattedTotalSize; // 格式化的总内存
73+
public long totalDeepSize; // 该类所有实例的总深度大小
74+
public String formattedTotalDeepSize; // 格式化的总深度大小
75+
public long avgSize; // 平均每个实例大小(浅表)
76+
public String formattedAvgSize; // 格式化的平均大小
77+
public long avgDeepSize; // 平均每个实例深度大小
78+
public String formattedAvgDeepSize; // 格式化的平均深度大小
79+
public int rank; // 排名
80+
public List<ClassInstance> topInstances; // 该类中内存占用最大的实例列表
81+
82+
public TopClassStat(String className, String shortName, String packageName, String category,
83+
int instanceCount, long totalSize, String formattedTotalSize,
84+
long totalDeepSize, String formattedTotalDeepSize,
85+
long avgSize, String formattedAvgSize,
86+
long avgDeepSize, String formattedAvgDeepSize,
87+
int rank, List<ClassInstance> topInstances) {
88+
this.className = className;
89+
this.shortName = shortName;
90+
this.packageName = packageName;
91+
this.category = category;
92+
this.instanceCount = instanceCount;
93+
this.totalSize = totalSize;
94+
this.formattedTotalSize = formattedTotalSize;
95+
this.totalDeepSize = totalDeepSize;
96+
this.formattedTotalDeepSize = formattedTotalDeepSize;
97+
this.avgSize = avgSize;
98+
this.formattedAvgSize = formattedAvgSize;
99+
this.avgDeepSize = avgDeepSize;
100+
this.formattedAvgDeepSize = formattedAvgDeepSize;
101+
this.rank = rank;
102+
this.topInstances = topInstances != null ? topInstances : new ArrayList<>();
103+
}
104+
}
105+
106+
// 类的实例信息
107+
public static class ClassInstance {
108+
public String id;
109+
public long size; // 浅表大小
110+
public String formattedSize; // 格式化的浅表大小
111+
public long retainedSize; // 深度大小(保留大小)
112+
public String formattedRetainedSize; // 格式化的深度大小
113+
public int rank; // 在该类中的排名
114+
public String packageName; // 包名
115+
public String objectType; // 对象类型
116+
public boolean isArray; // 是否数组
117+
public double sizePercentInClass; // 在该类中的内存占比
118+
public List<FieldInfo> fields; // 添加字段信息列表
119+
120+
public ClassInstance(String id, long size, String formattedSize,
121+
long retainedSize, String formattedRetainedSize, int rank,
122+
String packageName, String objectType, boolean isArray, double sizePercentInClass) {
123+
this.id = id;
124+
this.size = size;
125+
this.formattedSize = formattedSize;
126+
this.retainedSize = retainedSize;
127+
this.formattedRetainedSize = formattedRetainedSize;
128+
this.rank = rank;
129+
this.packageName = packageName;
130+
this.objectType = objectType;
131+
this.isArray = isArray;
132+
this.sizePercentInClass = sizePercentInClass;
133+
this.fields = new ArrayList<>();
134+
}
135+
}
136+
137+
public List<Node> nodes = new ArrayList<>();
138+
public List<Link> links = new ArrayList<>();
139+
public List<TopClassStat> top100Classes = new ArrayList<>(); // Top100类统计列表
140+
public int totalObjects; // 总对象数
141+
public long totalMemory; // 总内存占用
142+
public String formattedTotalMemory; // 格式化的总内存
143+
144+
// 字段信息类,用于存储对象的属性信息
145+
public static class FieldInfo {
146+
public String name; // 字段名称
147+
public String type; // 字段类型
148+
public String value; // 字段值(字符串表示)
149+
public long size; // 字段浅表大小(字节)
150+
public String formattedSize; // 格式化的浅表大小
151+
public long retainedSize; // 字段深度大小(保留大小)
152+
public String formattedRetainedSize; // 格式化的深度大小
153+
public double sizePercent; // 在对象中的占比(基于浅表大小)
154+
public double retainedSizePercent; // 在对象中的深度大小占比
155+
public boolean isPrimitive; // 是否为基本类型
156+
public boolean isReference; // 是否为引用类型
157+
158+
public FieldInfo(String name, String type, String value, long size,
159+
String formattedSize, long retainedSize, String formattedRetainedSize,
160+
double sizePercent, double retainedSizePercent,
161+
boolean isPrimitive, boolean isReference) {
162+
this.name = name;
163+
this.type = type;
164+
this.value = value;
165+
this.size = size;
166+
this.formattedSize = formattedSize;
167+
this.retainedSize = retainedSize;
168+
this.formattedRetainedSize = formattedRetainedSize;
169+
this.sizePercent = sizePercent;
170+
this.retainedSizePercent = retainedSizePercent;
171+
this.isPrimitive = isPrimitive;
172+
this.isReference = isReference;
173+
}
174+
175+
// 获取字段浅表大小
176+
public long getSize() {
177+
return size;
178+
}
179+
180+
// 获取字段深度大小
181+
public long getRetainedSize() {
182+
return retainedSize;
183+
}
184+
}
185+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example.memviz.model;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* 存储测试User对象的静态容器
8+
*/
9+
public class TestUserStorage {
10+
public static final List<User> users = new ArrayList<>();
11+
}

0 commit comments

Comments
 (0)