Java堆外缓存(一个很有意思的应用)
我们在开发过程中会遇到这样的场景:就是一个服务的各项 JVM 的配置都比较合理的情况下,它的 GC 情况还是不容乐观。分析之后发现有 2 个对象特别巨大,占了总存活堆内存的 90%以上。其中第 1 大对象是本地缓存, GC 之后对象一直存活。然后不久应用就会抛出OutOfMemoryError,那怎样避免这种情况呢?
这次给大家推荐一个比较好用的技术:堆外缓存。哈哈哈,顾名思义就是在Java堆之外的缓存,也就是把一些大的难以被GC回收的对象放到堆之外。PS堆外内存不受,堆内内存大小的限制,只受服务器物理内存的大小限制。这三者之间的关系是这样的:物理内存=堆外内存+堆内内存。
技术大佬的GITHUB地址:https://github.com/snazy/ohc 。
要使用他的技术就先要引用对应的jar包,Maven坐标如下:
<dependency>
<groupId>org.caffinitas.ohc</groupId>
<artifactId>ohc-core</artifactId>
<version>0.7.4</version>
</dependency>
//大神给的使用方式如下:
//Quickstart:
OHCache ohCache = OHCacheBuilder.newBuilder()
.keySerializer(yourKeySerializer)
.valueSerializer(yourValueSerializer)
.build();
上面是Quickstart 看起来使用如此的丝滑(简单),但是上面的代码是填空题,我们看到复制粘贴后代码不能使用后开始。。。。。。此处省略一万字。其实大神写的代码怎么不能用的呢,不要怀疑大神一定是自己的方法不对,我们的口号是?

如果提供的代码复制粘贴不能直接用的工程师不能称之为大神工程师。
但是github上的大神写的东西怎么不能直接用呢?一定是你的思路不对,老司机都知道,大神写代码一定有写单元测试的,要不然不会有那么多人用的(所以要想成为大神单元测试一定要写好),所以把代码拉下来,复制单元测试的东西应该能直接使用 。下面是CTRL+C来的代码。
public static void main(String[] args) {
OHCache ohCache = OHCacheBuilder.<String, String>newBuilder()
.keySerializer(new StringSerializer())
.valueSerializer(new StringSerializer())
.build();
ohCache.put("name","xiaozhang");
System.out.println(ohCache.get("name")); // 结果 xiaozhang
}
static class StringSerializer implements CacheSerializer<String>{
@Override
public void serialize(String value, ByteBuffer buf) {
// 得到字符串对象UTF-8编码的字节数组
byte[] bytes = value.getBytes(Charsets.UTF_8);
// 用前16位记录数组长度
buf.put((byte) ((bytes.length >>> 8) & 0xFF));
buf.put((byte) ((bytes.length) & 0xFF));
buf.put(bytes);
}
@Override
public String deserialize(ByteBuffer buf) {
// 判断字节数组的长度
int length = (((buf.get() & 0xff) << 8) + ((buf.get() & 0xff)));
byte[] bytes = new byte[length];
// 读取字节数组
buf.get(bytes);
// 返回字符串对象
return new String(bytes, Charsets.UTF_8);
}
@Override
public int serializedSize(String value) {
byte[] bytes = value.getBytes(Charsets.UTF_8);
// 设置字符串长度限制,2^16 = 65536
if (bytes.length > 65536)
throw new RuntimeException("encoded string too long: " + bytes.length + " bytes");
// 设置字符串长度限制,2^16 = 65536
return bytes.length + 2;
}
}
上面的代码我们看到这家伙类似Java中的Map 。也就是一个key和value 结构的对象。感觉So easy ,没什么大的用途。简单?那是你想简单了,后面大招来了。
很简单的代码演示如下:
public class MapCasheTest {
static HashMap<String,String> map = new HashMap<>();
public static void main(String[] args) throws Exception {
oomTest();
}
private static void oomTest() throws Exception{
// 休眠几秒,便于观察堆内存使用情况
TimeUnit.SECONDS.sleep(30);
int result = 0 ;
while (true){
String string = new String(new byte[1024*1024]) ;
map.put(result+"",string) ;
result++;
}
}
}
运行一小会就报这个错了,也是文章中刚开始说的那个错误。

然后我们去监控系统的堆栈使用情况如下图(只用一小会就把堆内存快用满了,然后自然系统就报错了)

下面我们使用同样的逻辑写如下代码,很神奇的事情出现了,大跌眼镜的事情出现了,先亮出代码如下:
public static void main(String[] args) throws Exception{
TimeUnit.SECONDS.sleep(30);
OHCache ohCache = OHCacheBuilder.<String, String>newBuilder()
.keySerializer(new Test.StringSerializer())
.valueSerializer(new Test.StringSerializer())
.build();
int result = 0 ;
while (true){
String string = new String(new byte[2048*2048]) ;
ohCache.put(result+"",string) ;
result++;
}
}
程序一直很稳定的运行,没有报错,堆栈运行一上一下,至少程序没报错:

为什么会出现这种情况呢?因为文章开始就讲了这个用的是堆外内存,也就是用的自己电脑的内存,自己电脑的内存目前普通的电脑也有2个G那么大,所以程序一直运行稳定。下面看看我们CPU运行的情况,刚开始有点高,当我把程序关闭CPU使用立马下来了。

哈哈哈,如果是自己测试的时候记得要把自己的工作相关的东西都先保存了,免得你的电脑内存太小,有可能会造成电脑关机。
那么我们在什么情况会使用这个大神的工具呢?就是自己程序有大的对象,并且GC一直无法把这个大对象回收,就可以使用上面的方法,能保证程序稳定运行。具体OH大神是怎么实现这种方式的呢?本文不做研究,他用的技术太深了。
最近ChatGPT比较火,我也问了些问题,回得很好,给个赞。你如果有啥想问的我也可以帮你问问它。

欢迎关注微信公众号:程序员xiaozhang 。会更新更多精彩内容。
Java堆外缓存(一个很有意思的应用)的更多相关文章
- Java堆外内存之一:堆外内存场景介绍(对象池VS堆外内存)
最近经常有人问我在Java中使用堆外(off heap)内存的好处与用途何在.我想其他面临几样选择的人应该也会对这个答案感兴趣吧. 堆外内存其实并无特别之处.线程栈,应用程序代码,NIO缓存用的都是堆 ...
- Java堆外内存之三:堆外内存回收方法
一.JVM内存的分配及垃圾回收 对于JVM的内存规则,应该是老生常谈的东西了,这里我就简单的说下: 新生代:一般来说新创建的对象都分配在这里. 年老代:经过几次垃圾回收,新生代的对象就会放在年老代里面 ...
- 一文深入了解史上最强的Java堆内缓存框架Caffeine
它提供了一个近乎最佳的命中率.从性能上秒杀其他一堆进程内缓存框架,Spring5更是为了它放弃了使用多年的GuavaCache 缓存,在我们的日常开发中用的非常多,是我们应对各种性能问题支持高并发的一 ...
- Java堆外内存管理
Java堆外内存管理 1.JVM可以使用的内存分外2种:堆内存和堆外内存: 堆内存完全由JVM负责分配和释放,如果程序没有缺陷代码导致内存泄露,那么就不会遇到java.lang.OutOfMemo ...
- Java堆外内存之二:堆外内存使用总结
目录: <堆外内存操作类ByteBuffer> <DirectBuffer> <Unsafe(java可直接操作内存(),挂起与恢复,CAS操作)> 有时候对内存进 ...
- google-perftools 分析JAVA 堆外内存
google-perftools 分析JAVA 堆外内存 分类: j2se2011-08-25 21:48 3358人阅读 评论(4) 收藏 举报 javahbasehtml工具os 原文转自:htt ...
- JDBC数据源(DataSource)数据源技术是Java操作数据库的一个很关键技术,流行的持久化框架都离不开数据源的应用。
JDBC数据源(DataSource)的简单实现 数据源技术是Java操作数据库的一个很关键技术,流行的持久化框架都离不开数据源的应用. 2.数据源提供了一种简单获取数据库连接的方式,并能在内部通 ...
- 实战经验 | Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- 超干货!Cassandra Java堆外内存排查经历全记录
背景 最近准备上线cassandra这个产品,同事在做一些小规格ECS(8G)的压测.压测时候比较容易触发OOM Killer,把cassandra进程干掉.问题是8G这个规格我配置的heap(Xmx ...
- Netty之Java堆外内存扫盲贴
Java的堆外内存本来是高贵而神秘的东西,只在一些缓存方案的收费企业版里出现.但自从用了Netty,就变成了天天打交道的事情,毕竟堆外内存能减少IO时的内存复制,不需要堆内存Buffer拷贝一份到直接 ...
随机推荐
- C#使用最小二乘法对多个离散点进行圆拟合
/// <summary> /// 最小二乘法拟合圆,计算拟合圆半径和拟合圆圆心 /// </summary> /// <param name="points& ...
- 大数据下一代变革之必研究数据湖技术Hudi原理实战双管齐下-上
@ 目录 概述 定义 发展历史 特性 使用场景 编译安装 编译环境 编译Hudi 关键概念 TimeLine(时间轴) File Layouts(文件布局) 索引 表类型 查询类型 概述 定义 Apa ...
- Dockerfile 使用 SSH docker build
如果在书写 Dockerfile 时,有些命令需要使用到 SSH 连接,比如从私有仓库下载文件等,那么我们应该怎么做呢? Dockerfile 文件配置 为了使得 Dockerfile 文件中的命令可 ...
- 我的Python基础(二)
python包含6种内奸的序列:列表.元组.字符串.Unicode字符串.buffer对象和xrange对象 列表和元组的主要区别在于,列表可以修改,元组则不能. 索引: 使用负数索引时,最后一个元素 ...
- Go语言核心36讲53
你好,我是郝林. 在2019年的春节来临之际,我恰好也更新完了专栏所有的配图和思考题答案.希望这些可以帮助到你,在新的一年中,祝你新年快乐,Go语言学习之路更加顺利. 基础概念篇 1. Go语言在多个 ...
- Go语言核心36讲31
我们在前两篇文章中讨论了互斥锁.读写锁以及基于它们的条件变量,先来总结一下. 互斥锁是一个很有用的同步工具,它可以保证每一时刻进入临界区的goroutine只有一个.读写锁对共享资源的写操作和读操作则 ...
- (工具) 交叉编译 gperftools及使用
交叉编译gperftools及使用 sudo apt-get install kcachegrind # 导出为 callgrind 格式时需要 sudo apt install doxygen-la ...
- .net core/5/6/7中WPF如何优雅的开始开发
WPF是微软的.net平台中的一个桌面客户端应用程序框架,经常用于企业开发windows桌面客户端,广泛应用于中小企业快速开发一款工具,本人也是比较喜欢利用WPF开发一些小工具. 目录 知名案例 .n ...
- day10 集合——队列(Queue)、Vector & Map集合常用方法 & HashMap的实现原理&二叉树&二叉查找树AVL树&红黑树
集合--List 栈先进后出 队列 先进先出 Queue队列 方法 Queue<Integer> q = new LinkedList<>(); //添加元素 q.add(2) ...
- 私藏!资深数据专家SQL效率优化技巧 ⛵
作者:韩信子@ShowMeAI 数据分析实战系列:https://www.showmeai.tech/tutorials/40 本文地址:https://www.showmeai.tech/artic ...