200w的数据量能够占多大内存?发生了OOM如何进行JVM调优? 您所在的位置:网站首页 ios146占多大内存 200w的数据量能够占多大内存?发生了OOM如何进行JVM调优?

200w的数据量能够占多大内存?发生了OOM如何进行JVM调优?

2024-07-10 20:49| 来源: 网络整理| 查看: 265

由OOM牵扯出的JVM分析与调优案例 案例介绍200w数据能占多大的内存OOM异常点分析定位异常点代码层面优化的过程JVM优化逻辑梳理

案例介绍

A平台是一个数据转换和转发平台,需要从数据库读取200w数据,进行数据转换(包括数据清理),将转换后的数据转成json格式,转换后的json格式转成字节数组,然后转发到其他平台。起初这个平台在数据量不大的时候没有任何问题,但是当读取200w数据时就会出现OOM。

200w数据能占多大的内存

当出现OOM的时候,我们会想到是什么导致的OOM。我们首先计算一下200w数据能够占有多大的内存空间。经过计算一个对象将近有4KB的大小。(5个String (每个字符串的长度很长) 2个Long 一个Integer 一个Boolean)。对象内存的计算方法:对象内存=对象头+实例数据+填充数据。未开启指针压缩的对象头一般是12B(数组16B)。 举个例子:如何计算内存(可以通过Instrumentation接口来测试) String str=“abcdefg”,这个占用多少字节?首先String的构造(int hash和一个char数组)对象=对象头(12)+实例化数据(4(int)+4(char指针))+填充(4)=24(原来是20 不满足,填充了4个字节)。所以String对象占用24字节,那么空字符串占用=24+char[]数组(16(对象头))=40。 那么str的内存=40+7乘2(char 占两个)=54 +2(填充)=56

HashMap的内存计算=table数组+table数组中每个元素entry的内存。ArrayLsit的计算方式同理 由于扩容,List和map中未使用的空间依然很大。

由此经过数据库读取的200w数据转化成List 的内存大小为 4KB*200w约等于8GB(mybatis返回的List的实现其实是ArrayList,这里没有算入)。 OOM异常点分析

查看日志发现,后台打印出频繁的Full GC,并且GC后的可使用内存并没有发生变化。经过多次Full GC之后 OOM。此时并不知道什么原因,我将虚拟内存增大至25G,在12min后终于执行成功。 这么长时间,这么大内存。运维手持40米砍刀走来。。。。

定位异常点

首先8G的内存是经过多次转换的,从数据转换到json的List,每个对象都是new出来的强引用,GC无法回收,随着对象的不断增加,Full GC频繁,因此造成8G数据内存吃下25G的内存容量(个人分析)。通过jamp jstate命令分析,dump转储内存快照,用Mat进行可视化,发现List太长。

代码层面优化的过程 数据库读取使用limit来限制读取的数据量(分批次读取数据,30w为一组) 但是出现了 java.lang.ArrayIndexOutOfBoundsException:-1” 的异常,这里分析原因是每个对象转换成json也是存在一个数组中,这个数组的下标是int类型,由此推断超过了int的范围。(不知道为什么一次性读取确成功了,而分批次却出现这个问题)省去转换成json这一步(写入txt文件) 直接写入文件中(txt文件可以存储几亿行数据,满足需求),由系统命令压缩(gzip),然后变成字节数组(让下游服务修改了逻辑 - -!)。提升时间效率(优化数据库查询) 数据库Limit读取是可以优化的点,因为Limit 100000 ,需要扫描定位到1000000行数据,如果数据量达到千万级,每次查询均需要扫描,性能会大大降低。使用cursor(游标),cursor适用于顺序且不支持跳页,也就是说每次查询的最后一条记录,自动作为下一次的起始记录,这样省去了扫描操作。通过游标读取,每30w数据量写入文件一次。读取完成后进行一次System.gc()。

经过此次优化流程可以将内存将至12G 时间稳定在6min中

我以为结束了,但是我查看了GC日志,YoungGC依旧频繁,Full GC依旧存在,虽然没有那么频繁,但是每次有5s左右的停顿。这仍不能满足我们的要求。

JVM优化

查看日志显示,Yong GC仍然频繁,并且平均8次左右触发一次Full GC。经过分析:一次Full GC相当于Young GC 、Old GC和元空间GC同时进行,耗费的时间可想而知。而且频繁的Young GC 也表明新生代的内存在不断的收集中。经过7、8次Yong GC之后通过动态对象年龄判断会直接进入老年代,由于对象的不可清除,导致老年代内存增加,担保失败(HandlePromotionFailure)而导致Full GC。 经过计算,30w数据量大约在1.2G。这里需要调整老年代、 eden、 s0和s1的内存。由于对象是一直存活的,并且尽可能减少的Full GC的次数。最终将虚拟机调整至5G,老年代:新生代=7:3 、eden:s0=5:1。 调整后的虚拟机内存至5G,总的处理时间为4min。 关于参数的调整:List属于大对象一开始就会进入老年代,而在List进行数据转换的时候依然会产生很多内存的消耗,所以我这里调整了一下eden和survivor的比例。并且增大老年代的比例。

关于从25G降至12G再降低至6G,时间从12min降到6min再降低至4min,为何内存降低竟有如此效果? 25G到12G是因为改变了代码逻辑,所以时间降低。 12G到6G。根据日志看,其实12G和6G区别在于Young GC的次数,显然12G的Yong GC次数较多,而Full GC的次数大致相同。我推断:由于12G的新生代内存较大,所以Yong GC的时间较长。而将内存将至6G,并且调节老年代的比例、eden和survivor的比例,使得新生代内存降低。同时List是属于大对象,一开始就会进入老年代,所以12G和6G的 Full GC次数大致相同。

逻辑梳理

OOM定位到GC日志异常,dump GC日志使用MAT软件分析。优化代码逻辑(批次从数据库读取、写入文件),稳定以后再根据GC日志进行JVM调整。 整个定位到优化的流程,使得我对jvm调优有一些经验,另外开始关注代码本身

对于List数据结构很多由ArrayLit实现(mybaits返回的Lsit就是ArrayList实现),由于ArrayLsit的1.5倍扩容,在数据量不大的情况下不会有什么问题。但是当数据量上去了以后,ArrayList所占用的内存将变的很大,并且空间使用效率也不高,1.5倍扩容后也会存在很多未使用的空间。大流量情况下尽量对ArrayList指定大小。数据处理可以放入文件中,然后使用系统内部的gzip命令进行压缩,唯一的缺点就是可能要修改下游逻辑数据库的Limit在数据量增大的情况下,由于会全表扫描,也会降低性能根据不同的需求对JVM进行调优

Limit性能优化以及分页数据性能优化 几百万的数据放入内容会把系统撑爆吗 一个对象占用多大内存 Full GC 和 Minor GC Minor GC与Full GC分别在什么时候发生? JVM从入门到入土之实战JVM调优(一)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有