2010-05-11 32 views
7

Tôi có JFrame chấp nhận các tệp cấp cao nhất. Tuy nhiên sau khi một giọt đã xảy ra, tham chiếu đến khung được tổ chức vô hạn định bên trong một số lớp nội bộ Swing. Tôi tin rằng việc vứt bỏ khung hình sẽ giải phóng tất cả các tài nguyên của nó, vậy tôi đang làm gì sai?Rò rỉ bộ nhớ với Swing Kéo và thả

Ví dụ

import java.awt.datatransfer.DataFlavor; 
import java.io.File; 
import java.util.List; 

import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.TransferHandler; 

public class DnDLeakTester extends JFrame { 
    public static void main(String[] args) { 
     new DnDLeakTester(); 

     //Prevent main from returning or the jvm will exit 
     while (true) { 
      try { 
       Thread.sleep(10000); 
      } catch (InterruptedException e) { 

      } 
     } 
    } 
    public DnDLeakTester() { 
     super("I'm leaky"); 

     add(new JLabel("Drop stuff here")); 

     setTransferHandler(new TransferHandler() { 
      @Override 
      public boolean canImport(final TransferSupport support) { 
       return (support.isDrop() && support 
         .isDataFlavorSupported(DataFlavor.javaFileListFlavor)); 
      } 

      @Override 
      public boolean importData(final TransferSupport support) { 
       if (!canImport(support)) { 
        return false; 
       } 

       try { 
        final List<File> files = (List<File>) 
          support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); 

        for (final File f : files) { 
         System.out.println(f.getName()); 
        } 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 

       return true; 
      } 
     }); 

     setDefaultCloseOperation(DISPOSE_ON_CLOSE); 
     pack(); 
     setVisible(true); 
    } 
} 

Để tái sản xuất, chạy các mã và thả một số tập tin trên khung. Đóng khung để nó được xử lý.

Để xác minh rò rỉ, tôi lấy một bãi chứa đống bằng cách sử dụng JConsole và phân tích nó với Eclipse Memory Analysis tool. Nó cho thấy rằng sun.awt.AppContext đang giữ một tham chiếu đến khung thông qua hashmap của nó. Có vẻ như TransferSupport bị lỗi.

image of path to GC root http://img402.imageshack.us/img402/4444/dndleak.png

Tôi đang làm gì sai? Tôi có nên yêu cầu mã hỗ trợ DND để tự dọn dẹp không?

Tôi đang chạy bản cập nhật JDK 1.6 19.

+0

Tôi bắt đầu nghĩ đây là lỗi JVM. Các lớp DnD có liên quan không có mã để xóa các tham chiếu vi phạm, vì vậy trừ khi DropHandler được loại bỏ khỏi bản đồ của AppContext bằng cách nào đó (tôi không thực sự hiểu được lớp đó), sự rò rỉ sẽ vẫn còn. – tom

+0

Bài đăng trên diễn đàn này mô tả một vấn đề tương tự [http://forums.java.net/jive/thread.jspa?messageID=276311]. Nó không có phản hồi. – tom

Trả lời

4

Mặc dù DropHandler không bị xóa khỏi bản đồ AppContext tĩnh, nhưng đó không thực sự là nguyên nhân gốc rễ, mà chỉ là một trong những nguyên nhân trong chuỗi. (Trình xử lý thả được dự định là một singleton, và không được xóa cho đến khi lớp AppContext được dỡ xuống, mà trong thực tế là không bao giờ.) Sử dụng một DropHandler đơn là theo thiết kế. Nguyên nhân thực sự của rò rỉ là DropHandler thiết lập một thể hiện của TransferSupport, được tái sử dụng cho mỗi hoạt động DnD, và trong một hoạt động DnD, cung cấp cho nó tham chiếu đến thành phần liên quan đến DnD. Vấn đề là nó không xóa tham chiếu khi DnD kết thúc. Nếu DropHandler gọi là TransferSupport.setDNDVariables(null,null) khi DnD thoát, thì vấn đề sẽ biến mất. Đây cũng là giải pháp hợp lý nhất, vì tham chiếu đến thành phần chỉ được yêu cầu trong khi DnD đang được tiến hành. Các cách tiếp cận khác, chẳng hạn như xóa bản đồ AppContext đang phá vỡ thiết kế thay vì sửa chữa một sự giám sát nhỏ.

Nhưng ngay cả khi chúng tôi sửa lỗi này, khung vẫn sẽ không được thu thập.Thật không may, có vẻ là một vấn đề khác: Khi tôi nhận xét ra tất cả các mã liên quan đến DnD, giảm xuống chỉ là một JFrame đơn giản, điều này cũng không được thu thập. Tham chiếu retaning là trong javax.swing.BufferStrategyPaintManager. Có một bug report cho điều này, như chưa được trộn.

Vì vậy, nếu chúng tôi sửa chữa DnD, chúng tôi sẽ gặp phải vấn đề lưu giữ khác khi sơn lại. May mắn thay, tất cả các lỗi này chỉ chứa một khung (hy vọng cùng một khung!), Vì vậy nó không tệ như nó có thể. Khung được xử lý, vì vậy tài nguyên gốc được phát hành và tất cả nội dung có thể bị xóa, cho phép giải phóng, giảm mức độ nghiêm trọng của rò rỉ bộ nhớ.

Vì vậy, để cuối cùng trả lời câu hỏi của bạn, bạn không làm gì sai, bạn chỉ đang đưa ra một số lỗi trong JDK một chút thời gian không khí!

UPDATE: Lỗi quản lý repaint có một cách nhanh chóng sửa chữa - thêm

-Dswing.bufferPerWindow=false 

Để các tùy chọn JVM khởi động tránh được lỗi. Với lỗi này bị quashed, có ý nghĩa khi đăng sửa lỗi cho lỗi DnD:

Để khắc phục sự cố DnD, bạn có thể thêm lệnh gọi phương thức này vào cuối importData().

  private void cancelDnD(TransferSupport support) 
      { 
       /*TransferSupport.setDNDVariables(Component component, DropTargetEvent event) 
       Call setDNDVariables(null, null) to free the component. 
*/ 
       try 
       { 
        Method m = support.getClass().getDeclaredMethod("setDNDVariables", new Class[] { Component.class, DropTargetEvent.class }); 
        m.setAccessible(true); 
        m.invoke(support, null, null); 
        System.out.println("cancelledDnd"); 
       } 
       catch (Exception e) 
       { 
       } 
      } 
+0

Cảm ơn thông tin. Tôi đã phát hiện BufferStrategyPaintManager giữ lại một số lớp nhưng bị phân tâm với các công cụ DnD. Tôi chấp nhận rằng thủ thuật phản chiếu sẽ hoạt động - nhưng tôi không định sử dụng nó – tom

+0

Chắc chắn, đó là lựa chọn của bạn nếu bạn sử dụng bản sửa lỗi hay không, nhưng bây giờ ít nhất bạn biết phạm vi của vấn đề và có thể quyết định cách bạn xử lý nó. Thích bạn. Tôi có thể không bận tâm thêm sửa chữa, và biết vấn đề có thể được giảm nhẹ một phần, có lẽ không làm gì hoặc nhiều nhất, loại bỏ nội dung từ khung trên vứt bỏ. Nhưng đó là trong hindsight bây giờ mức độ của vấn đề được biết đến. Tôi rất tò mò muốn biết tại sao sự rò rỉ đã xảy ra và hậu quả là gì, và thật tuyệt khi biết rằng nếu chúng ta đột nhiên tìm thấy một sửa chữa là cần thiết, thì cái đó có sẵn. Tôi có thể ngủ dễ dàng tối nay! :-) – mdma

1

Có thay đổi kết quả của bạn nếu bạn thêm kết quả này vào lớp học không?

@Override 
public void dispose() 
{ 
    setTransferHandler(null); 
    setDropTarget(null); // New 
    super.dispose(); 
} 

Cập nhật: Tôi đã thêm một cuộc gọi đến các dispose. Tôi nghĩ rằng thiết lập các mục tiêu thả để null nên phát hành một số tài liệu tham khảo nhiều hơn nữa. Tôi không thấy bất cứ điều gì khác về các thành phần có thể được sử dụng để có được mã DnD để cho đi của thành phần của bạn.

+0

Cảm ơn đề xuất của bạn Devon. Không, tôi sợ nó không thay đổi gì cả. – tom

+0

Xin lỗi, vẫn không có may mắn. Tôi cũng đã thử getDropTarget(). SetComponent (null), nhưng nó cũng không hoạt động. Vấn đề là không có bất kỳ mã nào bên trong TransferHandler hoặc các lớp bên trong của nó để loại bỏ DropHandler khỏi AppContext. Chỉ có không phải là một loại bỏ() để bổ sung cho put() – tom

+0

Có vẻ như không có cách nào để phát hành các tài liệu tham khảo được tổ chức bởi AppContext. May mắn thay, khóa được sử dụng trong put là Lớp DropTarget, vì vậy chỉ có thể có 1 instance xuất sắc. Viết tài liệu và giảm thiểu thiệt hại bằng cách vứt bỏ() giải phóng mọi thứ khác. Giống như getContentPane(). RemoveAll(). –

0

Sau khi đã loại bỏ nguồn gốc của các lớp học có liên quan, tôi tin chắc đây là một sự rò rỉ không thể tránh khỏi trong TransferHandler.

Đối tượng trong bản đồ của AppContext, một DropHandler, không bao giờ bị xóa. Vì khóa là đối tượng DropHandler.class và DropHandler là một lớp bên trong riêng tư, cách duy nhất nó có thể được gỡ bỏ từ bên ngoài TransferHandler là làm rỗng toàn bộ bản đồ, hoặc với thủ đoạn phản chiếu.

DropHandler giữ tham chiếu đến đối tượng TransferSupport, không bao giờ bị xóa. Đối tượng TransferSupport giữ một tham chiếu đến thành phần (trong trường hợp của tôi là JFrame), cũng không bị xóa.