花式计算java百万数据对象在内存中到底占用多少内存

背景:

最近在做一些总结,总结一些做过的一些设计,方案,踩空啥的,遇到的一个问题,就是做过的一个功能可以支持百万数据全量计算,那百万数据在内存中占多少内存呢,计算时长多少呢?毫秒级加载是怎么实现的?

PS:本地测试1百万数据(JavaBean)计算排序大概需要3-4秒左右,毫秒级加载是在浏览器一次请求到后端返回到浏览器大概是60~70ms.那百万数据在内存中占多少内存呢?

需求背景:

这个问题的背景是有个需求,做个内网的说说发布平台,类似于QQ空间或者新浪微博,只不过功能相对简单,
其中有个模块是首页最热模块,意思是根据一定的算法规则计算说说的热度然后进行排序,规则如下:
热度展示权重:
7日内动态50+评论数0.3+点赞数0.2
7-15天内容
40+评论数0.4+点赞数0.2
15-30天内容30+评论数0.4+点赞数0.3
30-60天内容
20+评论数0.4+点赞数0.4
60天以后内容10+评论数0.5+点赞数*0.4

按照计算权重由高到低进行排序,权重相同则发布时间>评论数>点赞数
由于此规则的背景是计算所有数据的权重,因此需要全量数据加载然后进行计算排序生成最终的热度排行。

本文将以上述需求方案设计中的一个计算中间模型为例,剖析百万数据在内存中占了多少内存,并介绍不同方式计算出来的内存占用情况。

HottestDataBean:
hid:动态id(long)
hweight:动态权重值(int),这个字段实际开发过程中是int,与上述文档有点诧异
htype:动态类型(short动态,赞扬,吐槽)
hcreatetime:动态创建时间(long)
hcommentnum:针对hid这条动态的评论数量(int)
hlikenum:针对hid这条动态的点赞数量(int)

基础说明
byte:1个字节 8位 -128~127
short :2个字节 16位
int :4个字节 32位
long:8个字节 64位

计算方式1.手动计算
8(对象头) + 8(hid)+ 8(hcreatetime) + 4(hweight)+ 4(hcommentnum)+hlikenum(4)+ 2(htype) = 38,补齐40b
1kb = 1024b 1m = 1024kb
10241024/40=26214 ;1m 可以装26000左右个对象
10000000 / 26000 = 38.46;1百万数据大概占用38.5M左右
16(64位对象头) + 8
2 + 43 + 2 = 46,补齐48b
1024
1024/48=21845;1m 可以装21845左右个对象
10000000 / 21845 = 45.78;1百万数据大概占用45.78左右

计算方式2.工具类
引入:

org.apache.lucene
lucene-core
4.0.0

该依赖有工具类可以计算对象所占内存,不过是以JVM标准计算的,实际可能不一定完全准确,不过差不多;
以1百万HottestDataBean对象列表List进行计算,下面看一下计算结果
该对象及其引用树上的所有对象的综合大小:52861992 单位字节
该List对象本身在堆空间的大小:24 单位字节
该List对象及其引用树上的所有对象的综合大小,返回可读的结果: 50.4 MB

计算方式3:Jprofiler idea插件查看内存占用情况
准备工作:下载Jprofiler idea插件,下载Jprofiler可执行文件,破解Jprofiler,重启idea

运行结果

计算方式4:自己写测试脚本
准备工作:
1.新建maven-Java项目,maven加入如下插件:


  
    maven-jar-plugin
    2.4
    
      test
      
        
          com.xxx.test.HottestDataBean2
          
          true
        
        false
 
        
          com.xxx.test.HottestDataBean2
        
      
 
    
  
  
    org.apache.maven.plugins
    maven-compiler-plugin
    
      8
      8
    
  

2.源码目录下加入如下代码:

package com.xxx.test;
 
 
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
 
/**
 * @Author fanchunshuai
 * @Date 2019/4/26 14
 * @Description:
 * live最热动态数据展示计算过程中间模型
 */
public class HottestDataBean2 implements Comparable {
 
    /**
     * 动态id
     */
    private long hid;
    /**
     * 动态权重值
     */
    private int hWeight;
    /**
     * 动态类型short动态,赞扬,吐槽
     */
    private short hType;
 
    /**
     * 动态创建时间
     */
    private long hCreateTime;
 
    /**
     * 针对hid这条动态的评论数量
     */
    private int hCommentNum;
 
    /**
     * 针对hid这条动态的点赞数量
     */
    private int hLikeNum;
 
    public long getHid() {
        return hid;
    }
 
    public void setHid(long hid) {
        this.hid = hid;
    }
 
    public int gethWeight() {
        return hWeight;
    }
 
    public void sethWeight(int hWeight) {
        this.hWeight = hWeight;
    }
 
    public short gethType() {
        return hType;
    }
 
    public void sethType(short hType) {
        this.hType = hType;
    }
 
    public long gethCreateTime() {
        return hCreateTime;
    }
 
    public void sethCreateTime(long hCreateTime) {
        this.hCreateTime = hCreateTime;
    }
 
    public int gethCommentNum() {
        return hCommentNum;
    }
 
    public void sethCommentNum(int hCommentNum) {
        this.hCommentNum = hCommentNum;
    }
 
    public int gethLikeNum() {
        return hLikeNum;
    }
 
    public void sethLikeNum(int hLikeNum) {
        this.hLikeNum = hLikeNum;
    }
 
    @Override
    public String toString() {
        return "HottestDataBean{" +
                "hid=" + hid +
                ", hWeight=" + hWeight +
                ", hType=" + hType +
                ", hCreateTime=" + hCreateTime +
                ", hCommentNum=" + hCommentNum +
                ", hLikeNum=" + hLikeNum +
                '}';
    }
 
    public static void main(String[] args) {
        List hottestDataBeans = new ArrayList<>();
        Random random = new Random();
 
        for (int i = 0;i< 1000000;i ++){
            hottestDataBeans.add(getBean(random));
        }
 
        List hottestDataBeanList = hottestDataBeans.stream()
                .sorted(Comparator
                        .comparing((HottestDataBean2 p) -> p.gethWeight())
                        .thenComparing(HottestDataBean2::gethCreateTime)
                        .thenComparing(HottestDataBean2::gethCommentNum)
                        .thenComparing(HottestDataBean2::gethLikeNum))
                .collect(Collectors.toList());
        System.out.println("SizeOfObjectUtils.sizeOf(hottestDataBeanList) = "+sizeOf(hottestDataBeanList));
        try {
            System.out.println("SizeOfObjectUtils.sizeOf(hottestDataBeanList) = "+fullSizeOf(hottestDataBeanList));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
 
    private static HottestDataBean2 getBean(Random random){
        HottestDataBean2 hottestDataBean = new HottestDataBean2();
        hottestDataBean.sethLikeNum(random.nextInt(100));
        hottestDataBean.sethCreateTime(8044701118741935097L);
        hottestDataBean.sethCommentNum(random.nextInt(300));
        int weight = hottestDataBean.gethCommentNum() * 5 + hottestDataBean.gethLikeNum() * 2;
        hottestDataBean.sethWeight(weight);
 
        return hottestDataBean;
    }
 
 
    //
    // 按照计算权重由高到低进行排序,权重相同则发布时间>评论数>点赞数
    @Override
    public int compareTo(HottestDataBean2 o) {
        //比较权重
        if(this.hWeight != o.hWeight){
            return this.hWeight > o.hWeight ? -1 : 1;
        }
 
        //比较发布时间
        if(this.hCreateTime != o.hCreateTime){
            return this.hCreateTime > o.hCreateTime ? -1 : 1;
        }
 
        //比较评论数
        if(this.hCommentNum != o.hCommentNum){
            return this.hCommentNum > o.hCommentNum ? -1 : 1;
        }
 
        //比较点赞数
        if(this.hLikeNum != o.hLikeNum){
            return this.hLikeNum > o.hLikeNum ? -1 : 1;
        }
 
        return 0;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}
        HottestDataBean2 that = (HottestDataBean2) o;
        return hid == that.hid &&
                hWeight == that.hWeight &&
                hType == that.hType &&
                hCreateTime == that.hCreateTime &&
                hCommentNum == that.hCommentNum &&
                hLikeNum == that.hLikeNum;
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(hid, hWeight, hType, hCreateTime, hCommentNum, hLikeNum);
    }
    static Instrumentation inst;
 
    public static void premain(String args, Instrumentation instP) {
        inst = instP;
    }
 
    /**
     * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、

* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;

* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小

* * @param obj * @return */ public static long sizeOf(Object obj) { return inst.getObjectSize(obj); } /** * 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 * * @param objP * @return * @throws IllegalAccessException */ public static long fullSizeOf(Object objP) throws IllegalAccessException { Set visited = new HashSet(); Deque toBeQueue = new ArrayDeque<>(); toBeQueue.add(objP); long size = 0L; while (toBeQueue.size() > 0) { Object obj = toBeQueue.poll(); //sizeOf的时候已经计基本类型和引用的长度,包括数组 size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { //[I , [F 基本类型名字长度是2 if (tmpObjClass.getName().length() > 2) { for (int i = 0, len = Array.getLength(obj); i < len; i++) { Object tmp = Array.get(obj, i); if (tmp != null) { //非基本类型需要深度遍历其对象 toBeQueue.add(Array.get(obj, i)); } } } } else { while (tmpObjClass != null) { Field[] fields = tmpObjClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) //静态不计 || field.getType().isPrimitive()) { //基本类型不重复计 continue; } field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; } /** * String.intern的对象不计;计算过的不计,也避免死循环 * * @param visited * @param obj * @return */ static boolean skipObject(Set visited, Object obj) { if (obj instanceof String && obj == ((String) obj).intern()) { return true; } return visited.contains(obj); } }

3.mvn clean package
4.拷贝target/test.jar到某一文件目录下,运行结果如下:


运行效果

计算方式5:见计算方式4中的Unsafe类,结合计算方式1即可。
当然还有很多方式,本文选取的模型相对简单,计算过程涉及到的更底层的东西相对较少,仅供参考入门哈
大家亲自计算过吗?或者有这方面的困惑吗?可以评论留言哈
参考文章:
https://blog.csdn.net/hykjtt/article/details/79457818
https://blog.csdn.net/tianqishu11/article/details/78823179

你可能感兴趣的:(花式计算java百万数据对象在内存中到底占用多少内存)