多线程并发查询百万数据的内存占用问题?

开启十个线程,每个线程都会去查询500W的数据。

  @Test
    void testThread() throws InterruptedException {
        int size = 10;
        CountDownLatch countDownLatch = new CountDownLatch(size);
        for (int i = 0; i < size; i++) {
            CompletableFuture.runAsync(() -> {
                testPage();
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
    }

@Test
    void testPage(){
        //查询出表中总记录数
        Long total = orderMapper.selectCount(null);
        //每次分页读取的结果数
        int fetchSize = 100000;
        // 分页优化参数,上次查询的最大ID
        int lastMaxId = 0;
        for (int i = 0; i < (total / fetchSize) + 1; i++) {
            LambdaQueryWrapper<Order> orderLambdaQueryWrapper = new LambdaQueryWrapper<>();
            orderLambdaQueryWrapper.gt(Order::getOrderId, lastMaxId);
            List<Order> records = orderMapper.selectPage(new Page<>(1, fetchSize), orderLambdaQueryWrapper).getRecords();
           records.stream().forEach(System.out::println);
            //获取本次最大的Id
            lastMaxId = records.get(records.size() - 1).getOrderId();
        }

    }

单独一个线程,堆内存占用500M。
image.png
十个线程,堆内存占用最高也不过1400MB,为什么会这样呢?这些内存占用居然不会叠加的吗?
image.png

阅读 638
avatarAI BotBETA

这个问题涉及到Java内存管理和多线程的并发控制。

首先,Java的内存管理是分代的。堆内存是其中最大的一部分,它被所有的线程共享。当你创建一个线程时,它会有自己的栈,但堆内存是共享的。这意味着每个线程都会使用相同的堆内存,而不是每个线程都有自己的堆内存。

其次,关于你的测试结果,这可能是因为Java的垃圾回收(GC)机制。当一个线程结束时,它的内存会被回收,包括堆内存。这就是为什么你在十个线程运行时,堆内存使用并没有叠加。每次线程结束,它使用的内存都会被回收,然后再被其他线程使用。

此外,你的代码中使用了流操作(stream),流操作在处理完数据后会自动关闭,这可能也是内存得到释放的原因之一。

最后,你可能在程序运行时启用了某些Java虚拟机(JVM)参数或垃圾收集器,这可能会影响内存使用情况。例如,你可能启用了并行垃圾收集器(Parallel GC),它通常能提供更好的吞吐量,但可能会导致更多的内存占用。

总的来说,你的代码和程序运行情况共同决定了你的内存使用情况。你的程序可能并没有产生大量的内存溢出,因为你的代码合理地管理了内存,并且垃圾回收器也有效地回收了不再使用的内存。

2 个回答

题目说十个线程各查 500 万,代码里是十个现成分批查,一批 10 万,500 万和 10 万的差距还是挺大的
多线程环境下这么观察内存使用其实没法观察,不如把单线程查询需要多少内存算准了,多线程就做个乘法的事
另外如果想知道内存使用多少,直接看字段大小就能大概估算出来

我觉得从“单线程查出数据时堆内存占用500M”不能得出“数据库全量数据对象的大小是500M左右”的结论,所以“10个线程是1400M不是叠加”这个结论也站不住脚。假设你的程序启动成本就是400M,一个线程在持续查询时内存占用是100M;那么10个就是1000M,加上启动成本就是1400M,也能解释你现在这个现象。
想要获得更精准一点的统计可以用jprofiler,可以根据类和调用来分析堆中的数据。不过,这样做的前提也是你的对象存在堆中,如果像代码这样查出来就丢,大概率是进不到老年代,在伊甸区就被回收了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏