第11章:资源管理 - 内存泄漏的隐形杀手

第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()监听销毁事件设置闹钟
资源池集中管理资源工具箱
资源追踪器检测泄漏图书馆管理员

费曼测试:你能解释清楚吗?

  1. 为什么 GC 不会自动回收 SWT 资源?

- SWT 资源是操作系统资源,GC 只回收 Java 对象

  1. 何时应该释放资源?

- 使用后立即释放,或在控件销毁时释放

  1. addDisposeListener 的作用?

- 监听控件销毁事件,自动释放相关资源

  1. 如何检测资源泄漏?

- 使用资源追踪器,记录资源的创建和释放

下一章预告

现在你已经掌握了资源管理,
可以避免内存泄漏,
让应用长期稳定运行。

下一章,我们将学习拖放与剪贴板,
让应用与其他应用交互,
提升用户体验。

练习题:

  1. 创建一个按钮,点击时创建 Color 并使用,正确释放资源。
  2. 创建一个资源池,管理多个 Color 和 Font。
  3. 使用资源追踪器,检测和打印泄漏的资源。

(提示:资源池使用 List 存储资源,统一释放)

← 返回目录