估算數據庫數據在java中內存占用


估算數據庫數據在java中內存占用

首先我們需要了解java中Class的內存結構

 

 

第一個Class頭的8個字節:這個字節存儲了比如這個實例目前的鎖信息、目前屬於的堆類型,初始化進度等等。第二個區域,oop指針,這個字段存儲的是這個類的定義,就比如Java反射可以拿到字段名稱,方法名稱這些值都是存儲在這個指針所指向的定義中。第三個區域,數據區域,存放數據的區域,這里的結構區分主要是兩種:數組和非數組。如果是數組,數據區域中還會包含這個數組的大小。
我們大概說明了Class內存結構模型,這個時候對計算大小有個基本的方案了。我們可以根據Class定義的結構進行計算此實例占用的大小了。

 

以上是一個通用數據測試結論,估大家參考。

 

數據庫記錄占用的空間大小比較好算,比如一個int占用4字節,bigint占用8字節,date占用3字節,datetime占用8字節,varchar是變長字節等。如果不想精確計算,在數據庫中通過統計信息也可以比較輕松的知道表總共占用的空間及每條記錄平均行長。

 

當我們用JDBC訪問數據庫時,經常會被問到內存溢出的問題,由於java是面向對象的語言,用JVM來自動內存回收,不能按普通方法計算內存,本文給出一個估算內存的思路和參考答案

 

先給出普通JDBC中數據庫對象與內存的映射關系

 

MySQL

Oracle

JDBC

Int

 

Integer

Int unsigned

 

Long

BigInt

 

Long

BigInt unsigned

 

BigInteger

Decimal

Number

BigDecimal

Varchar

Varchar2

String

Date

 

Date

Datetime

Date

Timestamp

Timestamp

Timestamp

Timestamp

Clob

Clob

String

Blob

blob

Byte[]

Text

Clob

String

float

binary_float

float

double

binary_double

double

 

上面這個比較好理解,接下來我們需要JAVA常用對象的內存占用空間,這個可以通過JDK 5 開始提供的Instrumentation 接口來完成,也可以通過開源的sizeOf.jar 來測試,筆者是通過sizeOf.jar驗證的。測試結果數據如下:

 

對象

64位 JVM 壓縮指針

 64位 JVM 非壓縮指針

Integer         

16

24

Long           

24

24

Object         

16

16

Date           

24

32

Timestamp      

32

40

String_0       

48

64

String_1       

56

72

String_10      

72

88

String_100     

248

264

StringBuilder  

24

32

BigDecimal     

40

48

BigInteger     

64

80

HashMap        

128

216

HashMap_0      

72

96

HashMap_100    

576

1112

HashMap_10000  

65600

131160

ArrayList      

80

144

ArrayList_0    

40

64

ArrayList_100  

440

864

ArrayList_10000

40040

80064

LinkedList     

48

80

LinkedHashMap  

96

144

ClassA         

32

40

ClassB         

40

48

ClassC         

40

56

 

 

由於現在主機一般都是64位, 64位JVM從JDK1.6.45開始,當JVM最大內存小於32GB時,自動打開壓縮指針特性,這樣對象的內存占用空間少很多,由上表可以看出,至少減少1/3的空間。

 

下面我們結合數據庫數據來測試

假如mysql數據庫有一張emp表,結構如下:

 

 CREATE TABLE `emp` (

`id` int(11) NOT NULL,

`setup_time` datetime DEFAULT NULL,

`setup_date` dateDEFAULT NULL,

`name` varchar(16) DEFAULT NULL,

‘unit_price’  decimal(18,2) DEFAULT 0,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

 

樣本數據如下:

hm.put("id", 32466);
hm.put("name", "鴻運汽車");
hm.put("setupDate", new Date());
hm.put("setupTime", new Timestamp(System.currentTimeMillis()));
hm.put("unitPrice", new BigDecimal(100.00));

按上面樣本數據計算,有效數據約50字節

在java里轉換為HashMap和Emp對象測試空間如下

 

對象

64位 JVM 壓縮指針

 64位 JVM 非壓縮指針

HashMap_empty  

48  

96

HashMap_full   

800

1040

Emp_empty      

56

87

Emp_full       

280

362

 

 

從上面測試結果看,數據到JAVA里占用的空間增加了許多,在64位壓縮指針下,如果存到HashMap,需要800字節,空間是數據庫約16.6倍,如果存為Emp普通對象,需要280字節,是數據庫的5倍。

 

如果我們是一個分頁從數據庫讀取emp信息,每頁顯示50條記錄,用List保存,HashMap需要39KB,emp對象需要13KB。

 

以上是一個通用數據測試結論,估大家參考。

下面是測試代碼:

 

package src.test.java;

import net.sourceforge.sizeof.SizeOf;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.*;

public class SizeOfTest {
static {
SizeOf.skipStaticField(true); //java.sizeOf will not compute static fields
//SizeOf.skipFinalField(true); //java.sizeOf will not compute final fields
//SizeOf.skipFlyweightObject(true); //java.sizeOf will not compute well-known flyweight objects
}

public static void main(String[] args) throws SQLException, IOException, IllegalAccessException {
SizeOfTest ts = new SizeOfTest();
ts.objectSize();
ts.dataSize();
System.out.println("ok");
}

public void objectSize() {
System.out.println("Integer:" + SizeOf.deepSizeOf(new Integer(22)));
System.out.println("Long:" + SizeOf.sizeOf(new Long(33L)));
System.out.println("Object:" + SizeOf.sizeOf(new Object()));
System.out.println("Date:" + SizeOf.sizeOf(new Date()));
System.out.println("Timestamp:" + SizeOf.sizeOf(new Timestamp(System.currentTimeMillis())));
System.out.println("String_0:" + SizeOf.deepSizeOf(new String()));
System.out.println("String_1:" + SizeOf.deepSizeOf(new String("1")));
System.out.println("String_10:" + SizeOf.deepSizeOf(new String("213214555")));
System.out.println("String_100:" + SizeOf.deepSizeOf("6743222211"));
System.out.println("StringBuilder:" + SizeOf.deepSizeOf(new StringBuilder()));
System.out.println("BigDecimal:" + SizeOf.deepSizeOf(new BigDecimal("21313")));
System.out.println("BigInteger:" + SizeOf.deepSizeOf(new BigInteger("34535643")));
System.out.println("HashMap:" + SizeOf.deepSizeOf(new HashMap()));
System.out.println("HashMap_0:" + SizeOf.deepSizeOf(new HashMap(0)));
System.out.println("HashMap_100:" + SizeOf.deepSizeOf(new HashMap(100)));
System.out.println("HashMap_10000:" + SizeOf.deepSizeOf(new HashMap(10000)));
System.out.println("ArrayList:" + SizeOf.deepSizeOf(new ArrayList()));
System.out.println("ArrayList_0:" + SizeOf.deepSizeOf(new ArrayList(0)));
System.out.println("ArrayList_100:" + SizeOf.deepSizeOf(new ArrayList(100)));
System.out.println("ArrayList_10000:" + SizeOf.deepSizeOf(new ArrayList(10000)));
System.out.println("LinkedList:" + SizeOf.deepSizeOf(new LinkedList<Object>()));
System.out.println("LinkedHashMap:" + SizeOf.deepSizeOf(new LinkedHashMap<Object, Object>()));

System.out.println("Class1:" + SizeOf.deepSizeOf(new Text1()));
System.out.println("Class2:" + SizeOf.deepSizeOf(new Text2()));
System.out.println("Class3:" + SizeOf.deepSizeOf(new Tlass3()));

}

public void dataSize() throws IOException, IllegalAccessException {
HashMap hm = new HashMap();
System.out.println("HashMap_empty:" + SizeOf.deepSizeOf(hm));
hm.put("id", 32466);
hm.put("name", "鴻運汽車");
hm.put("setupDate", new Date());
hm.put("setupTime", new Timestamp(System.currentTimeMillis()));
hm.put("unitPrice", new BigDecimal(100.00));
System.out.println("HashMap_full:" + SizeOf.deepSizeOf(hm));
Emp emp = new Emp();
System.out.println("Emp_empty:" + SizeOf.deepSizeOf(emp));
emp.setId(32466);
emp.setName("鴻運汽車");
emp.setSetupDate(new Date());
emp.setSetupTime(new Timestamp(System.currentTimeMillis()));
emp.setUnitPrice(new BigDecimal(100.00));
System.out.println("Emp_full_deepSizeOf:" + SizeOf.deepSizeOf(emp));
System.out.println("Emp_full_sizeOf:" + SizeOf.sizeOf(emp));
}

class Text1 {
}

class Text2 extends Text1 {
}

class Tlass3 extends Text2 {
}

class Emp {
private Integer id;
private Timestamp setupTime;
private Date setupDate;
private String name;
private BigDecimal UnitPrice;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Timestamp getSetupTime() {
return setupTime;
}

public void setSetupTime(Timestamp setupTime) {
this.setupTime = setupTime;
}

public Date getSetupDate() {
return setupDate;
}

public void setSetupDate(Date setupDate) {
this.setupDate = setupDate;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public BigDecimal getUnitPrice() {
return UnitPrice;
}

public void setUnitPrice(BigDecimal unitPrice) {
UnitPrice = unitPrice;
}
}
}

在上述代碼測試中,還需要添加RUN啟動JVM options配置,不加配置會報錯:



 這是因為 SizeOf 用到了 JDK1.5 后新加入的 java.lang.instrument.Instrumentation 接口,所以需要為TestSize 設置 VM arguments

我的SizeOf.jar在F盤*路徑下,參數配置為:-javaagent:F:\2019java\java-sizeOf\SizeOf.jar
  1. 添加完成后,重新運行TestSize類

     

     可以看到,數據在java中占用內存都打印出來,默認為Byte字節,可自由轉換

    KB=(Byte)*1024

    MB=(Byte)*1024*1024

    在上述打印中,細心的小伙伴已經關注到了最后兩行:

    Emp_full_deepSizeOf:280
    Emp_full_sizeOf:40

    這塊為什么都是接收,使用不同函數后,得到的字節卻不同呢,原因如下:

    這3行的使用方法基本包含了這個項目的所有內容,1.計算一層內存占用大小,2.計算所有引用關系包含的內存占用。
    newInstance的初始化里面初始化了3種不同的計算Class實例大小的方法:

     按照這個順序進行優先初始化,如果失敗了才會使用后面的方法。從前到后也是建議的使用順序,反射的效率是最低的。

     

    根據這個簡單測試,我們可以總結一個結論:

    1.數據庫記錄放在JAVA里,用對象(ORM一般的處理方式)需要3-4倍左右的內存空間,用HashMap這種KV保存需要10倍空間;

    2.如果你主要數據是text大文本,那空間一般可以按2倍估算。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM