范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

堆外内存这玩意是真不错,我要写进简历了

  你好呀,我是歪歪。
  之前在《3 招将吞吐量提升了 100%,现在它是我的了》这篇文章中,我在 OHC 堆外缓存上插了个眼:
  这次就把这个眼给回收了吧,给你盘一下 OHC。
  之前的文章里面说的是啥场景呢,我们先简单回顾一下。
  就是一个服务的各项 JVM 的配置都比较合理的情况下,它的 GC 情况还是不容乐观。
  然后 dump 了一把内存,一顿分析之后发现有 2 个对象特别巨大,占了总存活堆内存的 76.8%。其中第 1 大对象是本地缓存, GC 之后依旧存活,干都干不掉。
  怎么办呢?
  把缓存对象移到堆外。
  因为堆外内存并不在 GC 的工作范围内,所以避免了缓存过大对 GC 的影响。
  堆外内存不受堆内内存大小的限制,只受服务器物理内存的大小限制。这三者之间的关系是这样的:物理内存=堆外内存+堆内内存。
  对于堆外内存的使用,有个现成开源项目就是 OHC,开箱即用,香的一比。
  当时就是这样做了一个简单的介绍,也没有深入的去分析,我个人对这个 OHC 还是比较感兴趣,但是有一说一,这玩意应用场景是真的不丰富,但是如果恰巧碰到了可以使用的应用场景。就可以开始你的表(装)演(逼)了
  什么是可以使用的应用场景?
  就是你的本地缓存对象特别多,多到都把你"堆"里面都快塞满了,从而 GC 频繁、时间长,都影响到服务的正常运行了。
  这个时候一拨人说:我要求调整 JVM 参数,调大堆内存。
  还有一拨人说:依我看,这个本地缓存干脆就别滥用了,非必要,不缓存,减少内存占用。
  另外一拨人说:又不是不能用?
  大家争得面红耳赤的时候,你轻飘飘的来一句:这个问题我觉得可以用堆外内存来解决,比如有个开源项目叫做 OHC,就比较好,可以调研一下。
  事了拂衣去,深藏身与名。
  所以为了以后能更好的装这个逼,这篇文章我准备盘一盘它,但是先说好,本文不会带你去盘源码,只是让你知道有这个框架的存在,做个简单的导读而已。 Demo
  老规矩,对于自己不了解的技术,都是先会简单使用,再深入了解。
  所以还是得搞个 Demo 才行,直接到它的 github 上找 Quickstart 就完事它的 Quickstart 就这么一行代码:
  https://github.com/snazy/ohc
  我看到的第一眼就是觉得这也太简陋了,在我想象中,一个好的 Quickstart 是我自己粘贴过来就能直接跑,很显然,它这个不行,本资深白嫖党表示强烈的谴责以及极度愤怒。
  但是没办法,还是先粘过来再说。
  对了,记得先导入 maven 依赖:      org.caffinitas.ohc     ohc-core     0.7.4 
  粘贴过来之后,我发现它这属于一个填空题啊:
  key 和 value 的序列化方式并没有给我们提供,而是需要我们进行自定义,这一点在它的 README 中也提到了:
  它说 key 和 value 的序列化需要去实现 CacheSerializer 接口,这个接口三个方法,分别是对象序列化之后的长度,序列化和反序列化方法。
  需要自己去实现一个序列化方式,一瞬间我的脑海里面蹦出了好几个关键词:Protobuf、Thrift、kryo、hessian 什么的。
  但是都太麻烦了,还得自己去编码,我只是想搞个 Demo 尝个味道而已,要是能从哪儿直接借鉴一个过来就好了。
  所以,我把 OHC 的源码拉下来了,因为直觉告诉我,它的测试用例里面肯定有现成的序列化方案。
  果不其然,测试案例非常的多,而我找到了这个:
  org.caffinitas.ohc.linked.TestUtils
  这个序列化方式就是测试用例里面广泛使用的方式:
  现在序列化方式有了,那么整个完整的代码就是这样的,我也给你搞个舒服的 Quickstart,粘过去就能用那种: public class OhcDemo {      public static void main(String[] args) {         OHCache ohCache = OHCacheBuilder.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 stringSerializer = new CacheSerializer() {         public void serialize(String s, ByteBuffer buf) {             // 得到字符串对象UTF-8编码的字节数组             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) {             // 获取字节数组的长度             int length = (((buf.get() & 0xff) << 8) + ((buf.get() & 0xff)));             byte[] bytes = new byte[length];             // 读取字节数组             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;         }     }; }
  从上面的 Demo 你也能看出来,OHCache 这个东西,和 Map 差不多,基本方法也是 put,get。
  只是 put 的对象,也就是缓存的对象,是由用户自定义的序列化方法决定的。比如我上面这个只能缓存字符串类型,如果你想要放个自定义对象进去,就得实现一个自定义对象的系列化方法,很简单的,网上搜一下,多的很。
  现在我们已经有一个可以运行的 Demo 了,运行之后输出是这样的,没有任何毛病:
  Demo 跑起来了,我们就算是找到"抓手"了,接下来就是分析它,结合自己的实际业务,沉淀出一套"可迁移、可复用"的组合拳,用来给自己"赋能"。
  对比
  为了让你能更加直观的看到堆外内存和堆内内存的区别,我给你搞两段程序跑跑。
  首先是我们堆内内存的代表选手,HashMap: /**  * -Xms100m -Xmx100m  */ public class HashMapCacheExample {     private static HashMap HASHMAP = new HashMap<>();      public static void main(String[] args) throws InterruptedException {         hashMapOOM();     }     private static void hashMapOOM() throws InterruptedException {         //准备时间,方便观察         TimeUnit.SECONDS.sleep(10);         int num = 0;         while (true) {             //往 map 中存放 1M 大小的字符串             String big = new String(new byte[1024 * 1024]);             HASHMAP.put(num + "", big);             num++;         }     } }
  通过 JVM 参数控制堆内存大小为 100m,然后不断的往 Map 中存放 1M 大小的字符串,那么这个程序很快就会出现 OOM:
  其对应的在 visualvm 里面的内存走势图是这样的:
  程序基本上属于一启动,然后内存就被塞满了,接着立马就凉了。
  属于秒了,被秒杀了。
  但是,当我们同样的逻辑,用堆外内存的时候,情况就不一样了: /**  * -Xms100m -Xmx100m  */ public class OhcCacheDemo {      public static void main(String[] args) throws InterruptedException {         //准备时间,方便观察         TimeUnit.SECONDS.sleep(10);         OHCache ohCache = OHCacheBuilder.newBuilder()                 .keySerializer(stringSerializer)                 .valueSerializer(stringSerializer)                 .build();         int num = 0;         while (true) {             String big = new String(new byte[1024 * 1024]);             ohCache.put(num + "", big);             num++;         }     }      public static final CacheSerializer stringSerializer = new CacheSerializer() {//前面写过,这里略了}; }
  关于上面程序中的 stringSerializer 需要注意一点的是我做测试的时候把这个大小的限制取消掉了,目的是和 HashMap 做测试是用同样大小为 1M 的字符串:
  这是程序运行了 3 分钟之后的内存走势图:
  这个图怎么说呢?
  丑是丑了点,但是咱就是说至少没秒,程序没崩。
  当这两个内存走势图一对比,是不是稍微就有那么一点点感觉了。
  但是另外一个问题就随之而来了:我怎么看 OHCache 这个玩意占用的内存呢?
  前面说了,它属于堆外内存。JVM 的堆外,那就是我本机的内存了。
  打开任务管理器,切换到内存的走势图,正常来说走势图是这样的,非常的平稳:
  从上面截图可以看到,我本机是 16G 的内存大小,目前还有 9.9G 的内存可以使用。
  也就是说截图的这个时刻,我能使用的堆外内存顶天了也就是 9.9G 这个数。
  那么我先用它个 6G,程序一启动,走势图就会变成这样:
  而程序一关闭,内存占用立马就释放了:
  也许你没注意到,前面我说了一句"用它个 6G",我怎么控制这个 6G 的呢?
  因为我在程序里面加了这样一行代码:
  如果你不加的话,默认只会使用 64M 的堆外内存,看不出啥曲线。
  如果你要想自己玩一玩,想亲眼看看这个走势图,记得加上这行代码,具体的值按照你机器的情况给就行了,个人建议是先做好保存工作,最好是意思意思就行了,别把值给的太大,电脑玩坏了你来找我,我不仅不赔钱,我还会笑你。
  然后除了这个 "6G" 可以自定义外,还有一些很多可以自定义的参数,清单如下,可以自己研究一波:
  源码
  前面说了,本文也不会带你去阅读源码,因为这个项目的源码写的已经很通俗易懂了,你自己去看,就知道主干逻辑写的非常的顺畅,没必要做太多的源码解析。
  我最多在这里指个路。
  我看源码是从 put 方法开始看的,但是 put 方法有两个实现类:
  关于这两个实现类,github 上进行了介绍:
  linked 实现方式:为每个需要缓存的对象单独分配堆外内存,对中、大条目效果最好。 chunked 实现方式:为每个哈希段整体分配堆外内存,相当于有个预分配的意思,适用于小条目。
  但是这里你只需要看 linked 实现方式就行了。
  为啥?
  别问,问就是作者建议的,在 github 的 README 里面有这样一个 NOTE:
  Note: the chunked implementation should still be considered experimental.
  翻译一下就是说:目前,chunked 实现方式应被当做是 experimental。
  experimental,放在句末,你就知道这是一个形容词了,什么意思呢?
  四级词汇,如果不认识的话赶紧背一背哈,考试要考的。
  作者说 chunked 实现方式还是实验阶段,肯定是有什么"暗坑"在里面的,不踩坑的最好方式,就是不用它。
  然后,你看着看着会发现,这个数据结构,和 ConcurrentHashMap 好像啊。是的,有 Segment,有 bucket,有 entry,所以不要怀疑自己,确实很像。
  接着,你看源码的时候,肯定是 Debug 的方式效率更高嘛。
  当你 debug 的对象是 put 方法的时候,要不了几下你就能看看这个地方:
  这个地方是申请堆外内存的操作,对应的是 IAllocator 这个接口:
  接口里面有三个方法: allocate:申请内存 free:释放内存 getTotalAllocated:获取已申请内存(空方法,未实现)
  主要关心前两个方法,因为我前面说了,这个是堆外内存,需要自己管理内存。管理就分为申请和释放,对应的就是这两个方法。
  所以,这里可以说是整个 OHC 框架的核心。
  带你盘一下这部分。
  操作堆外内存
  其实堆外内存这个东西,你一定是接触过的,只不过一般是框架封装好了,它是自己悄悄咪咪的使用,你没注意到而已罢了。
  一般我们申请堆外内存,就会这样去写:
  这个方法最终会调用 Unsafe 里面的 allocateMemory 这个 native 方法,它相当于 C++ 的 malloc 函数:
  这个方法会为我们在操作系统的内存中去分配一个我们指定大小的内存供我们使用,这个内存就叫做堆外内存,不由 JVM 控制,即不在 gc 管理范围内的。
  这个方法返回值是 long 类型数值,也就是申请的内存对应的首地址。
  但是需要注意的是,JVM 有个叫做 -XX:MaxDirectMemorySize (最大堆外内存)的配置,如果使用 ByteBuffer.allocateDirect 申请堆外内存,大小会受到这个配置的限制,因为会调用这个方法:
  OHC 要使用堆外内存,必然也是通过某个方法向操作系统申请了一部分内存,那么它申请内存的方法,是不是也是 allocateMemory 呢?
  这个问题,在 github 上作者给出了否认三连:
  不仅告诉了你没有使用,还告诉了你为什么没有使用:
  首先,开头的这个玩意 "TL;DR" 就直接把我干懵逼了。然后我查了一下,原来是 "Too long; Don"t read" 的缩写,直译过来的意思就是:太长了,读不下去。
  但是我觉得结合语境分析,作者放在的意思应该是一种类似于"长话短说"的意思。
  这个短语,一般用于在文章开头,先给出干货。
  你看,又学一个小知识。
  然后,我大概给你解释一下这一段 English 在说个什么意思。
  作者说,绕过 ByteBuffer.allocateDirect 方法,直接分配堆外内存,对 GC 来说是更加平稳的,因为我们可以明确控制内存分配,更重要的是可以由我们自己完全控制内存的释放。
  如果使用 ByteBuffer.allocateDirect 方法,可能在垃圾回收期间,就释放了堆外内存。
  这句话对应到代码中就是这里,而这样的操作,在 OHC 里面是不需要的。OHC 希望由框架自己来全权掌握什么时候应该释放:
  然后作者接着说:此外,如果分配内存的时候,没有更多的堆外内存可以使用,它可能会触发一个 Full GC,如果多个申请内存的线程同时遇到这种情况,这是有问题的,因为这意味着大量 Full GC 的连续发生。
  这句话对应的代码是这里:
  如果堆外内存不足的时候,会触发一次 Full GC。可以想象,在机器内存吃紧的时候,程序还在不停的申请堆外内存,继而导致 Full GC 的频繁出现,是一种什么样的"灾难性"的后果,基本上服务就处于不可用状态了。
  OHC 需要避免这种情况的发生。
  除了这两个原因之后,作者还说:
  Further, the stock implementation uses a global, synchronized linked list to track off-heap memory allocations.
  在 ByteBuffer.allocateDirect 方法的实现里面,还使用了一个全局的、同步的 linked List 这个数据结构来跟踪堆外内存的分配。
  这里我不清楚它说的这个 "linked list" 对应具体是什么东西,所以我也不乱解释了,你要知道的话可以在评论区给我指个路,我也学习学习。
  综上,作者最后一句说:这就是为什么 OHC 直接分配堆外内存的原因。
  This is why OHC allocates off-heap memory directly。
  然后他还提了一个建议:
  and recommends to preload jemalloc on Linux systems to improve memory managment performance.
  建议在 Linux 系统上预装 jemalloc 以提高内存管理性能。
  弦外之音就是要拿它来替换 glibc 的 malloc 嘛,jemalloc 基本上是碾压 malloc。
  关于 jemalloc 和 malloc 网上有很多相关的文章了,有兴趣的也可以去找找,我这里就不展开了。
  现在我们知道 OHC 并没有使用常规的 ByteBuffer.allocateDirect 方法来完成堆外内存的申请,那么它是怎么实现这个"骚操作"的呢?
  在 UnsafeAllocator 实现类里面是这样写的:
  org.caffinitas.ohc.alloc.UnsafeAllocator
  通过反射直接获取到 Unsafe 并进行操作,没有任何多余的代码。
  而在 JNANativeAllocator 实现类里面,则采用的是 JNA 的方式操作内存:
  OHC 框架默认采用的是 JNA 的方式,这一点通过代码或者日志输出也能进行验证:
  关于 Unsafe 和 JNA 这两种操作堆外内存的方式,到底谁更好,我在网上找到了这个链接:
  https://mail.openjdk.org/pipermail/hotspot-dev/2015-February/017089.html
  这封邮件的是 Aleksey Shipilev 针对一个叫做 Robert 的网友提出问题进行的回复。
  问题是这样的,Robert 他用对 Native.malloc() 和 Unsafe.allocateMemory() 进行了基准测试,发现前者的性能是后者的三倍。想知道为什么:
  然后 Aleksey Shipilev 针对这个问题进行了解析。
  这哥们是谁?
  他是基准测试的爸爸:
  所以他的回答还是比较权威的,但是需要注意的是,他并没有正面说明两个方法岁更好,只是解释了为什么用 JMH 出现了性能差 3 倍这个现象。
  另外,我必须得多说一句,通过反射拿 Unsafe 这段代码可是个好东西啊,建议熟读、理解、融会贯通:
  在 OHC 里面不就是一个非常好的例子嘛,虽然有现成的方法,但是和我的场景不是非常的匹配,我并不需要一些限制性的判断,只是想要简简单单的要一个堆外内存来用一用而已。
  那我就绕过中间商,自己直接调用 Unsafe 里面的方法。
  怎么拿到 Unsafe 呢?
  就是前面这段代码,就是通过反射,你在其他的开源框架里面可以看到非常多类似的或者一模一样的代码片段。
  背下来就完事。
  好了,文章就到这里了,如果对你有一丝丝的帮助,帮我点个免费的赞,不过分吧?

第十八届苍溪梨花节3月12日开幕来源原创稿游人在梨博园游玩。苍溪县委宣传部供图盛开的桃花。苍溪县委宣传部供图梨博园一角。苍溪县委宣传部供图第十七届梨花节开幕式现场。苍溪县委宣传部供图苍溪县湿地公园一角。苍溪县委宣商子雍一桩旧事在北京西站购物北京展览馆门外由于不是有钱人,所以在购物这件事上,一直比较克制(也没有办法不克制)。但从年轻的时候开始,我和老伴就喜欢旅游。不过早期的东游西转,除过在住宿上稍稍讲究以外,其它的行吃Z45次武昌到杭州,Z47次杭州到武昌凭什么能成为最受欢迎特快列车云游风物之旅春暖花开的季节,又到了旅游旺季,来一场顺走就走的旅行,是多少人的理想与向往,尤其是运火车去旅行,让心灵放松,让心情舒畅,让青春荡漾,让春天奔放。可能你没坐过Z45次武昌迪丽热巴连续参加两场活动,造型判若两人,但腹部却始终成为焦点顶流女星迪丽热巴最近参加了两场活动,一场是线上直播,一场是线下活动。大家都知道,过去的两个月,网上关于迪丽热巴的传闻一直没有平息。所以,这一次露面,尤其让人关注。3月2日那场线上直春天的外套在精不在多!这些外套穿搭可照搬,保暖有风度到了春天又是一个新的季节,而且每到这个换季的时刻,女性朋友们总是特别激动,终于可以给自己选择各种时尚的单品去凹造型了。而在春天,女性朋友们都特别喜欢各种各样的外套,用这些时尚外套去舒淇担任国际时尚奢侈品品牌全球代言人,气场强大形象华美如果提到哪位女明星不仅气质优雅而且充满魅力时,你会想到谁呢?30岁以上的观众或许很多人会想到散发个性美的舒淇。浪漫的大波浪长发性感的烈焰红唇,还有从不矫揉造作的个性,都成为舒淇的个时尚的麦迪逊比尔,深V长裙性感迷人的造型惊艳了无数人!麦迪逊比尔在美国公告牌女性音乐奖上又惊艳了无数人的眼眸!麦迪逊比尔不愧是时尚达人,一身渐变色的长裙将气质衬托的淋漓尽致,尤其是深V和动物纹理设计元素的点缀,将性感的自信彰显的没有一林允儿新杂志大片时尚运动性感完美结合头条创作挑战赛时尚品牌miumiu全球代言人林允儿近日为韩国杂志W拍摄了一组大片。林允儿身穿miumiu当季新款,可爱俏皮同时展现完美身材,将运动时尚风完美结合,不愧是代言人。林允饱满曲线身材微胖的身材是一种体态上的美,它从线条曲线上进一步增加了饱满立体的视觉效果。更是在肌肤体现了弹性的接触感。短款的针织上衣恰到好处的暴露出腰身的美态,以及双臂的肉弹感。让人感觉身体的上有一种变脸叫刘诗诗化浓妆在米兰时装周亮相看秀的刘诗诗,不仅着装明艳动人,妆造风格也别有风情!(via明星爱街拍)身穿靓丽的红色皮质风衣,蓬松的水波纹卷发披肩,不少人又被诗诗的生图圈粉了!无论是肤色还是气质威少,一半天使一半魔鬼谈起威少这个球员,有的人特别喜欢他,喜欢他的直率他的活力,他的暴力篮球美学。但他所在球队的球迷却很怕他,怕的是威少这把双刃剑不知道在哪个时刻就会刺杀球队。威少的打球方式一直没变,转
中国新能源汽车城市商业地理合肥模式多样化新能源汽车产业雏形21世纪新汽车研究院左茂轩今年,21世纪经济报道与汽车之家强强联手,在去年2021中国新能源汽车城市发展报告的基础上共同打造的2022中国新能源汽车发展报告(拟)将于年底发布,选取曹德旺预言已应验?如今持有多套房的家庭,将会面临什么问题20多年的城镇化建设,名下有多套房子的家庭越来越多。比如本来一个儿子,老家县城父母一套,儿子备一套大城市房子儿子住一套,老子又备一套。这种情况非常普遍。更有甚者,在大城市拥有十几套曹德旺预言应验?中国手握两套房的家庭,未来将面临什么结果从去年下半年开始,国内房地产市场就呈现出明显的调整趋势。现在越来越多的城市都加入到了房价下跌的队伍中来。数据显示,8月份,70个大中城市中,50个城市新建商品住宅价格环比下跌,19免门票!金秋十月,云南德宏芒市紫藤瀑布下万只孔雀与你雨林相约金秋十月,德宏芒市紫藤瀑布下万只孔雀与你雨林相约免门票哟。眼前国庆佳节日近,金色深秋来临,德宏芒市碧空如洗,架不住滇西友人三番五次相约,于是便乘此良辰美景,去德宏芒市热带雨林,与万杜淳1岁女儿边吃边吐,婆婆指出问题,王灿她还小咋养成好习惯三字经说过人之初,性本善。性相近,习相远。苟不教,性乃迁。习惯,是可以从小养成的吗?答案是的。在不同的家庭,自然会造就不同的孩子。不同的妈妈自然也会养育出不同的孩子。杜淳的妻子王灿云南大爷到上海参加侄子婚宴,随礼500,看到上菜后直说太划算云南省有许多浪漫的色彩,也有许多少数民族,他们的文化习俗更是魅力十足,这里是一个非常美丽神奇的地方,云南有一位老大爷从来没有出过远门,但是有一位非常要好的亲戚在上海为自己的儿子办了说走就走的旅行河西走廊一次偶然的机会观看了纪录片河西走廊,被纪录片里的故事情节深深打动,也被河西走廊的美景深深吸引。千年丝路古辉煌,河西走廊载沧桑,历史如今只能用来怀念,古人笔下的河西走廊美景静静地等着为什么要带孩子去旅行?长见识开拓视野是其次,还有一点更重要不少家长在假期的时候,都会带着自己的孩子出门旅行。对于这种现象,有些家长表示反对,因为这些家长认为带孩子往外跑是玩物丧志,但更多的家长表示赞同,因为带孩子出门旅行,能够看到更多的外2022十一旅游去哪好?陕西国庆赏秋景点线路推荐更多陕西旅游精彩内容,请订阅秦娃爱旅游头条号欢迎投送原创旅游类稿件欢迎转发收藏你喜欢的内容如果一年只能安排旅行一次,什么时间最合适呢?秦娃认为,十一假期的天气,舒服得刚刚好。下面这山西多所高校国庆假期调整,太原理工仅放三天,学生喜忧参半现在距离今年的国庆假期,仅仅还有不足一周的时间,在这个时间里,很多学生都已经难掩内心的焦躁,开始了焦急的等待,期望着国庆小长假能够早一点到来,这样就能够休息玩耍,放松一些紧绷了一个国庆假期旅游预订进入稳定增长阶段,相关细分板块有望见底回升国庆假期旅游预订进入稳定增长阶段,相关细分板块有望见底回升9月26日,酒店及餐饮板块大幅高开,涨幅居首位。截至发稿,华天酒店涨停,君亭酒店涨逾8,同庆楼西安饮食锦江酒店等均不同程度