Java OutOfMemoryError GC Overhead Limit Exceeded - Java 147

Java OutOfMemoryError GC Overhead Limit Exceeded – Java 147

超過 GC 回收上限,表示 Java 程序花費 98% 以上的時間執行 GC,但只恢復了不到 2% 的記憶體,且執行連續重複了 5 次,就會產生 Java OutOfMemoryError 的錯誤,模擬建立一個物件,將其加入到一個 List 中,大小限制為 1000 個,重複建立過程,直到 JVM 拋出錯誤, Java 147 增加了範例,並透過單元測試來驗證產出結果。

Java OutOfMemoryError

GC Overhead Limit Exceeded

超過 GC 回收上限,設定 JVM Arguments。
-Xmn30m -Xms120m -Xmx120m

檔案目錄

./
   +- src
       +- test
       |   +- org
       |       +- ruoxue
       |           +- java_147
       |               +- memory
       |                   +- GCOverheadLimitExceededTest.java 

Out of memory

當引用大量物件沒有被釋放,JVM 無法對其自動回收,常見於使用了文件等資源沒有回收等。

測試 JUnit 4

list

建立一個物件,將其加入到一個 List 中,大小限制為 1000 個,重複建立過程。

	public static class Key {
		private Integer id;

		public Key(Integer id) {
			this.id = id;
		}

		@Override
		public int hashCode() {
			return id.hashCode();
		}
	}
	
	@Test
	public void list() {
		List<Key> list = new ArrayList<Key>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				list.add(new Key(i));
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(list.size());
		}
	}
[3362] free memory: 4132688
3362000
[3363] free memory: 4132688
3363000
[3364] free memory: 4132688
3364000
[3365] free memory: 4132688
3365000
[3366] free memory: 4132688
3366000
[Full GC (Ergonomics)  115235K->115235K(119296K), 0.9471333 secs]
[Full GC (Ergonomics)  115236K->115236K(119296K), 0.9147213 secs]
[Full GC (Ergonomics)  115237K->115237K(119296K), 0.8869343 secs]

java.lang.OutOfMemoryError: GC overhead limit exceeded

map

建立一個物件,將其加入到一個 Map 中,大小限制為 1000 個,重複建立過程。

	@Test
	public void map() {
		Map<Key, Integer> map = new HashMap<Key, Integer>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				map.put(new Key(i), i);
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(map.size());
		}
	}
[1086] free memory: 4272040
1086000
[1087] free memory: 4116664
1087000
[1088] free memory: 4116664
1088000
[1089] free memory: 4116664
1089000
[1090] free memory: 4116664
1090000
[Full GC (Ergonomics)  115274K->115274K(119296K), 0.0707265 secs]
[Full GC (Ergonomics)  115275K->115275K(119296K), 0.0706493 secs]
[Full GC (Ergonomics)  115277K->115277K(119296K), 0.0702664 secs]

java.lang.OutOfMemoryError: GC overhead limit exceeded

byteArray

建立一個 Byte Array,將其加入到一個 Map 中,大小限制為 1000 個,重複建立過程。

	@Test
	public void byteArray() {
		Map<Byte[], Integer> map = new HashMap<Byte[], Integer>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				map.put(new Byte[10], i);
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(map.size());
		}
	}
[1064] free memory: 4086008
1064000
[1065] free memory: 4086008
1065000
[1066] free memory: 4086008
1066000
[1067] free memory: 4086008
1067000
[1068] free memory: 4086008
1068000
[Full GC (Ergonomics)  115346K->115290K(119296K), 0.3285218 secs]
[Full GC (Ergonomics)  115292K->115292K(119296K), 0.2679968 secs]
[Full GC (Ergonomics)  115294K->115294K(119296K), 0.2674329 secs]

java.lang.OutOfMemoryError: GC overhead limit exceeded

normal

建立一個物件,將其加入到一個 Map 中,大小限制為 1000 個,重複建立過程,觸發 GC 回收,記憶體一直保持在可用空間,並不會產生記憶體不足的錯誤。

	public static class KeyEquals {
		Integer id;

		public KeyEquals(Integer id) {
			this.id = id;
		}

		@Override
		public int hashCode() {
			return id.hashCode();
		}

		public boolean equals(Object object) {
			if (!(object instanceof KeyEquals)) {
				return false;
			}
			if (this == object) {
				return true;
			}
			KeyEquals other = (KeyEquals) object;
			return id.equals(other.id);
		}
	}
	
	@Test
	public void normal() {
		Map<KeyEquals, Integer> map = new HashMap<KeyEquals, Integer>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				map.put(new KeyEquals(i), i);
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(map.size());
		}
	}
[1236275] free memory: 122525768
1000
[1236276] free memory: 122525768
1000
[1236277] free memory: 122525768
1000
[1236278] free memory: 122525768
1000
[1236279] free memory: 122525768
1000

GCOverheadLimitExceededTest.java

package org.ruoxue.java_147.memory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;

public class GCOverheadLimitExceededTest {

	public static class Key {
		private Integer id;

		public Key(Integer id) {
			this.id = id;
		}

		@Override
		public int hashCode() {
			return id.hashCode();
		}
	}

	public static class KeyEquals {
		Integer id;

		public KeyEquals(Integer id) {
			this.id = id;
		}

		@Override
		public int hashCode() {
			return id.hashCode();
		}

		public boolean equals(Object object) {
			if (!(object instanceof KeyEquals)) {
				return false;
			}
			if (this == object) {
				return true;
			}
			KeyEquals other = (KeyEquals) object;
			return id.equals(other.id);
		}
	}

	@Test
	public void list() {
		List<Key> list = new ArrayList<Key>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				list.add(new Key(i));
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(list.size());
		}
	}

	@Test
	public void map() {
		Map<Key, Integer> map = new HashMap<Key, Integer>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				map.put(new Key(i), i);
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(map.size());
		}
	}

	@Test
	public void byteArray() {
		Map<Byte[], Integer> map = new HashMap<Byte[], Integer>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				map.put(new Byte[10], i);
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(map.size());
		}
	}

	@Test
	public void normal() {
		Map<KeyEquals, Integer> map = new HashMap<KeyEquals, Integer>();
		int counter = 1;
		for (;;) {
			for (int i = 0; i < 1000; i++) {
				map.put(new KeyEquals(i), i);
			}
			Runtime rt = Runtime.getRuntime();
			System.out.printf("[%d] free memory: %s%n", counter++, rt.freeMemory());
			System.out.println(map.size());
		}
	}
}

心得分享

從輸出結果可以看到,限制 1000 個物件並沒有達到作用,集合的容量遠超過了 1000 ,最後也出現了預期的錯誤,因為類別 Key 只重寫了 hashCode 方法,卻沒有重寫 equals 方法,在使用 containsKey 方法其實就出現了問題,於是就會一直往集合中增加 Key,直至 GC 都無法清除,最終會拋出此錯誤。

Java Heap Space

此外,與 JVM 配置也會有關係,如果設置的 Head Space 特別小,會直接拋出此錯誤,所以有時在資源受限的情況下,無法準確預測程式會拋出哪種具體的原因,參考以下情況作進一步調整:

  1. 增加 JVM Arguments -XX:-UseGCOverheadLimit 但這樣並沒有真正解決問題,只是延遲錯誤發生。
  2. 檢查程式中是否有大量的無窮迴圈或有使用佔用大記憶體的程式,應該優化程式語法
  3. dump 記憶體分析,檢查是否存在 Memory Leak ,如果沒有的話,可以加大記憶體設置。

發佈留言