查询如何引起gc

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了查询如何引起gc相关的知识,希望对你有一定的参考价值。

您好,要引起GC,首先要了解GC是什么。GC(垃圾回收)是一种自动内存管理机制,它可以自动清理不再使用的内存,以便释放出可用的内存空间。GC的主要目的是提高程序的性能,减少内存泄漏,提高程序的可靠性。

要引起GC,可以通过以下几种方式:

1. 内存分配:如果程序中的内存分配量过大,就会触发GC,以释放不再使用的内存空间。

2. 内存占用:如果程序中的内存占用量过大,就会触发GC,以释放不再使用的内存空间。

3. 内存泄漏:如果程序中存在内存泄漏,就会触发GC,以释放不再使用的内存空间。

4. 程序运行时间:如果程序运行时间较长,就会触发GC,以释放不再使用的内存空间。

5. 程序暂停:如果程序暂停,就会触发GC,以释放不再使用的内存空间。

总之,GC的触发机制主要是内存分配量、内存占用量、内存泄漏、程序运行时间和程序暂停等。只要程序中出现以上情况,就会触发GC,以释放不再使用的内存空间。
参考技术A 说结论:scroll 查询相对普通查询占用的内存开销大很多,考虑到遍历数据的场景,安全的量是控制在 10qps 左右。

相比于普通query,scroll 查询需要后端保留遍历请求的上下文,具体的就是当有init scroll请求到达时,当时的 index searcher 会持有全部索引段的句柄直至scroll请求结束,如果处理不当,比如段缓存等,容易在server端占用大量内存;另外, scroll 查询还需要在server端保存请求上下文,比如翻页深度、scroll context等,也会占用内存资源。

在后续的测试中,客户端单线程使用scroll查询遍历百万级别的索引数据,server端的CPU占用率高达70%左右,观察进程的CPU占用,发现大部分的CPU时间都耗在gc上,这使得server没有足够的CPU时间调度其他任务,

不恰当使用线程池处理 MQ 消息引起的故障

现状

业务部门反应网站访问特别慢,负责运维监控的同事说MQ消息队列积压了,中间件的说应用服务器内存占用很高,GC 一直回收不了内存,GC 线程占了近 100% 的 CPU,其他的基本上都在等待,数据库很正常,完全没压力。没啥办法,线程、堆 dump 出来后,重启吧,然后应用又正常了。

分析

这种故障之前其实也碰到过了,分析了当时 dump 出来的堆后发现,处理 MQ 消息的线程池的队列长度达百万级别,占用了超过 1.3G 内存,这些内存都是没法回收的。

程序的实现目前是这样的:关联系统把消息推送到 MQ 上,我们再从 MQ 上拉消息下来处理;每种类型的消息都有一个线程负责从 MQ 上拉消息,拉下来后封装成线程池的任务提交给相应的线程池去执行。代码可以简化为:

  package net.coderbee.mq.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MQListener {
     public ExecutorService executor = Executors.newFixedThreadPool(8);

     public void onMessage(final Object message) {
          executor.execute(new Runnable() {
               @Override
               public void run() {
                    // 耗时且复杂的消息处理逻辑
                    complicateHanlde(message);
               }
          });
     }

     private void complicateHanlde(Object message) {
     }
}

 

这个实现就是导致故障的根源, Executors.newFixedThreadPool(8) 创建的线程池的任务队列是无边界的:

  public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
}

当时是关联系统出故障了,他们恢复后,往 MQ 里狂推消息,我们系统里面的 MQListener 不断地从 MQ 拉消息下来,直接塞进线程池里,由于线程池处理消息的速度远远慢于消息进入的速度,所以线程池的队列不断增长,直到把所有的堆内存都占用了,这时不断引发 FullGC,但每次 FullGC 都没法回收到内存,应用也就挂死在那了。

之前那次故障也是线程池队列积压导致的,引起的原因是消息处理逻辑调用了外部接口,由于外部接口的响应非常慢,严重拖慢了消息的处理进度,改成异步调用之后好了些。但问题的根源并没有解决,就像昨天关联系统狂推消息后,我们的系统还是挂了。

解决方法

我的思路其实很简单,MQ 是用来系统间解耦的,也是一个缓冲,目前的实现是把处理消息的线程池又用作一个 MQ 了,消息不能不受控地进入线程池的任务队列,所以,要换成使用定长的阻塞队列,队列满了就暂停拉取消息。把线程池替换成:

  private int nThreads = 8;
private int MAX_QUEUQ_SIZE = 2000;
private ExecutorService executor = new ThreadPoolExecutor(nThreads,
          nThreads, 0L, TimeUnit.MILLISECONDS,
          new ArrayBlockingQueue<Runnable>(MAX_QUEUQ_SIZE),
          new ThreadPoolExecutor.CallerRunsPolicy());

把线程池队列满的时候直接让调用者(也就是 MQListener)执行任务,这样即延缓了消息拉取的速度,当 MQListener 再去拉取消息时,发现线程池有空间时可以提交到线程池,让线程池的工作线程去处理,它继续保持拉取速度。

这样既控制了线程池占用的内存,又可以让消息处理线程池处理不过来时多一个线程处理消息。

由于上面的代码采用调用者执行的方式,那么要考虑消息处理的顺序问题,比如一个订单的处理可能有多个步骤,对应多条 MQ 消息,那么要考虑这些步骤如果乱序了是否可以接受,因为第3步骤的处理消息可能被 MQListener 处理了,而第2步的处理消息还积压在线程池里。

网上看到的,其实也是我遇到的问题! 记录下 !感谢!

以上是关于查询如何引起gc的主要内容,如果未能解决你的问题,请参考以下文章

Kafka如何通过经典的内存缓冲池设计来优化JVM GC问题?

【翻译】JMV GC 停顿时间过长问题排查

性能测试三十五:jvm垃圾回收-GC

引起:java.lang.OutOfMemoryError:超出GC开销限制

如何防止 java.lang.OutOfMemoryError: GC 开销限制在 for 循环期间超出?

一次因网络引起的诡异GC问题,DBA该怎么做?