我正在使用IntelliJ对Java堆进行一些测试,我收到此错误:
shell_exec ("2>&1 git pull http://testperson:tes%40t123@gitlab.companyname.com master");
From https://gitlab.companyname.com/testperson/git-bootcamp
* branch HEAD -> FETCH_HEAD
Updating ee30924..c92f020
Fast-forward
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
我知道当GC耗费太多时间时会发生这种情况,但我无法理解为什么GC会如此频繁地发生并花费很多时间。
设计
在主线程中,我重复创建新对象,然后将它们放在地图中。为了防止收集这些对象,我会在将这些对象全部放入地图后打印这些对象。
在监视器线程中,每次创建新对象时都会打印当前的空闲堆大小。
运行时堆大小配置:
java.lang.OutOfMemoryError: GC overhead limit exceeded.
测试代码:
-Xms5m -Xmx5m
结果:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Example {
public static ReentrantLock lock = new ReentrantLock(true);
public static Condition writeCondition = lock.newCondition();
public static Condition monitorCondition = lock.newCondition();
public static void main(String[] args) throws Exception {
new Thread(new Monitor(lock, writeCondition, monitorCondition)).start(); // start a monitor thread
Map<Integer, Object> map = new HashMap<>();
for (int count = 0; count < 10000000; count++) {
lock.lock();
try {
// every time create a new Object, I will use monitor thread print current free heap size
monitorCondition.signal();
map.put(count, new Object());
writeCondition.await();
} finally {
lock.unlock();
}
}
for (Map.Entry entry : map.entrySet()) {
// keep reference to these Objects, so they will not be garbage collected.
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
}
class Monitor implements Runnable {
ReentrantLock lock;
Condition writeCondition;
Condition monitorCondition;
public Monitor(ReentrantLock lock, Condition writeCondition, Condition monitorCondition) {
this.lock = lock;
this.writeCondition = writeCondition;
this.monitorCondition = monitorCondition;
}
@Override
public void run() {
int count = 0;
while (true) {
lock.lock();
try {
writeCondition.signal();
long heapFreeSize = Runtime.getRuntime().freeMemory();
System.out.println(count + " times run monitor : ");
System.out.println("heapFreesize : " + heapFreeSize);
System.out.println();
count++;
monitorCondition.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
答案 0 :(得分:3)
当可用内存变低时,GC会尝试通过收集堆中无法访问的对象来释放一些内存。检查对象是否无法访问并不是一件容易的事,需要一些时间。
当所有(或大多数)对象仍在某处被引用时,这是浪费时间,因为没有回收内存。因此,当JVM认识到运行GC时花费了大量运行时而不是执行有用的操作时,程序将以OutOfMemoryError
终止。
答案 1 :(得分:2)
在JVM
参数中,您指定了-Xms5m -Xmx5m
,因此堆大小设置为5MB
,即5242880 bytes
。现在,由于您已执行循环10000000
次,因此您在10000000
变量Integer
中分配Object
和Map
类型的map
个对象。现在,如果您查看功能,那么您将意识到您在0.524288
中的单个条目平均提供了map
个字节。但是您知道Integer
对象的大小超过4 bytes
(因为int
的大小为4 bytes
且Integer
包裹了int
类型)。
因此,简而言之,您没有为JVM
堆分配足够的内存,这就是您获得异常的原因 java.lang.OutOfMemoryError: GC overhead limit exceeded.
。
注意:在查看了progarm的以下输出后,我意识到单个条目正在占用848 bytes
内存(请参阅下面的计算)。因此,您至少需要10000000 * 848 = 8480000000 bytes
即8088 MB
个内存(物理或虚拟内存):
heapFreesize : 32344
4699761 times run monitor :
heapFreesize : 31496
4699762 times run monitor :
heapFreesize : 30648
4699763 times run monitor :
heapFreesize : 29800
4699764 times run monitor :
heapFreesize : 28952
4699765 times run monitor :
heapFreesize : 28104
4699766 times run monitor :
heapFreesize : 27256
4699767 times run monitor :
heapFreesize : 26408
4699768 times run monitor :
heapFreesize : 25560
...
尝试使用更多分配的堆到JVM
,您将不会收到此错误。有关Java HotSpot VM Options
的详细信息,请参阅here和here。
计算:在提供的输出中(由我上面发布),正在打印可用内存。现在,如果你进行计算,那么你会发现在向地图添加一个条目之后内存之间的差异是848
。因此我可以说,map
中的单个条目(不是单个对象,因为有2个对象 - 整数类和对象类)正在消耗848 byres
,例如32344 - 31496 = 848
; 31496 - 30648 = 848
; 30648 - 29800 = 848
等等。
另请注意,如果您未设置JVM
和-Xms
次切换,-Xmx
将根据需要增加 - 减少其堆大小具有相同的值(因为我在执行样本运行时没有使用这些开关)。
注意:您的代码似乎逻辑上不正确,因为writeCondition.signal();
和monitorCondition.await();
的使用不在sync
中。该计划是awaiting infinitely
。我认为最终会陷入困境。