多线程数据库驱动加载引发的惨案

简介: 以往项目生产java.lang.OutOfMemoryError: Compressed class space错误排查总结,

背景:

以往数据类项目,有这样的一个业务场景:异构数据源整库和各库表业务数据量定时统计,时效性每天统计,数据源涉及200+数据源,表数据量较大,客户要求每天凌晨精确查询数据量,不允许通过数据元数据模糊查询表数据量,项目上利用了多线程并发定时统计汇聚,因涉及到数据源多样性,采用动态加载不同版本的数据库驱动实现jdbc连接,导致的java.lang.OutOfMemoryError: Compressed class space错误,服务不可用

排查思路:

1、了解jdk8 压缩空间是什么?

jdk8内存结构

   jdk8之前有perm这一整块内存来存klass等信息(参数里必不可少地会配置-XX:PermSize以及-XX:MaxPermSize来控制这块内存的大小),jdk8后改成了metaspace元数据存储空间

为啥需要压缩空间?

OOPS:原始对象指针(ordinary object pointers)

    64bit的JVM出现后,OOPS的尺寸也变成了64bit,比之前的大了一倍。这会引入性能损耗——占的内存double了,并且同尺寸的CPU Cache要少存一倍的OOPS

    使用了这个压缩功能,每个对象中的 Klass* 字段就会被压缩成 32bit(不是所有的 oop 都会被压缩的),总所周知 Klass* 指向的 Klass 在永久代(Java7 及之前)。但是在 Java8 及之后,永久代没了,有了一个 Metaspace,于是之前压缩指针 Klass* 指向的这块 Klass 区域有了一个名字 —— Compressed Class Space。Compressed Class Space 是 Metaspace 的一部分,默认大小为 1G。所以其实 Compressed Class Space 这个名字取得很误导,压缩的并不是 Klass,而是 Klass*

metaspace元数据存储空间组成?

  • Klass Metaspace
  • NoKlass Metaspace

Klass Metaspace就是用来存klass的,klass是我们熟知的class文件在jvm里的运行时数据结构,不过有点要提的是我们看到的类似A.class其实是存在heap里的,是java.lang.Class的一个对象实例。这块内存是紧接着Heap的,和我们之前的perm一样,这块内存大小可通过-XX:CompressedClassSpaceSize参数来控制,这个参数前面提到了默认是1G,但是这块内存也可以没有,假如没有开启压缩指针就不会有这块内存,这种情况下klass都会存在NoKlass Metaspace里,另外如果我们把-Xmx设置大于32G的话,其实也是没有这块内存的,因为会这么大内存会关闭压缩指针开关。还有就是这块内存最多只会存在一块。

NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。这块内存是必须的,虽然叫做NoKlass Metaspace,但是也其实可以存klass的内容,上面已经提到了对应场景。

Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块。如果Klass Metaspace用完了,那就会OOM了,不过一般情况下不会,NoKlass Mestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。


2、jstat监控服务器内存以及gc情况

jstat-gc${pid} 10001000-gc:将显示与垃圾收集相关的统计信息${pid}:目标JVM进程ID1000:每10,00毫秒(即1秒)将打印一次统计信息。1000:将打印1000次迭代的统计信息S0C:第一个幸存区的大小S1C:第二个幸存区的大小S0U:第一个幸存区的使用大小S1U:第二个幸存区的使用大小EC:伊甸园区的大小EU:伊甸园区的使用大小OC:老年代大小OU:老年代使用大小MC:方法区大小MU:方法区使用大小CCSC:压缩类空间大小CCSU:压缩类空间使用大小YGC:年轻代垃圾回收次数YGCT:年轻代垃圾回收消耗时间FGC:老年代垃圾回收次数FGCT:老年代垃圾回收消耗时间GCT:垃圾回收消耗总时间



jstat命令样例:

在启动各个数据源物理表统计数据量的任务后,发现压缩空间巨增到700多M

程序代码并发流程如下:

排查出现主动加载类的代码段如下:


3、优化方案,全局缓存加载过的驱动


相关文章
|
3月前
|
关系型数据库 MySQL Java
【IDEA】java后台操作mysql数据库驱动常见错误解决方案
【IDEA】java后台操作mysql数据库驱动常见错误解决方案
127 0
|
2月前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
在9月20日2024云栖大会上,阿里云智能集团副总裁,数据库产品事业部负责人,ACM、CCF、IEEE会士(Fellow)李飞飞发表《从数据到智能:Data+AI驱动的云原生数据库》主题演讲。他表示,数据是生成式AI的核心资产,大模型时代的数据管理系统需具备多模处理和实时分析能力。阿里云瑶池将数据+AI全面融合,构建一站式多模数据管理平台,以数据驱动决策与创新,为用户提供像“搭积木”一样易用、好用、高可用的使用体验。
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
|
2月前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
77 4
|
6月前
|
SQL druid Java
线程池相关故障问题之Druid数据库连接池中,为何需要设置TransactionTimeout
线程池相关故障问题之Druid数据库连接池中,为何需要设置TransactionTimeout
181 0
|
3月前
|
Java 关系型数据库 MySQL
如何用java的虚拟线程连接数据库
本文介绍了如何使用Java虚拟线程连接数据库,包括设置JDK版本、创建虚拟线程的方法和使用虚拟线程连接MySQL数据库的示例代码。
78 6
如何用java的虚拟线程连接数据库
|
3月前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
阿里云瑶池在2024云栖大会上重磅发布由Data+AI驱动的多模数据管理平台DMS:OneMeta+OneOps,通过统一、开放、多模的元数据服务实现跨环境、跨引擎、跨实例的统一治理,可支持高达40+种数据源,实现自建、他云数据源的无缝对接,助力业务决策效率提升10倍。
|
4月前
|
数据库 数据库管理
qt对sqlite数据库多线程的操作
本文总结了在Qt中进行SQLite数据库多线程操作时应注意的四个关键问题,包括数据库驱动加载、加锁、数据库的打开与关闭,以及QsqlQuery变量的使用。
247 1
|
4月前
|
存储 人工智能 Cloud Native
云栖重磅|从数据到智能:Data+AI驱动的云原生数据库
阿里云数据库重磅升级!元数据服务OneMeta + OneOps统一管理多模态数据
|
5月前
|
SQL Java 数据库连接
java连接数据库加载驱动到java项目
该博客文章介绍了如何在Java项目中通过代码加载数据库驱动并连接SQL Server数据库,包括具体的加载驱动和建立数据库连接的步骤,以及如何将驱动包添加到Java项目的构建路径中。
|
5月前
|
数据库连接 数据库
实现加载驱动、得到数据库对象、关闭资源的代码复用,将代码提取到相应的工具包里边。优化程序
该博客文章展示了如何通过创建工具类`Connectiontools`实现数据库连接、语句执行以及资源关闭的代码复用,以优化程序并提高数据库操作的效率和安全性。