估算數據庫數據在java中內存占用
首先我們需要了解java中Class的內存結構
我們大概說明了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
-
添加完成后,重新運行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倍估算。