第11章:资源管理 - 内存泄漏的隐形杀手
作者:步子哥 (steper@foxmail.com)
11.1 SWT 资源的特殊性
操作系统资源的本质
// 创建一个颜色
Color color = new Color(display, 255, 0, 0);
// 使用颜色
button.setBackground(color);
// 必须手动释放
color.dispose();
费曼解释:SWT 资源与 Java 对象的区别
SWT 资源 = 向图书馆借的书
- 你从图书馆借了一本书(创建资源)
- 你看完后,必须还书(释放资源)
- 不还书 = 图书馆的书越来越少(内存泄漏)
Java 对象 = 自己买的书
- 你买了一本书(创建对象)
- 你看完后,不需要还书(GC 自动回收)
- 不需要还 = 不会"泄漏"
GC 不会自动回收 SWT 资源
// 错误示例:不释放 SWT 资源
for (int i = 0; i < 1000; i++) {
Color color = new Color(display, 255, 0, 0); // 创建 1000 个颜色
button.setBackground(color);
// 没有释放!
// GC 不会回收这 1000 个颜色
// 结果:内存泄漏!
}
// 正确示例:释放 SWT 资源
for (int i = 0; i < 1000; i++) {
Color color = new Color(display, 255, 0, 0); // 创建颜色
button.setBackground(color);
color.dispose(); // 释放颜色!
// 释放后,资源可以被复用
// 结果:没有泄漏
}
费曼解释:为什么 GC 不会回收 SWT 资源?
GC = Java 的垃圾回收器
- GC 只回收 Java 对象
- SWT 资源 = 操作系统资源
- GC 不知道如何回收操作系统资源
类比:
GC = 扫地机器人
- GC 只扫 Java 对象的"垃圾"
- SWT 资源 = 图书馆的书
- 扫地机器人不知道"还书"怎么做
- 必须手动"还书"(dispose)
代码示例:SWT 资源 vs Java 对象
public class ResourceExample {
private static int javaObjectCount = 0;
private static int swtResourceCount = 0;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("SWT 资源示例");
shell.setLayout(new GridLayout(1, false));
// 按钮:创建 Java 对象
Button javaButton = new Button(shell, SWT.PUSH);
javaButton.setText("创建 Java 对象(不手动释放)");
javaButton.addListener(SWT.Selection, event -> {
for (int i = 0; i < 1000; i++) {
// 创建 Java 对象
String str = new String("Java 对象 " + i);
// 不需要手动释放,GC 会自动回收
}
javaObjectCount += 1000;
System.out.println("创建了 " + javaObjectCount + " 个 Java 对象");
});
// 按钮:创建 SWT 资源(错误)
Button wrongButton = new Button(shell, SWT.PUSH);
wrongButton.setText("创建 SWT 资源(不释放)");
wrongButton.addListener(SWT.Selection, event -> {
for (int i = 0; i < 100; i++) {
// 创建 SWT 资源(错误:不释放)
Color color = new Color(display, 255, 0, 0);
// 应该释放:color.dispose();
}
swtResourceCount += 100;
System.out.println("创建了 " + swtResourceCount + " 个 SWT 资源(内存泄漏!)");
});
// 按钮:创建 SWT 资源(正确)
Button correctButton = new Button(shell, SWT.PUSH);
correctButton.setText("创建 SWT 资源(正确释放)");
correctButton.addListener(SWT.Selection, event -> {
for (int i = 0; i < 100; i++) {
// 创建 SWT 资源(正确:释放)
Color color = new Color(display, 255, 0, 0);
// 使用后释放
color.dispose();
}
swtResourceCount += 100;
System.out.println("创建了 " + swtResourceCount + " 个 SWT 资源(正确释放)");
});
// 按钮:触发 GC
Button gcButton = new Button(shell, SWT.PUSH);
gcButton.setText("触发 GC");
gcButton.addListener(SWT.Selection, event -> {
System.out.println("触发 GC...");
System.gc(); // 触发 GC
System.out.println("GC 完成");
});
// 按钮:查看内存
Button memoryButton = new Button(shell, SWT.PUSH);
memoryButton.setText("查看内存");
memoryButton.addListener(SWT.Selection, event -> {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
System.out.println("总内存:" + totalMemory / 1024 / 1024 + " MB");
System.out.println("空闲内存:" + freeMemory / 1024 / 1024 + " MB");
System.out.println("已用内存:" + usedMemory / 1024 / 1024 + " MB");
});
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
11.2 需要释放的资源类型
Color、Font、Image、Cursor
// Color:必须释放
Color color = new Color(display, 255, 0, 0);
button.setBackground(color);
color.dispose();
// Font:必须释放
Font font = new Font(display, "Arial", 12, SWT.BOLD);
label.setFont(font);
font.dispose();
// Image:必须释放
Image image = new Image(display, "path/to/image.png");
label.setImage(image);
image.dispose();
// Cursor:必须释放
Cursor cursor = new Cursor(display, SWT.CURSOR_HAND);
shell.setCursor(cursor);
cursor.dispose();
费曼解释:为什么这些资源需要释放?
Color、Font、Image、Cursor = 图书馆的书、碟片、磁带
- 这些都是"有限的资源"
- 图书馆只有一定数量的书、碟片、磁带
- 你借了不还,别人就无法借到
- 内存泄漏 = 图书馆的资源越来越少
类比:
Color = 图书馆的书
- 你借了一本书(创建 Color)
- 看完后,必须还书(dispose)
- 不还书 = 别人无法借到这本书
- 借多了不还 = 图书馆的书越来越"少"(内存泄漏)
GC、Path、Pattern
// GC:必须释放
GC gc = new GC(canvas);
gc.drawLine(0, 0, 100, 100);
gc.dispose();
// Path:必须释放
Path path = new Path(display);
path.addArc(0, 0, 100, 100, 0, 360);
gc.drawPath(path);
path.dispose();
// Pattern:必须释放
Pattern pattern = new Pattern(display, 0, 0, 100, 0,
display.getSystemColor(SWT.COLOR_WHITE),
display.getSystemColor(SWT.COLOR_BLACK));
gc.setBackgroundPattern(pattern);
pattern.dispose();
费曼解释:为什么绘图资源需要释放?
GC、Path、Pattern = 绘图工具
- GC = 画笔
- Path = 画板
- Pattern = 调色板
- 这些工具也是"有限的资源"
- 用完后,必须放回工具箱(dispose)
类比:
GC、Path、Pattern = 餐厅的餐具
- GC = 筷子
- Path = 盘子
- Pattern = 杯子
- 这些餐具也是"有限的资源"
- 用完后,必须放回消毒柜(dispose)
其他设备相关资源
// Device:通常不需要手动释放(Display 会自动管理)
// 但是,如果你创建了额外的 Device,需要释放
Device device = new Device();
// ... 使用 device ...
device.dispose();
// Printer:需要释放
Printer printer = new Printer();
PrinterData printerData = printer.getDefaultPrinterData();
printer.startJob(printerData);
printer.endJob();
printer.dispose();
代码示例:需要释放的资源
public class ResourceTypesExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("资源类型示例");
shell.setLayout(new GridLayout(1, false));
// 按钮:测试 Color
Button colorButton = new Button(shell, SWT.PUSH);
colorButton.setText("测试 Color");
colorButton.addListener(SWT.Selection, event -> {
// 正确:创建后释放
Color color = new Color(display, 255, 0, 0);
colorButton.setBackground(color);
color.dispose();
System.out.println("Color 已释放");
});
// 按钮:测试 Font
Button fontButton = new Button(shell, SWT.PUSH);
fontButton.setText("测试 Font");
fontButton.addListener(SWT.Selection, event -> {
// 正确:创建后释放
Font font = new Font(display, "Arial", 16, SWT.BOLD);
fontButton.setFont(font);
font.dispose();
System.out.println("Font 已释放");
});
// 按钮:测试 GC
Button gcButton = new Button(shell, SWT.PUSH);
gcButton.setText("测试 GC");
gcButton.addListener(SWT.Selection, event -> {
Canvas canvas = new Canvas(shell, SWT.BORDER);
canvas.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
canvas.addPaintListener(event -> {
// 正确:创建后释放
GC gc = event.gc;
gc.drawLine(0, 0, 100, 100);
gc.dispose();
});
System.out.println("GC 已释放");
});
// 按钮:测试 Image
Button imageButton = new Button(shell, SWT.PUSH);
imageButton.setText("测试 Image");
imageButton.addListener(SWT.Selection, event -> {
// 注意:实际应用中应该从文件加载
// Image image = new Image(display, "path/to/image.png");
// label.setImage(image);
// image.dispose();
System.out.println("Image 使用示例(需要从文件加载)");
});
// 按钮:测试 Cursor
Button cursorButton = new Button(shell, SWT.PUSH);
cursorButton.setText("测试 Cursor");
cursorButton.addListener(SWT.Selection, event -> {
// 正确:创建后释放
Cursor cursor = new Cursor(display, SWT.CURSOR_HAND);
shell.setCursor(cursor);
cursor.dispose();
System.out.println("Cursor 已释放");
});
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
11.3 资源释放的最佳实践
dispose() 的调用时机
// 方案 1:使用后立即释放
Color color = new Color(display, 255, 0, 0);
button.setBackground(color);
color.dispose(); // 使用后立即释放
// 方案 2:在控件销毁时释放
button.addDisposeListener(event -> {
color.dispose(); // 控件销毁时释放
});
// 方案 3:在 Shell 销毁时释放
shell.addDisposeListener(event -> {
color.dispose(); // Shell 销毁时释放
});
// 方案 4:在 Display 销毁时释放
// Display 销毁时,会自动释放所有控件
// 但是,Color、Font、Image 不会自动释放
// 必须手动释放
费曼解释:何时释放资源?
释放资源 = 还书
- 读完书后,立即还书(立即释放)
- 或者在离开图书馆前还书(控件销毁时释放)
- 或者在图书馆闭馆时还书(Shell 销毁时释放)
- 或者在图书馆倒闭时还书(Display 销毁时释放)
类比:
释放资源 = 使用工具
- 用完工具后,立即放回工具箱(立即释放)
- 或者在下班前放回工具箱(控件销毁时释放)
- 或者在店铺关门时放回工具箱(Shell 销毁时释放)
- 或者在店铺倒闭时放回工具箱(Display 销毁时释放)
Widget.addDisposeListener()
// 创建颜色
Color color = new Color(display, 255, 0, 0);
// 创建按钮
Button button = new Button(shell, SWT.PUSH);
button.setText("点击我");
// 使用颜色
button.setBackground(color);
// 添加释放监听器
button.addDisposeListener(event -> {
color.dispose(); // 按钮销毁时,释放颜色
});
费曼解释:addDisposeListener 的作用
addDisposeListener = 记备忘录
- 你在备忘录上写下:"按钮销毁时,释放颜色"
- 当按钮销毁时,SWT 自动执行备忘录上的任务
- 你不用记着"什么时候释放颜色"
类比:
addDisposeListener = 设置闹钟
- 你设置闹钟:"按钮销毁时,响铃(释放颜色)"
- 当按钮销毁时,闹钟自动响铃
- 你不用记着"什么时候释放颜色"
资源池(ResourceRegistry)模式
// 资源池:管理资源的创建和释放
class ResourceRegistry {
private List<Resource> resources = new ArrayList<>();
public Color getColor(int red, int green, int blue) {
Color color = new Color(Display.getCurrent(), red, green, blue);
resources.add(color);
return color;
}
public Font getFont(String name, int height, int style) {
Font font = new Font(Display.getCurrent(), name, height, style);
resources.add(font);
return font;
}
public void dispose() {
for (Resource resource : resources) {
if (!resource.isDisposed()) {
resource.dispose();
}
}
resources.clear();
}
}
// 使用资源池
ResourceRegistry registry = new ResourceRegistry();
Color color = registry.getColor(255, 0, 0);
Font font = registry.getFont("Arial", 12, SWT.BOLD);
// 在应用关闭时,释放所有资源
shell.addDisposeListener(event -> {
registry.dispose();
});
费曼解释:资源池的原理
资源池 = 工具箱
- 所有工具都放在工具箱里
- 你需要工具,从工具箱里拿
- 用完后,不用立即放回
- 在下班前,统一把所有工具放回工具箱
类比:
资源池 = 餐厅的餐具
- 所有餐具都放在餐具柜
- 你需要餐具,从餐具柜里拿
- 用完后,不用立即放回
- 在餐厅打烊前,统一把所有餐具放回餐具柜
代码示例:资源管理最佳实践
public class ResourceManagementExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("资源管理最佳实践");
shell.setLayout(new GridLayout(1, false));
// 方案 1:使用后立即释放
Button immediateButton = new Button(shell, SWT.PUSH);
immediateButton.setText("立即释放");
immediateButton.addListener(SWT.Selection, event -> {
Color color = new Color(display, 255, 0, 0);
immediateButton.setBackground(color);
color.dispose(); // 使用后立即释放
System.out.println("立即释放:成功");
});
// 方案 2:在控件销毁时释放
Button disposeListenerButton = new Button(shell, SWT.PUSH);
disposeListenerButton.setText("控件销毁时释放");
Color disposeListenerColor = new Color(display, 0, 255, 0);
disposeListenerButton.setBackground(disposeListenerColor);
disposeListenerButton.addDisposeListener(event -> {
disposeListenerColor.dispose(); // 控件销毁时释放
System.out.println("控件销毁时释放:成功");
});
// 方案 3:使用资源池
Button poolButton = new Button(shell, SWT.PUSH);
poolButton.setText("使用资源池");
ResourceRegistry registry = new ResourceRegistry();
Color poolColor = registry.getColor(0, 0, 255);
poolButton.setBackground(poolColor);
poolButton.addListener(SWT.Selection, event -> {
Font poolFont = registry.getFont("Arial", 16, SWT.BOLD);
poolButton.setFont(poolFont);
System.out.println("资源池:添加了资源");
});
// 在 Shell 销毁时,释放资源池的所有资源
shell.addDisposeListener(event -> {
registry.dispose();
System.out.println("资源池:所有资源已释放");
});
// 按钮:关闭窗口
Button closeButton = new Button(shell, SWT.PUSH);
closeButton.setText("关闭窗口");
closeButton.addListener(SWT.Selection, event -> {
shell.dispose();
});
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
// 资源池
static class ResourceRegistry {
private List<Resource> resources = new ArrayList<>();
public Color getColor(int red, int green, int blue) {
Color color = new Color(Display.getCurrent(), red, green, blue);
resources.add(color);
return color;
}
public Font getFont(String name, int height, int style) {
Font font = new Font(Display.getCurrent(), name, height, style);
resources.add(font);
return font;
}
public Image getImage(String path) {
try {
Image image = new Image(Display.getCurrent(), path);
resources.add(image);
return image;
} catch (Exception e) {
return null;
}
}
public void dispose() {
for (Resource resource : resources) {
if (!resource.isDisposed()) {
resource.dispose();
}
}
resources.clear();
}
}
}
11.4 常见泄漏场景
Color、Font 的反复创建
// 错误示例:在循环中反复创建 Color
for (int i = 0; i < 1000; i++) {
Color color = new Color(display, 255, 0, 0); // 创建 1000 个红色
button.setBackground(color);
// 没有释放!
}
// 内存泄漏!
// 正确示例:只创建一次 Color
Color red = new Color(display, 255, 0, 0); // 只创建一个红色
for (int i = 0; i < 1000; i++) {
button.setBackground(red); // 复用同一个颜色
}
red.dispose(); // 最后释放
费曼解释:为什么反复创建会导致泄漏?
反复创建 = 反复借书
- 你借了一本书,不还(创建 Color 1)
- 你又借了一本书,不还(创建 Color 2)
- 你又借了一本书,不还(创建 Color 3)
- ...
- 图书馆的书越来越少(内存泄漏)
类比:
反复创建 = 餐厅反复拿餐具
- 你拿了一双筷子,不还
- 你又拿了一双筷子,不还
- 你又拿了一双筷子,不还
- ...
- 餐厅的筷子越来越少(资源泄漏)
Image 未释放
// 错误示例:加载多个 Image,不释放
for (int i = 0; i < 1000; i++) {
Image image = new Image(display, "image" + i + ".png"); // 加载 1000 张图片
label.setImage(image);
// 没有释放!
}
// 内存泄漏!
// 正确示例:使用后释放
for (int i = 0; i < 1000; i++) {
Image image = new Image(display, "image" + i + ".png");
label.setImage(image);
image.dispose(); // 释放!
}
费曼解释:为什么 Image 未释放会泄漏?
Image 未释放 = 照片占相册
- 你拍了 1000 张照片(加载 1000 张 Image)
- 不删除旧照片(不释放旧 Image)
- 相册满了(内存满了)
- 无法再拍照(无法再加载 Image)
类比:
Image 未释放 = 电脑硬盘满了
- 你下载了 1000 个文件(加载 1000 张 Image)
- 不删除旧文件(不释放旧 Image)
- 硬盘满了(内存满了)
- 无法再下载文件(无法再加载 Image)
GC 未 dispose
// 错误示例:在循环中反复创建 GC,不释放
for (int i = 0; i < 1000; i++) {
GC gc = new GC(canvas);
gc.drawLine(0, 0, 100, 100);
// 没有释放!
}
// 内存泄漏!
// 正确示例:使用后释放
GC gc = new GC(canvas);
for (int i = 0; i < 1000; i++) {
gc.drawLine(0, 0, 100, 100);
}
gc.dispose(); // 只创建一次 GC,最后释放
费曼解释:为什么 GC 未释放会泄漏?
GC 未释放 = 画笔不归还
- 你拿了一支画笔,不还(创建 GC 1)
- 你又拿了一支画笔,不还(创建 GC 2)
- 你又拿了一支画笔,不还(创建 GC 3)
- ...
- 画笔架满了(内存泄漏)
类比:
GC 未释放 = 筷子不归还
- 你拿了一双筷子,不还(创建 GC 1)
- 你又拿了一双筷子,不还(创建 GC 2)
- 你又拿了一双筷子,不还(创建 GC 3)
- ...
- 餐厅的筷子越来越少(资源泄漏)
代码示例:常见泄漏场景
public class LeakScenariosExample {
private static int colorCount = 0;
private static int fontCount = 0;
private static int imageCount = 0;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("常见泄漏场景");
shell.setLayout(new GridLayout(1, false));
// 错误场景 1:Color 反复创建
Button colorLeakButton = new Button(shell, SWT.PUSH);
colorLeakButton.setText("Color 泄漏(错误)");
colorLeakButton.addListener(SWT.Selection, event -> {
for (int i = 0; i < 100; i++) {
Color color = new Color(display, 255, 0, 0);
colorLeakButton.setBackground(color);
// 错误:没有释放
}
colorCount += 100;
System.out.println("创建了 " + colorCount + " 个 Color(泄漏)");
});
// 正确场景 1:Color 复用
Button colorCorrectButton = new Button(shell, SWT.PUSH);
colorCorrectButton.setText("Color 复用(正确)");
colorCorrectButton.addListener(SWT.Selection, event -> {
Color red = new Color(display, 255, 0, 0);
for (int i = 0; i < 100; i++) {
colorCorrectButton.setBackground(red);
}
red.dispose();
System.out.println("只创建了 1 个 Color(正确)");
});
// 错误场景 2:Font 反复创建
Button fontLeakButton = new Button(shell, SWT.PUSH);
fontLeakButton.setText("Font 泄漏(错误)");
fontLeakButton.addListener(SWT.Selection, event -> {
for (int i = 0; i < 100; i++) {
Font font = new Font(display, "Arial", 12, SWT.BOLD);
fontLeakButton.setFont(font);
// 错误:没有释放
}
fontCount += 100;
System.out.println("创建了 " + fontCount + " 个 Font(泄漏)");
});
// 正确场景 2:Font 复用
Button fontCorrectButton = new Button(shell, SWT.PUSH);
fontCorrectButton.setText("Font 复用(正确)");
fontCorrectButton.addListener(SWT.Selection, event -> {
Font font = new Font(display, "Arial", 12, SWT.BOLD);
for (int i = 0; i < 100; i++) {
fontCorrectButton.setFont(font);
}
font.dispose();
System.out.println("只创建了 1 个 Font(正确)");
});
// 按钮:查看内存
Button memoryButton = new Button(shell, SWT.PUSH);
memoryButton.setText("查看内存");
memoryButton.addListener(SWT.Selection, event -> {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
System.out.println("总内存:" + totalMemory / 1024 / 1024 + " MB");
System.out.println("空闲内存:" + freeMemory / 1024 / 1024 + " MB");
System.out.println("已用内存:" + usedMemory / 1024 / 1024 + " MB");
});
shell.setBounds(100, 100, 500, 300);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
11.5 调试技巧
如何检测资源泄漏
// 方法 1:手动计数
class ResourceTracker {
private Map<Resource, StackTraceElement[]> resources = new HashMap<>();
public void track(Resource resource) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
resources.put(resource, stackTrace);
}
public void dispose(Resource resource) {
resources.remove(resource);
}
public void printLeaks() {
System.out.println("泄漏的资源:");
for (Map.Entry<Resource, StackTraceElement[]> entry : resources.entrySet()) {
System.out.println("资源:" + entry.getKey());
System.out.println("创建位置:");
for (StackTraceElement element : entry.getValue()) {
System.out.println(" " + element);
}
}
}
}
// 使用资源追踪器
ResourceTracker tracker = new ResourceTracker();
Color color = new Color(display, 255, 0, 0);
tracker.track(color); // 追踪资源
// 查看泄漏
tracker.printLeaks();
费曼解释:资源追踪器的原理
资源追踪器 = 图书馆管理员
- 你借了一本书(创建资源)
- 管理员记下:你什么时候借的、借的什么(记录堆栈)
- 你还书时,管理员划掉记录
- 查看泄漏 = 管理员查看未还的书单
类比:
资源追踪器 = 餐厅服务员
- 你拿了一双筷子(创建资源)
- 服务员记下:你什么时候拿的、拿的什么(记录堆栈)
- 你还筷子时,服务员划掉记录
- 查看泄漏 = 服务员查看未还的筷子单
SWT 资源跟踪工具
// 方法 2:使用 SWT 的资源跟踪
Display display = new Display();
// 启用资源跟踪
System.setProperty("org.eclipse.swt.graphics.resource", "true");
// 创建资源
Color color = new Color(display, 255, 0, 0);
// 查看资源信息
System.out.println("资源总数:" + display.getData("org.eclipse.swt.graphics.resource"));
费曼解释:SWT 资源跟踪工具的作用
SWT 资源跟踪工具 = 图书馆的电子系统
- 记录所有借书信息(资源信息)
- 可以查看:借了什么书、借了多久、什么时候借的
- 可以统计:总共借了多少本书(资源总数)
类比:
SWT 资源跟踪工具 = 餐厅的点餐系统
- 记录所有点餐信息(资源信息)
- 可以查看:点了什么菜、点了多少、什么时候点的
- 可以统计:总共点了多少菜(资源总数)
代码示例:调试资源泄漏
public class DebugLeaksExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("调试资源泄漏");
shell.setLayout(new GridLayout(1, false));
// 资源追踪器
ResourceTracker tracker = new ResourceTracker();
// 按钮:创建并追踪资源
Button trackButton = new Button(shell, SWT.PUSH);
trackButton.setText("创建并追踪资源");
trackButton.addListener(SWT.Selection, event -> {
Color color = new Color(display, 255, 0, 0);
tracker.track(color);
trackButton.setBackground(color);
System.out.println("创建了 Color,并追踪");
});
// 按钮:释放资源
Button releaseButton = new Button(shell, SWT.PUSH);
releaseButton.setText("释放资源");
releaseButton.addListener(SWT.Selection, event -> {
// 注意:这里无法释放之前创建的 color,因为 color 是局部变量
// 实际应用中应该把 color 存储在成员变量中
System.out.println("释放资源(示例:需要将 color 存储在成员变量中)");
});
// 按钮:查看泄漏
Button leakButton = new Button(shell, SWT.PUSH);
leakButton.setText("查看泄漏");
leakButton.addListener(SWT.Selection, event -> {
tracker.printLeaks();
});
shell.setBounds(100, 100, 500, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
// 资源追踪器
static class ResourceTracker {
private Map<Resource, StackTraceElement[]> resources = new HashMap<>();
public void track(Resource resource) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
resources.put(resource, stackTrace);
}
public void dispose(Resource resource) {
resources.remove(resource);
if (!resource.isDisposed()) {
resource.dispose();
}
}
public void printLeaks() {
System.out.println("泄漏的资源:");
for (Map.Entry<Resource, StackTraceElement[]> entry : resources.entrySet()) {
System.out.println("资源:" + entry.getKey());
System.out.println("创建位置:");
for (StackTraceElement element : entry.getValue()) {
System.out.println(" " + element);
}
}
}
}
}
11.6 本章小结
资源管理总结
| 概念 | 作用 | 类比 |
|---|---|---|
| SWT 资源 | 操作系统资源 | 图书馆的书 |
| dispose() | 释放资源 | 还书 |
| addDisposeListener() | 监听销毁事件 | 设置闹钟 |
| 资源池 | 集中管理资源 | 工具箱 |
| 资源追踪器 | 检测泄漏 | 图书馆管理员 |
费曼测试:你能解释清楚吗?
- 为什么 GC 不会自动回收 SWT 资源?
- SWT 资源是操作系统资源,GC 只回收 Java 对象
- 何时应该释放资源?
- 使用后立即释放,或在控件销毁时释放
- addDisposeListener 的作用?
- 监听控件销毁事件,自动释放相关资源
- 如何检测资源泄漏?
- 使用资源追踪器,记录资源的创建和释放
下一章预告
现在你已经掌握了资源管理,
可以避免内存泄漏,
让应用长期稳定运行。
下一章,我们将学习拖放与剪贴板,
让应用与其他应用交互,
提升用户体验。
练习题:
- 创建一个按钮,点击时创建 Color 并使用,正确释放资源。
- 创建一个资源池,管理多个 Color 和 Font。
- 使用资源追踪器,检测和打印泄漏的资源。
(提示:资源池使用 List 存储资源,统一释放)