陷阱記憶體洩漏
Java 自動管理記憶體。你不需要手動釋放記憶體。當活動執行緒無法再訪問物件時,垃圾收集器可以釋放堆上的物件記憶體。
但是,你可以通過允許不再需要的物件來防止釋放記憶體。無論你將此稱為記憶體洩漏還是記憶體打包,結果都是相同的 - 分配的記憶體不必要地增加。
Java 中的記憶體洩漏可能以各種方式發生,但最常見的原因是永久物件引用,因為垃圾收集器在仍然有對它們的引用時無法從堆中刪除物件。
靜態欄位
可以通過使用包含一些物件集合的 static
欄位定義類來建立這樣的引用,並且在不再需要集合之後忘記將 static
欄位設定為 null
。static
欄位被認為是 GC 根,並且從未收集過。另一個問題是使用 JNI 時非堆記憶體中的洩漏。
Classloader 洩漏
但到目前為止,最陰險的記憶體洩漏型別是類載入器洩漏 。類載入器包含對已載入的每個類的引用,並且每個類都包含對其類載入器的引用。每個物件都包含對其類的引用。因此,即使類載入器載入的類的單個物件不是垃圾,也不能收集該類載入器載入的單個類。由於每個類也引用其靜態欄位,因此也無法收集它們。
累積洩漏累積洩漏示例可能如下所示:
final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
final Deque<BigDecimal> numbers = new LinkedBlockingDeque<>();
final BigDecimal divisor = new BigDecimal(51);
scheduledExecutorService.scheduleAtFixedRate(() -> {
BigDecimal number = numbers.peekLast();
if (number != null && number.remainder(divisor).byteValue() == 0) {
System.out.println("Number: " + number);
System.out.println("Deque size: " + numbers.size());
}
}, 10, 10, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(() -> {
numbers.add(new BigDecimal(System.currentTimeMillis()));
}, 10, 10, TimeUnit.MILLISECONDS);
try {
scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
此示例建立兩個計劃任務。第一個任務從一個名為 numbers
的雙端佇列中獲取最後一個數字,如果該數字可被 51 整除,則會列印數字和雙端佇列的大小。第二項任務是將數字放入雙端佇列。這兩項任務都按固定速率安排,每 10 毫秒執行一次。
如果執行程式碼,你將看到雙端佇列的大小永久增加。這最終將導致 deque 填充消耗所有可用堆記憶體的物件。
為了在保留此程式的語義的同時防止這種情況,我們可以使用另一種方法從 deque 中獲取數字:pollLast
。與方法 peekLast
相反,pollLast
返回元素並將其從雙端佇列中移除,而 peekLast
僅返回最後一個元素。