GC需要太多时间

时间:2018-02-16 09:27:01

标签: java garbage-collection jvm

我正在使用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();
            }
        }
    }
}

2 个答案:

答案 0 :(得分:3)

当可用内存变低时,GC会尝试通过收集堆中无法访问的对象来释放一些内存。检查对象是否无法访问并不是一件容易的事,需要一些时间。

当所有(或大多数)对象仍在某处被引用时,这是浪费时间,因为没有回收内存。因此,当JVM认识到运行GC时花费了大量运行时而不是执行有用的操作时,程序将以OutOfMemoryError终止。

答案 1 :(得分:2)

JVM参数中,您指定了-Xms5m -Xmx5m,因此堆大小设置为5MB,即5242880 bytes。现在,由于您已执行循环10000000次,因此您在10000000变量Integer中分配ObjectMap类型的map个对象。现在,如果您查看功能,那么您将意识到您在0.524288中的单个条目平均提供了map个字节。但是您知道Integer对象的大小超过4 bytes(因为int的大小为4 bytesInteger包裹了int类型)。

因此,简而言之,您没有为JVM堆分配足够的内存,这就是您获得异常的原因 java.lang.OutOfMemoryError: GC overhead limit exceeded.

注意:在查看了progarm的以下输出后,我意识到单个条目正在占用848 bytes内存(请参阅下面的计算)。因此,您至少需要10000000 * 848 = 8480000000 bytes8088 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的详细信息,请参阅herehere

计算:在提供的输出中(由我上面发布),正在打印可用内存。现在,如果你进行计算,那么你会发现在向地图添加一个条目之后内存之间的差异是848。因此我可以说,map中的单个条目(不是单个对象,因为有2个对象 - 整数类和对象类)正在消耗848 byres,例如32344 - 31496 = 848; 31496 - 30648 = 848; 30648 - 29800 = 848等等。

另请注意,如果您未设置JVM-Xms次切换,-Xmx将根据需要增加 - 减少其堆大小具有相同的值(因为我在执行样本运行时没有使用这些开关)。

注意:您的代码似乎逻辑上不正确,因为writeCondition.signal();monitorCondition.await();的使用不在sync中。该计划是awaiting infinitely。我认为最终会陷入困境。