java解决es索引字段上限,关于生成elasticsearch索引数据时内存溢出的故障

一、问题描述

在java应用中按批次生成elasticsearch中的索引数据的同时,首先在mysql中查询大量数据转成对象存放在list列表,然后把list中的大批量数据通过bulk的方式写入索引,最终导致java内存泄露。通过top命令惊奇的发现CPU突然从60%-90%-160%-飙到200%.。。。当发现此问题时,只是简单的设置了JVM的内存参数 -Xms:1024m,-Xmx:3074m再次调大了 -Xmx:4096m ,当设置JVM参数后,还是不可以。

二、异常列表

2、1 Java heap space

Exception in thread "pool-2-thread-3" java.lang.OutOfMemoryError: Java heap space

at com.fasterxml.jackson.core.util.BufferRecycler.balloc(BufferRecycler.java:155)

at com.fasterxml.jackson.core.util.BufferRecycler.allocByteBuffer(BufferRecycler.java:96)

at com.fasterxml.jackson.core.util.BufferRecycler.allocByteBuffer(BufferRecycler.java:86)

at com.fasterxml.jackson.core.io.IOContext.allocWriteEncodingBuffer(IOContext.java:160)

at com.fasterxml.jackson.core.json.UTF8JsonGenerator.(UTF8JsonGenerator.java:122)

at com.fasterxml.jackson.core.JsonFactory._createUTF8Generator(JsonFactory.java:1426)

at com.fasterxml.jackson.core.JsonFactory.createGenerator(JsonFactory.java:1139)

at org.elasticsearch.common.xcontent.json.JsonXContent.createGenerator(JsonXContent.java:79)

at org.elasticsearch.common.xcontent.XContentBuilder.(XContentBuilder.java:168)

at org.elasticsearch.common.xcontent.XContentBuilder.(XContentBuilder.java:142)

at org.elasticsearch.common.xcontent.XContentBuilder.builder(XContentBuilder.java:69)

at org.elasticsearch.common.xcontent.json.JsonXContent.contentBuilder(JsonXContent.java:47)

at org.elasticsearch.common.xcontent.XContentFactory.contentBuilder(XContentFactory.java:121)

at org.elasticsearch.action.index.IndexRequest.source(IndexRequest.java:313)

at org.elasticsearch.action.index.IndexRequest.source(IndexRequest.java:303)

at org.elasticsearch.action.update.UpdateRequest.doc(UpdateRequest.java:553)

at org.elasticsearch.action.update.UpdateRequestBuilder.setDoc(UpdateRequestBuilder.java:212)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

at java.lang.Thread.run(Thread.java:748)

java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid250485.hprof ...

java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid250485.hprof ...

2、2 c3p0

[-AdminTaskTimer] c.m.v.a.ThreadPoolAsynchronousRunner : com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@bf88359 -- APPARENT DEADLOCK!!! Complete Status:

Managed Threads: 3

Active Threads: 3

Active Tasks:

com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@501c26bd

on thread: C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#0

com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@396518ab

on thread: C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#1

com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@682291e5

on thread: C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#2

Pending Tasks:

com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@1b7d5a99

com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@2ae5be85

Pool thread stack traces:

Thread[C3P0PooledConnectionPoolManager[identityToken->2se77na1kbtz82s9jca9|1fac1d5c]-HelperThread-#0,5,main]

三、系统环境还原

cat /etc/redhat-release

CentOS Linux release 7.4.1708 (Core)

uname -r

3.10.0-693.21.1.el7.x86_64

java -version

openjdk version “1.8.0_171”

OpenJDK Runtime Environment (build 1.8.0_171-b10)

OpenJDK 64-Bit Server VM (build 25.171-b10, mixed mode)

三台mesos-1.6.0、三台marathon-1.5、三台elasticsearch6.1.4

启动java应用的marathon配置:cpu0.2,内存3g

初始化c3p0的mysql5.7连接属性:

##连接池大小管理

##初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default:3(规范值:20,可根据系统链路总体性能进行更改)

spring.datasource.c3p0.mediadb.initialPoolSize=5

##连接池中保留的最小连接数。Default:3(规范值:20,可根据系统链路总体性能进行更改)

spring.datasource.c3p0.mediadb.minPoolSize=5

##连接池中保留的最大连接数。Default:15(规范值:100~500之间,可根据系统链路总体性能进行更改)

spring.datasource.c3p0.mediadb.maxPoolSize=50

##当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default:3(规范值:5,无特殊情况不建议修改)

spring.datasource.c3p0.mediadb.acquireIncrement=5

##重连接相关规范

##从数据库获取新连接失败后重复尝试的次数。Default:30

spring.datasource.c3p0.mediadb.acquireRetryAttempts=30

##两次连接中间隔时间,单位毫秒。Default:1000

spring.datasource.c3p0.mediadb.acquireRetryDelay=1000

##当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒。Default:0(不建议此配置使用)

##checkoutTimeout: 0

##连接池的大小和连接的生存时间管理规范

##连接的最大空闲时间,单位秒,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。Default:0(规范值:120,无特殊情况不建议修改)

spring.datasource.c3p0.mediadb.maxIdleTime=120

##配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待 它close再断开。配置为0的时候则不会对连接的生存时间进行限制。(规范值:0,除非使用其他类型数据库)

spring.datasource.c3p0.mediadb.maxConnectionAge=0

##配置PreparedStatement缓存规范

##连接池为数据源缓存的PreparedStatement的总数。default: 0

spring.datasource.c3p0.mediadb.maxStatements=100

##定义了连接池内单个连接所拥有的最大缓存statements数。Default:0(规范值:5,无特殊情况不建议修改)

spring.datasource.c3p0.mediadb.maxStatementsPerConnection=5

##配置连接测试

##SQL检测语句。Default:null

spring.datasource.c3p0.mediadb.preferredTestQuery=SELECT SYSDATE() FROM DUAL

##用来配置测试空闲连接的间隔时间,单位秒。测试方式还是上面的两种之一,因为它保证连接池会每隔一定时间对空闲连接进行一次测试,从而保证有效的空闲连接能每隔一定时间访问一次数据库,为0则不测试。Default:0

spring.datasource.c3p0.mediadb.idleConnectionTestPeriod=120

##c3p0是异步操作的,缓慢的JDBC操作通过进程完成。扩展这些操作可以有效的提升性能通过多线程实现多个操作同时被执行。Default:3

spring.datasource.c3p0.mediadb.numHelperThreads=3

四、代码示例

4、1未优化前java部分

List> list = new ArrayList<>();

for (int i = beginNum; i < endNum; i++) {

User sysUser = userList.get(i);

String userId = sysUser.getId();

List> initList = xxMapper.selectUserXXList(userId);

if(!initList.isEmpty()){

list.addAll(initList);

}

}

4、2未优化前sql部分

SELECT

CONCAT(#{uId},'$',less.l_id) as u_l_id,

(

SELECT

COUNT(1)

FROM

Xx_favorites fa

WHERE

fa.user_id = #{userId}

AND fa.l_id = less.l_id) AS is_f,

IFNULL(

(SELECT

lm.ip

FROM

xx_mapping lum

WHERE

lm.user_id = #{userId}

AND lm.l_id = less.l_id),0) AS is_p

FROM

xx_ls ls

4、3改造方法

4、3、1加入多线程

在调用“4、1未优化前java部分”的方法时,由于是使用分批次处理,这里使用CountDownLatch的countDown()和await()方法,注意每次处理时,需重新初始化CountDownLatch对象,且设置的批次个数和CountDownLatch构造方法中传的值的个数务必保持一致。还要注意的时,此应用是否跟其他批处理应用在同一台机器中,机器资源是否允许等硬件环境。

4、3、2优化生成索引方式

“4、2未优化前sql部分”中的内容是生成一个索引,在测试环境开发时,由于开发库数量级比较少,且未询问DBA生产环境中的数据条数,所以这是导致内存溢出的一个主要原因。

在“4、2未优化前sql部分”中,xx_ls表以及“4、1未优化前java部分”中的Users表的条数是关键因素,二者和下述内容是笛卡尔积的关系,如果Xx_favorites表和xx_mapping表中的数据条数可忽略,且按照下述SQL生成索引的话,那么索引中的数据将是亿级别的,这样虽然在查询时不用多嵌套其他索引查询来获取字段,但是这种数据量的积累也造成了不必要的开销。

在ES解决方案就是把“4、2未优化前sql部分”中的Xx_favorites表和xx_mapping表分别单独建立索引,与表的内容保持一致,在其他情况允许的情况下,查询时多查询两个索引即可。

所以在建立索引时,不仅要熟悉业务逻辑、表关系以外,在硬件环境和人为因素也要考虑进去。

#include "mainform.h" #include "ui_mainform.h" #include<libusb.h> #include<QLabel> #include<QThread> Mainform::Mainform(QWidget *parent) : QMainWindow(parent), ui(new Ui::Mainform) { ui->setupUi(this); } Mainform::~Mainform() { delete ui; } // // Mainform.cpp // void Mainform::handleImageReady(QImage image) { // // 将图像显示到label // ui->label->setPixmap(QPixmap::fromImage(image).scaled(640, 512)); // ui->label->setMinimumSize(640, 512); // } class subThread : public QThread { Q_OBJECT public: explicit subThread(Mainform* parent); signals: void on_pushButton_clicked(); void imageReady(QImage); // 新增图像就绪信号 protected: void run(){ //初始化libusb库 libusb_init(NULL); struct libusb_device_handle *devh=libusb_open_device_with_vid_pid(NULL,0xaa55,0x8866);//VID,PID int transferred_bytes=0; if(devh) { unsigned char buf[512];//数据头帧 libusb_bulk_transfer( devh, // 设备句柄 0x81, // 端点地址 (IN方向) buf, // 数据缓冲区 sizeof(buf), // 缓冲区大小 &transferred_bytes, // 实际传输字节数 120 // 超间 ); unsigned char data[655360]; int i=0; while (devh) { unsigned char buffer[16384];//每40次接收是一帧数据 libusb_bulk_transfer( devh, // 设备句柄 0x81, // 端点地址 (IN方向) buffer, // 数据缓冲区 sizeof(buffer), // 缓冲区大小 &transferred_bytes, // 实际传输字节数 120 // 超间 ); if(i<40) memcpy(data+i*16384,buffer,16384); if(i==40) { QImage image( 640, // 宽度(像素列数) 512, // 高度(像素行数) QImage::Format_Grayscale8 ); for (int y=0; y<512; ++y) { for (int x=0; x<640; ++x) { int idx = 2 * (y*640 + x); // 每像素占2字节 quint16 high = data[idx]; // 高位字节 quint16 low = data[idx+1]; // 低位字节 quint16 pixel = (high << 8) | low; // 合并为16位 quint8 gray = pixel >> 8; // 取高8位作为灰度值 image.setPixel(x, y, gray); } } emit imageReady(image); // ui->label->setPixmap(QPixmap::fromImage(image)); //ui->label->setText(QString::number(sizeof(data))); //ui->label->setMinimumSize(640,512); i=0; } i++; } } } }; void Mainform::on_pushButton_clicked() { } 怎么启动subThread 线程
最新发布
03-26
<think>嗯,我现在需要帮助用户解决如何在Qt中启动他们定义的subThread线程。让我仔细看一下用户提供的代码,然后一步一步分析问题所在。 首先,用户有一个Mainform类,继承自QMainWindow。里面有一个on_pushButton_clicked的槽函数,目前是空的。用户的问题是如何启动subThread线程。他们的subThread类继承自QThread,并且有一个run方法,里面处理了USB设备的通信和图像数据的接收与处理,最后通过信号imageReady发送图像数据。 用户可能是在点击按钮的候想要启动这个子线程,所以需要将按钮的点击信号连接到线程的启动方法。不过现在他们的on_pushButton_clicked函数是空的,所以需要在这里实例化subThread并启动它。 接下来,我需要检查subThread的定义是否正确。注意到subThread的构造函数需要一个Mainform指针作为父类,但用户当前的代码中构造函数是explicit subThread(Mainform* parent);,这里可能需要正确初始化QThread的父类,否则可能会有问题。通常,在继承QThread,构造函数应该调用基类的构造函数,比如使用Q_OBJECT宏和正确的初始化列表。 另外,subThread类中声明了信号on_pushButton_clicked(),这似乎不太对,因为这个信号应该是按钮点击发出的,但子线程自己不应该发出这个信号。可能这是一个错误,或者用户可能原本想将按钮的信号连接到线程的某个槽,但需要进一步确认。 在subThread的run函数里,用户使用了libusb进行数据传输,并且在接收到足够的数据后构造QImage,并通过imageReady信号发送出去。用户希望这个图像能显示在Mainform的label上,所以需要将imageReady信号连接到Mainform中的某个槽函数,比如之前注释掉的handleImageReady函数。 现在,回到如何启动线程的问题。在Mainform的on_pushButton_clicked槽函数中,用户需要创建subThread的实例,并将其启动。同,需要确保线程的正确管理,比如在窗口关闭安全退出线程,避免内存泄漏。 可能的步骤: 1. 在Mainform的头文件中声明subThread,或者将subThread的定义放在Mainform.cpp中,避免头文件问题。 2. 在Mainform的on_pushButton_clicked中创建subThread的实例,并连接其imageReady信号到处理图像的槽。 3. 调用thread->start()启动线程。 4. 处理线程的退出,比如在Mainform的析构函数中请求线程终止,并等待其结束。 但是,注意到用户提供的subThread类中,run函数里有一个无限循环:while (devh)。这可能导致线程无法正常退出。所以需要添加一个标志位或者条件变量来控制循环的退出,比如使用一个成员变量m_stop,当需要停止线程设置为true,并在循环中检查这个变量。 另外,用户可能在subThread的构造函数中没有正确传递父指针,导致线程无法正确管理生命周期。例如,subThread的构造函数应该调用QThread的构造函数,如subThread::subThread(Mainform* parent) : QThread(parent) { ... }。 此外,subThread类中使用了Q_OBJECT宏,这意味着需要运行moc(元对象编译器)生成moc文件,否则会导致链接错误。用户需要确保subThread类的定义在头文件中,或者在cpp文件中包含相应的moc生成指令,比如在代码末尾添加#include "mainform.moc",如果subThread是在Mainform.cpp中定义的。 还有,在subThread的run函数中,当图像准备好后,通过emit imageReady(image)发送信号,但需要确保这个信号连接到Mainform的槽函数,比如handleImageReady,这个槽函数已经被注释掉了,需要取消注释并实现。或者,用户需要将imageReady信号连接到某个现有的处理函数,以更新label的显示。 总结一下,正确的启动步骤可能包括: - 在Mainform的on_pushButton_clicked槽中创建subThread实例。 - 连接subThread的imageReady信号到Mainform的显示图像的槽。 - 启动线程。 - 管理线程的生命周期,防止内存泄漏。 另外,需要注意线程中访问UI组件的问题。在Qt中,UI操作必须在主线程中进行,所以不能在子线程中直接操作UI组件,比如设置label的文本或图像。用户目前的代码在子线程中注释掉了对ui->label的操作,这是正确的,应该通过信号槽机制将图像数据发送到主线程处理。 现在,用户需要将imageReady信号连接到handleImageReady槽,该槽函数负责更新UI。例如,在Mainform的构造函数或on_pushButton_clicked函数中,连接信号和槽。 可能的实现步骤: 1. 在Mainform类中恢复handleImageReady槽函数,并确保其正确实现,例如: void Mainform::handleImageReady(QImage image) { ui->label->setPixmap(QPixmap::fromImage(image).scaled(640, 512)); ui->label->setMinimumSize(640, 512); } 2. 在Mainform的on_pushButton_clicked函数中,创建subThread对象,并连接其imageReady信号到handleImageReady: void Mainform::on_pushButton_clicked() { subThread *thread = new subThread(this); connect(thread, &subThread::imageReady, this, &Mainform::handleImageReady); connect(thread, &QThread::finished, thread, &QObject::deleteLater); // 确保线程结束后自动删除 thread->start(); } 3. 修改subThread的构造函数,正确初始化QThread的父类: subThread::subThread(Mainform* parent) : QThread(parent) { // 初始化代码,如果有的话 } 4. 确保subThread类在Mainform.cpp中的定义正确,或者移动到头文件中,并处理Q_OBJECT宏的moc生成。如果subThread定义在cpp文件中,可能需要在文件末尾添加#include "mainform.moc"以生成元对象代码。 5. 处理线程的终止问题。由于run函数中的循环是无限的,需要添加退出条件。例如,在subThread中添加一个成员变量m_running,并在run函数中检查它: void run() { m_running = true; while (m_running && devh) { // ... 原有代码 ... } // 释放资源,如libusb_close等 } 然后在需要停止线程,设置m_running为false,并适当处理libusb的资源释放。 此外,用户需要确保libusb的初始化和释放正确,比如在subThread的run函数开始调用libusb_init,结束调用libusb_exit。当前代码中,如果devh打开失败,可能不会调用libusb_exit,导致资源泄漏。 可能还需要在subThread的析构函数中请求停止线程,并等待其结束,但需要注意线程的安全退出。 综上所述,用户需要修正线程的启动方式,正确连接信号和槽,处理线程的生命周期,并确保资源正确释放。</think>在Qt中启动自定义线程`subThread`需要遵循以下步骤。以下是针对您代码的逐步解决方案: --- ### **步骤1:修复线程类定义** ```cpp // 在Mainform.cpp中添加以下修改(注意需要包含Q_OBJECT宏) class subThread : public QThread { Q_OBJECT // 必须包含Q_OBJECT宏以支持信号槽 public: explicit subThread(Mainform* parent = nullptr) : QThread(parent) {} // 修改构造函数 // 其他代码保持不变... }; ``` --- ### **步骤2:实现图像显示槽函数** 在`Mainform`类中恢复注释的槽函数: ```cpp // Mainform.h 声明 private slots: void handleImageReady(QImage image); // Mainform.cpp 实现 void Mainform::handleImageReady(QImage image) { ui->label->setPixmap(QPixmap::fromImage(image).scaled(640, 512)); ui->label->setMinimumSize(640, 512); } ``` --- ### **步骤3:连接信号与槽** 修改`on_pushButton_clicked`函数以启动线程: ```cpp void Mainform::on_pushButton_clicked() { subThread *thread = new subThread(this); // 创建线程对象 connect(thread, &subThread::imageReady, this, &Mainform::handleImageReady); // 连接信号 connect(thread, &QThread::finished, thread, &QObject::deleteLater); // 自动释放内存 thread->start(); // 启动线程 } ``` --- ### **步骤4:解决编译问题** 1. **Q_OBJECT宏问题**: 如果`subThread`类定义在`.cpp`文件中,需在文件末尾添加: ```cpp #include "moc_mainform.cpp" // 或根据实际文件名调整 ``` 或通过Qt Creator右键菜单 **运行qmake** 自动生成元对象代码。 2. **资源释放问题**: 在`subThread::run()`中,添加退出条件和资源清理: ```cpp void run() { bool isRunning = true; // ... 原有初始化代码 ... while (isRunning && devh) { // 添加退出条件 // ... 数据接收代码 ... } libusb_close(devh); // 关闭设备 libusb_exit(NULL); // 释放libusb资源 } ``` --- ### **步骤5:线程安全退出** 在`Mainform`析构函数中请求线程停止: ```cpp Mainform::~Mainform() { // 遍历并停止所有子线程(需自行实现线程管理容器) // 示例:if (thread && thread->isRunning()) thread->requestInterruption(); delete ui; } ``` --- ### **关键问题总结** 1. **信号槽连接**:通过`imageReady`信号将图像数据传递到主线程显示。 2. **线程生命周期**:使用`deleteLater`确保线程结束后自动释放内存。 3. **UI操作限制**:所有UI更新(如`setPixmap`)必须在主线程中执行,通过信号槽实现跨线程通信。 --- ### **验证运行** 1. 点击按钮触发`on_pushButton_clicked`。 2. 线程启动后接收USB数据生成图像。 3. 图像通过信号槽更新到`ui->label`。 若仍有问题,请检查USB设备权限和VID/PID是否匹配实际硬件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值