2011-02-09 17 views
26

thể trùng lặp:
Creating a memory leak with JavaCách dễ nhất để gây rò rỉ bộ nhớ trong Java?

cách dễ nhất để gây rò rỉ bộ nhớ java là gì?

+0

Bạn đang tìm kiếm một ví dụ giả tạo hoặc một lỗi lập trình rất phổ biến? – mikerobi

+0

một ví dụ giả tạo sẽ là tốt nhất xin vui lòng. –

+0

Rò rỉ bộ nhớ được tạo ra bất cứ khi nào một đối tượng không có ý định sử dụng có tham chiếu đến nó. Hầu như bất kỳ chương trình nào có thể viết sẽ là một ví dụ giả mạo về rò rỉ bộ nhớ. – OrangeDog

Trả lời

27

Bạn có thể không thực sự "rò rỉ bộ nhớ" trong Java, trừ khi bạn:

  • tập chuỗi
  • tạo ra các lớp bộ nhớ
  • rò rỉ trong mã nguồn gốc gọi bằng JNI
  • giữ tham chiếu đến điều mà bạn không muốn ở một số nơi bị lãng quên hoặc tối tăm.

Tôi lấy nó mà bạn quan tâm đến trường hợp cuối cùng. Các kịch bản phổ biến là:

  • người nghe, đặc biệt là thực hiện với các lớp bên trong
  • cache.

Một ví dụ tốt đẹp sẽ đến:

  • xây dựng một gui Swing mà ra mắt một số có khả năng không giới hạn của các cửa sổ phương thức;
  • có cửa sổ modal làm điều gì đó như thế này trong quá trình khởi của nó:
     
    StaticGuiHelper.getMainApplicationFrame().getOneOfTheButtons().addActionListener(new ActionListener(){ 
        public void actionPerformed(ActionEvent e){ 
        // do nothing... 
        } 
    }) 
    

Hành động đăng ký không có gì, nhưng nó sẽ gây ra cửa sổ modal để nán lại trong ký ức mãi mãi, ngay cả sau khi đóng cửa, gây rò rỉ - vì các trình lắng nghe không bao giờ được hủy đăng ký, và mỗi đối tượng lớp bên trong vô danh giữ một tham chiếu (vô hình) với đối tượng bên ngoài của nó. Hơn nữa - bất kỳ đối tượng nào được tham chiếu từ các cửa sổ phương thức đều có khả năng bị rò rỉ.

Đây là lý do tại sao các thư viện như EventBus sử dụng tham chiếu yếu theo mặc định.

Ngoài thính giả, các ví dụ điển hình khác là cache, nhưng tôi không thể nghĩ ra một ví dụ hay.

+4

Chuỗi nội bộ không thực sự là rò rỉ bộ nhớ, chúng cũng có thể bị thu gom rác. Vấn đề là chỉ có họ (trong việc thực hiện thông thường) là trong một khu vực bộ nhớ đặc biệt (PermGen) mà là nhỏ hơn so với phần còn lại của bộ nhớ, và do đó dễ dàng hơn đầy lên. –

+0

Bạn nói đúng. Chuỗi nội bộ không phải là một "rò rỉ" thực sự (sau đó một lần nữa, "rò rỉ thực sự" là không thể với jvm). Tuy nhiên, perm được thu thập chỉ khi mọi thứ khác thất bại và nội dung của nó tồn tại các bộ sưu tập lớn, do đó, nó là một trong số ít các nguồn của các vấn đề bộ nhớ thực. Ngoài ra, các chuỗi ký tự được thực hiện chiếm không gian mặc dù chúng không được tham chiếu từ chương trình của bạn. Theo nghĩa đó, chúng gần như bị rò rỉ như bạn có thể nhận được. – fdreger

+0

Một trong những câu trả lời hay nhất: https://www.reddit.com/r/AMA/comments/7z8tw0/people_who_are_experts_in_their_fieldhobby_ama/dumd8y8/ –

3
  1. Tạo một bộ sưu tập của các đối tượng ở phạm vi lớp
  2. kỳ thêm các đối tượng mới vào bộ sưu tập
  3. Đừng thả các tham chiếu đến thể hiện của lớp chứa bộ sưu tập

Bởi vì có luôn luôn là một tham chiếu đến bộ sưu tập và thể hiện của đối tượng sở hữu bộ sưu tập mà Garbage Collector sẽ không bao giờ dọn sạch bộ nhớ đó do đó gây ra "rò rỉ" theo thời gian.

8
public static List<byte[]> list = new ArrayList<byte[]>(); 

Và sau đó thêm (lớn) mảng mà không xóa chúng. Tại một số điểm bạn sẽ hết bộ nhớ mà không nghi ngờ. (Bạn có thể làm điều này với bất kỳ đối tượng nào, nhưng với các mảng lớn, đầy đủ, bạn có thể hết bộ nhớ nhanh hơn)

Trong Java, nếu bạn coi trọng một đối tượng (nó nằm ngoài phạm vi), nó sẽ được thu thập rác. Vì vậy, bạn phải giữ một tham chiếu đến nó để có một vấn đề bộ nhớ.

+0

Điều này sẽ khiến bạn hết bộ nhớ, nhưng làm thế nào bạn có thể bị rò rỉ nếu bạn không bao giờ làm bất cứ điều gì để phá vỡ một tham chiếu đối tượng? – mikerobi

+5

@mikerobi - rò rỉ bộ nhớ là khi bạn "chiếm" một số bộ nhớ mà không làm sạch nó (và không sử dụng nó). Tuy nhiên, nếu bạn dereference đối tượng, nó sẽ được thu gom rác thải. – Bozho

+1

Tôi hiểu điều đó, nhưng tôi không coi đây là sự rò rỉ trong mọi trường hợp. Nó chắc chắn là một rò rỉ nếu bạn nhầm lẫn làm cho một biến lớp tĩnh, nó có thể là một rò rỉ nếu bạn đang sử dụng nó như là một giá trị toàn cầu trong một quá trình chạy dài. Nó không phải là một rò rỉ nếu ý định của bạn là cho dữ liệu tồn tại cho đến khi chương trình chấm dứt. Thực tế là một vòng lặp vô hạn loại bỏ bộ nhớ của bạn không có gì để làm với thực tế rằng đây là một rò rỉ. Rất nhiều rò rỉ không được chú ý, trừ khi chúng liên tục phân bổ dữ liệu mới, nhưng có một đoạn cố định của bộ nhớ mồ côi vẫn là một sự rò rỉ. – mikerobi

8

Dưới đây là một ví dụ đơn giản

public class finalizer { 
    @Override 
     protected void finalize() throws Throwable { 
     while (true) { 
      Thread.yield(); 
     } 
    } 

    public static void main(String[] args) { 
     while (true) { 
      for (int i = 0; i < 100000; i++) { 
       finalizer f = new finalizer(); 
      } 

      System.out.println("" + Runtime.getRuntime().freeMemory() + " bytes free!"); 
     } 
    } 
} 
+0

Bạn có thể giải thích một chút về cách bạn đạt được rò rỉ bộ nhớ trong ví dụ này? – TheBlueNotebook

+0

Không chắc chắn nhưng mã này dường như hoạt động, ít nhất nó đã giết máy tính của tôi và các quy trình được giữ trên nền ngay cả sau khi đóng eclipse – pescamillam

+1

@ TheBlueNotebook Phương thức hoàn tất mà anh ta dùng là những gì Java thường gọi khi nó sắp giải phóng bộ nhớ một đối tượng. Trong phương pháp chính của mình, anh ta tạo ra 100K finalizers và sau đó yêu cầu JVM giải phóng tất cả bộ nhớ. JVM thực hiện điều này một cách lịch sự và kết thúc cuộc gọi trước khi thực sự giải phóng bộ nhớ. Phương thức finalize nó gọi là yields mãi mãi, vì vậy các đối tượng không bao giờ bị xóa, nhưng vòng lặp chính vẫn tiếp tục, do đó tạo một đối tượng 100K khác sẽ không bao giờ bị xóa, sau đó một đối tượng khác, ... – Jeutnarg

3

Từ những gì tôi đã đọc trong câu trả lời đã bỏ phiếu nhất, bạn có lẽ hầu hết các yêu cầu cho một rò rỉ bộ nhớ C-như thế nào. Vâng, kể từ khi có bộ sưu tập garbagge, bạn không thể phân bổ một đối tượng, mất tất cả các tham chiếu của nó và làm cho nó vẫn chiếm bộ nhớ - đó sẽ là lỗi JVM nghiêm trọng. Mặt khác, bạn có thể xảy ra các luồng rò rỉ - tất nhiên, sẽ gây ra trạng thái này, bởi vì bạn sẽ có một số luồng chạy với tham chiếu đến các đối tượng và bạn có thể mất tham chiếu của chuỗi. Bạn vẫn có thể nhận được các tài liệu tham khảo chủ đề thông qua các API - http://www.exampledepot.com/egs/java.lang/ListThreads.html

0

Hãy thử lớp này đơn giản:

public class Memory { 
private Map<String, List<Object>> dontGarbageMe = new HashMap<String, List<Object>>(); 

public Memory() { 
    dontGarbageMe.put("map", new ArrayList<Object>()); 
} 

public void useMemInMB(long size) { 
    System.out.println("Before=" + getFreeMemInMB() + " MB"); 

    long before = getFreeMemInMB(); 
    while ((before - getFreeMemInMB()) < size) { 
     dontGarbageMe.get("map").add("aaaaaaaaaaaaaaaaaaaaaa"); 
    } 

    dontGarbageMe.put("map", null); 

    System.out.println("After=" + getFreeMemInMB() + " MB"); 
} 

private long getFreeMemInMB() { 
    return Runtime.getRuntime().freeMemory()/(1024 * 1024); 
} 

public static void main(String[] args) { 
    Memory m = new Memory(); 
    m.useMemInMB(15); // put here apropriate huge value 
} 
} 
+0

Đây là ví dụ đơn giản phức tạp nhất ở đây. ;) –

+0

Bị rò rỉ ở đâu? không phải là danh sách được giải phóng sau GC? –

15

"Một rò rỉ bộ nhớ, khoa học máy tính (hoặc rò rỉ, trong bối cảnh này), xảy ra khi một chương trình máy tính tiêu thụ bộ nhớ nhưng không thể phát hành nó trở lại hệ điều hành. " (Wikipedia)

Câu trả lời dễ dàng là: Bạn không thể. Java thực hiện quản lý bộ nhớ tự động và sẽ giải phóng các tài nguyên không cần thiết cho bạn. Bạn không thể ngăn điều này xảy ra. Nó sẽ luôn luôn có thể phát hành các nguồn lực. Trong các chương trình với quản lý bộ nhớ thủ công, điều này là khác nhau. Bạn không thể nhận được một số bộ nhớ trong C bằng cách sử dụng malloc(). Để giải phóng bộ nhớ, bạn cần con trỏ mà malloc trả về và gọi miễn phí() trên đó. Nhưng nếu bạn không có con trỏ nữa (ghi đè hoặc vượt quá tuổi thọ), thì bạn không may không có khả năng giải phóng bộ nhớ này và do đó bạn bị rò rỉ bộ nhớ.

Tất cả các câu trả lời khác cho đến nay là trong định nghĩa của tôi không thực sự rò rỉ bộ nhớ. Tất cả đều nhằm mục đích lấp đầy bộ nhớ bằng những thứ vô nghĩa thật nhanh. Nhưng bất cứ lúc nào bạn vẫn có thể dereference các đối tượng bạn tạo ra và do đó giải phóng bộ nhớ -> NO LEAK.câu trả lời của acconrad đến khá gần mặc dù tôi phải thừa nhận kể từ khi giải pháp của ông là có hiệu quả để chỉ "sụp đổ" các nhà sưu tập rác bằng cách buộc nó trong một vòng lặp vô tận).

Câu trả lời dài là: Bạn có thể bị rò rỉ bộ nhớ bằng cách viết thư viện cho Java bằng JNI, có thể có quản lý bộ nhớ thủ công và do đó có rò rỉ bộ nhớ. Nếu bạn gọi thư viện này, quá trình java của bạn sẽ bị rò rỉ bộ nhớ. Hoặc, bạn có thể có lỗi trong JVM, để JVM mất bộ nhớ. Có lẽ có lỗi trong JVM, thậm chí có thể có một số lỗi đã biết vì bộ sưu tập rác không phải là tầm thường, nhưng sau đó nó vẫn là một lỗi. Bằng cách thiết kế này là không thể. Bạn có thể yêu cầu một số mã java được thực hiện bởi một lỗi như vậy. Xin lỗi tôi không biết một và nó cũng có thể không phải là một lỗi nữa trong phiên bản Java tiếp theo anyway.

+1

"Nhưng bất cứ lúc nào bạn vẫn có thể dereference các đối tượng bạn tạo ra và do đó giải phóng bộ nhớ". Tôi không đồng ý. Trình triển khai lớp có thể ẩn các đối tượng xử lý từ thế giới bên ngoài. –

+0

@trinithis: Nếu bạn có một đối tượng lãng phí bộ nhớ riêng tư bằng cách cấp phát lượng bộ nhớ khổng lồ, thì bạn không thể buộc đối tượng giải phóng bộ nhớ mà không loại bỏ đối tượng đó. Nhưng trong trường hợp này nó vẫn chỉ lãng phí bộ nhớ và không bị rò rỉ. Bộ nhớ có thể được giải phóng. Nó sẽ được giải phóng khi đối tượng tham chiếu đến bộ nhớ bị lãng phí không được tham chiếu nữa. Hay tôi hiểu lầm bạn? – yankee

+0

Tôi nghĩ rằng tôi hiểu lầm những gì bạn có nghĩa là do 'dereference'. Tôi đã suy nghĩ về ý nghĩa của từ C. –

0

Dường như hầu hết các câu trả lời đều không phải là rò rỉ bộ nhớ kiểu C.

Tôi nghĩ rằng tôi muốn thêm một ví dụ về một lớp thư viện với một lỗi mà sẽ cung cấp cho bạn một ngoại lệ bộ nhớ. Một lần nữa, nó không phải là một rò rỉ bộ nhớ thực sự nhưng là một ví dụ về một cái gì đó chạy ra khỏi bộ nhớ mà bạn sẽ không mong đợi.

public class Scratch { 
    public static void main(String[] args) throws Exception { 
     long lastOut = System.currentTimeMillis(); 
     File file = new File("deleteme.txt"); 

     ObjectOutputStream out; 
     try { 
      out = new ObjectOutputStream(
        new FileOutputStream("deleteme.txt")); 

      while (true) { 
       out.writeUnshared(new LittleObject()); 
       if ((System.currentTimeMillis() - lastOut) > 2000) { 
        lastOut = System.currentTimeMillis(); 
        System.out.println("Size " + file.length()); 
        // out.reset(); 
       } 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

class LittleObject implements Serializable { 
    int x = 0; 
} 

Bạn sẽ tìm thấy mã gốc và mô tả lỗi tại

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4363937

0

Sau đây cực kỳ giả tạo Box lớp sẽ bị rò rỉ bộ nhớ nếu được sử dụng. Các đối tượng là put vào lớp này là cuối cùng (sau khi một cuộc gọi đến put để được chính xác ... miễn là cùng một đối tượng không phải là lại put vào nó.) Không thể tiếp cận với thế giới bên ngoài. Họ không thể được dereferenced thông qua lớp này, nhưng lớp học này đảm bảo họ không thể được thu thập. Đây là một rò rỉ REAL. Tôi biết điều này là thực sự contrived, nhưng các trường hợp tương tự có thể làm do tai nạn.

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Stack; 

public class Box <E> { 
    private final Collection<Box<?>> createdBoxes = new ArrayList<Box<?>>(); 
    private final Stack<E> stack = new Stack<E>(); 

    public Box() { 
     createdBoxes.add(this); 
    } 

    public void put (E e) { 
     stack.push(e); 
    } 

    public E get() { 
     if (stack.isEmpty()) { 
      return null; 
     } 
     return stack.peek(); 
    } 
}