什么是堆外內存
堆外內存也叫直接內存(Direct Memory)赊堪,并不是JVM內存區(qū)域的一部分祝旷,也不是《Java虛擬機規(guī)范》中定義的內存區(qū)域履澳。
JDK1.4引入了NIO包(new input/output),引入了一種基于通道(Channel)和緩沖區(qū)(Buffer)的IO方式怀跛,可以使用native函數直接分配堆外內存距贷,然后通過一個存儲在Java堆里的DirectByteBuffer對象作為這塊內存的引用進行操作。在一些場景中可以顯著提高性能吻谋,因為避免了在Java對和Native堆中來回復制數據忠蝗。
申請堆外內存時,如果空間不足漓拾,會拋出java.lang.OutOfMemoryError: Direct buffer memory
堆外內存的優(yōu)勢
- 堆外內存受操作系統(tǒng)管理阁最,不受JVM管理,可以減少JVM GC壓力骇两,減輕JVM垃圾回收對程序性能的影響速种。
- 提高IO效率
- 堆內內存屬于用戶態(tài)的緩沖區(qū),堆外內存屬于內核態(tài)的緩沖區(qū)
- 如果從堆向磁盤/網卡寫數據低千,需要CPU把數據復制從用戶態(tài)復制到內核態(tài)配阵,再由操作系統(tǒng)DMA從內核態(tài)復制到磁盤/網卡。
-
如果直接從堆外內存向磁盤/網卡寫數據,可以省略掉用戶態(tài)到內核態(tài)的復制
堆外內存的缺點
- 需要自行分配和回收內存闸餐,增加了復雜度
- 如果回收內存處理不當饱亮,容易引發(fā)內存泄漏,并且難以排查
堆外內存引發(fā)內存泄漏排查: https://mp.weixin.qq.com/s/55slokngVRgqEav6c3TxOA
堆外內存的使用
- 調用java.nio.ByteBuffer的allocateDirect方法舍沙,最終會調用sun.misc.Unsafe#allocateMemory近上,相當于C++的malloc函數
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
- 可以通過設置-XX:MaxDirectMemorySize=10M控制堆外內存的使用上限
使用場景
- 常駐在本地內存的本地緩存,比如國家拂铡、省份壹无、城市信息等,長期駐扎在老年代感帅。
堆外內存的回收
- 等到full gc的時候垃圾回收
堆外內存對應的Java對象引用在堆里斗锭,受JVM垃圾回收的管理,一旦該對象引用被回收失球,JVM會釋放對應的堆外內存 - 程序手動回收
private void clean (ByteBuffer byteBuffer) {
if (byteBuffer.isDirect()) {
((DirectBuffer)byteBuffer).cleaner().clean();
}
}
堆外內存緩存框架
https://mp.weixin.qq.com/s/YYMnXRFyozHpsmRwN-vvGQ
- 引入依賴
<dependency>
<groupId>org.caffinitas.ohc</groupId>
<artifactId>ohc-core</artifactId>
<version>0.7.4</version>
</dependency>
- 自定義序列化岖是、反序列器,調用put实苞、get方法進行寫入和讀取豺撑,操作類似HashMap
import org.apache.commons.io.Charsets;
import org.caffinitas.ohc.CacheSerializer;
import org.caffinitas.ohc.OHCache;
import org.caffinitas.ohc.OHCacheBuilder;
import java.nio.ByteBuffer;
public class OhcDemo {
public static void main(String[] args) {
OHCache ohCache = OHCacheBuilder.<String, String>newBuilder()
.keySerializer(OhcDemo.stringSerializer)
.valueSerializer(OhcDemo.stringSerializer)
.build();
ohCache.put("hello","why");
System.out.println("ohCache.get(hello) = " + ohCache.get("hello"));
}
public static final CacheSerializer<String> stringSerializer = new CacheSerializer<String>() {
public void serialize(String s, ByteBuffer buf) {
// 得到字符串對象UTF-8編碼的字節(jié)數組
byte[] bytes = s.getBytes(Charsets.UTF_8);
// 用前16位記錄數組長度
buf.put((byte) ((bytes.length >>> 8) & 0xFF));
buf.put((byte) ((bytes.length) & 0xFF));
buf.put(bytes);
}
public String deserialize(ByteBuffer buf) {
// 獲取字節(jié)數組的長度
int length = (((buf.get() & 0xff) << 8) + ((buf.get() & 0xff)));
byte[] bytes = new byte[length];
// 讀取字節(jié)數組
buf.get(bytes);
// 返回字符串對象
return new String(bytes, Charsets.UTF_8);
}
public int serializedSize(String s) {
byte[] bytes = s.getBytes(Charsets.UTF_8);
// 設置字符串長度限制,2^16 = 65536
if (bytes.length > 65535)
throw new RuntimeException("encoded string too long: " + bytes.length + " bytes");
return bytes.length + 2;
}
};
}