实际开发中,如果在内存中一次性放入大量的数据,一旦超过内存设置的最大值,很容易出现堆溢出的情况,在当前环境允许的情况下,通过模拟对象估算生产实际对象在内存中占用空间大小就显得很有必要了。
问题:批处理数据时使用lambda同步流导致CPU飙满,内存占用超过90%,极大侵占了其他的业务处理的资源
解决方式:改写数据处理方式,控制内存占用量和CPU使用量
关键点:确定数据处理规模,估算数据在内存中占用的空间大小,适时触发GC回收机制,及时清理结果。
首先,模拟数据量在内存中占用的大小:
1.添加依赖
2.编写测试用例:
package space;
import com.carrotsearch.sizeof.RamUsageEstimator;
import org.apache.commons.lang3.RandomStringUtils;
import java.lang.String;
import java.util.*;
/**
* 测试对象占用的空间
* Created by ZHAYA008 on 2021/12/17.
*/
public class SpaceUsage {
public static void main(String[] args) {
System.out.println("--------------------------基本类型---------------------------------------------------------------------");
//估计给定对象的“浅层”内存使用情况。 对于数组,这将是数组存储占用的内存(后面没有子引用)。 对对象来说是字段占用的内存
System.out.println("给定byte数组对象的“浅层”内存使用情况:"+RamUsageEstimator.shallowSizeOf(new byte[]{1,2,3,4}));
System.out.println("给定short数组对象的“浅层”内存使用情况:"+RamUsageEstimator.shallowSizeOf(new short[]{1,2,3,4}));
System.out.println("给定int数组对象的“浅层”内存使用情况:"+RamUsageEstimator.shallowSizeOf(new int[]{1,2,3,4}));
System.out.println("给定long数组对象的“浅层”内存使用情况:"+RamUsageEstimator.shallowSizeOf(new long[]{1,2,3,4}));
System.out.println("给定char数组对象的“浅层”内存使用情况:"+RamUsageEstimator.shallowSizeOf(new char[]{1,2,3,4}));
System.out.println("给定char数组对象的“浅层”内存使用情况:"+RamUsageEstimator.shallowSizeOf(new String[]{"1","2","3","4"}));
//估计给定对象的“浅层”内存使用情况。 是对shallowSizeOf()的内存占用汇总
System.out.println("给定多对象的“浅层”内存使用情况:"+RamUsageEstimator.shallowSizeOfAll(1,2,3,4));
//shallowSizeOfInstance返回实例的大小,实例大小取决于对象元素的多少
System.out.println("--------------------------包装类型---------------------------------------------------------------------");
System.out.println("返回Byte对象实例的大小:"+RamUsageEstimator.shallowSizeOfInstance(Byte.class));
System.out.println("返回Short对象实例的大小:"+RamUsageEstimator.shallowSizeOfInstance(Short.class));
System.out.println("返回Integer对象实例的大小:"+RamUsageEstimator.shallowSizeOfInstance(Integer.class));
System.out.println("返回Long对象实例的大小:"+RamUsageEstimator.shallowSizeOfInstance(Long.class));
System.out.println("返回Character对象实例的大小:"+RamUsageEstimator.shallowSizeOfInstance(Character.class));
System.out.println("返回String对象实例的大小:"+RamUsageEstimator.shallowSizeOfInstance(String.class));
//返回不受支持的JVM特性集,这些特性可以改进估计。
System.out.println("返回不受支持的JVM特性集:"+RamUsageEstimator.getUnsupportedFeatures().toString());
System.out.println("返回受支持的JVM特性集:"+RamUsageEstimator.getSupportedFeatures().toString());
Iterator iterator = RamUsageEstimator.getSupportedFeatures().iterator();
while(iterator.hasNext()){
System.out.println("返回受支持的JVM特性集:"+iterator.next());
}
//返回可供读识单位的大小
System.out.println("MiniproUser对象大小:"+RamUsageEstimator.shallowSizeOfInstance(MiniproUser.class));
System.out.println("可供读识单位:"+RamUsageEstimator.humanReadableUnits(RamUsageEstimator.shallowSizeOfInstance(MiniproUser.class)));
System.out.println("--------------------------对象集合类型大小---------------------------------------------------------------------");
List<MiniproUser> list = new ArrayList<MiniproUser>();
int count = 5000;
for(int i = 0;i<count;i++){
MiniproUser mu = MiniproUser.builder().id(String.valueOf((int)(Math.random()* 1000000000))).avatar(RandomStringUtils.randomAlphanumeric(10))
.memberCode(RandomStringUtils.random(9))
.nickname(RandomStringUtils.random(5))
.openid(RandomStringUtils.random(10))
.unionid(RandomStringUtils.random(8))
.createTime(new Date()).updateTime(new Date()).build();
list.add(mu);
}
System.out.println(count+"个对象:"+list);
System.out.println(count+"个对象的大小:"+RamUsageEstimator.humanReadableUnits(RamUsageEstimator.sizeOf(list)));
System.out.println("--------------------------集合类型大小---------------------------------------------------------------------");
System.out.println("ArrayList:"+RamUsageEstimator.sizeOf(new ArrayList()));
System.out.println("HashMap:"+RamUsageEstimator.sizeOf(new HashMap()));
System.out.println("LinkedHashMap:"+RamUsageEstimator.sizeOf(new LinkedHashMap()));
System.out.println("LinkedHashSet:"+RamUsageEstimator.sizeOf(new LinkedHashSet()));
System.out.println("HashSet:"+RamUsageEstimator.sizeOf(new HashSet()));
System.out.println("TreeSet:"+RamUsageEstimator.sizeOf(new TreeSet()));
System.out.println("TreeMap:"+RamUsageEstimator.sizeOf(new TreeMap()));
}
}
3.测试结果
由于经过多次测试,发现对象在内存中的大小基本呈线性关系,这点可以自行验证,基本不会差太多,对大数据量的估算很有帮助。
5000个对象占用3.1M,50000->31M,500000->310M,由于处理的目标数据大概在500000左右,因此占用内存大小可以接收。
原代码:
改造后:
同步流只对对象进行处理,处理完后其实可以调用一下
Runtime.getRuntime().gc();
使不使用的对象能够尽快回收,但是发现会少几条数据,原因未知,所以放在了最后回收。
接上:
然后数据分割,根据数据分片大小对数据做多线程处理,线程数取
Runtime.getRuntime().availableProcessors();
网上有资料说这种获取方式在i9上会出现获取为null的情况,因此最好做一个获取处理,防止程序直接报错。
修改完后,完整模拟测试3800条数据,调用外部接口设置sleep时长600ms,使用lambda执行总时间平均在128s,使用修改后的降低到115s,同时CPU下降了30%,内存使用量降低了40%。对半夜处理数据来说够用。