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

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

  你好呀,我是歪歪。
  之前在《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连板赛伍技术宁德时代相关产品销售额占比较小,对业绩影响较小,第二大股东减持计划尚未完成3连板赛伍技术11月7日发布股票交易风险提示性公告,公司股票价格于11月2日11月3日11月4日连续三个交易日收盘价格涨幅偏离值累计超过20。根据有关规定,属于股票异常波动情形。1食品调理疾病?中冠健康公司旗下中冠肽虚假宣传运营模式涉嫌传销文章来源投融新资讯,版权归原作者,如有侵权,联系删除,转载请注明出处,特此鸣谢!投融新资讯消息近几年来,大健康产业异军突起,越来越多的健康产品开始步入市场,与小分子肽相关的产品开始隐形盐危害大,大数据告诉你隐形盐藏在哪里隐形盐指食品中看不见的盐。很多食品调味品中都有盐,虽然我们看不见,但吃这些食品的同时,也吃进了很多盐。隐形盐吃多了和盐吃多了对人体健康的危害是一样的,不仅容易患高血压,而且增加脑卒禁闭求生玩家数突破1300万正式版于9月底发布Xbox负责人MattBooty最近做客了SkillUp的视频节目,视频中确认了禁闭求生的玩家数已经突破1300万。在今年二月份,本作的玩家数就突破了一千万。,而禁闭求生正式版已经TGA官方发文预热今年活动近八年的年度游戏回顾随着2022年步入尾声,游戏界最后的欢庆盛典TGA的举办也进入倒计时。三年之期已到,今年的2022TGA颁奖典礼将于12月8日在洛杉矶微软剧院举行。举办方在今日(11月7日)发推为曾经的民间游戏汉化大神,如今过得怎么样?这几天,对于很多中国玩家来说,估计心情都有点复杂。上古卷轴OL刚刚上线,很多玩家抱着激动的心情进入游戏,结果一看这官方汉化,瞬间就被浇上了一盆冷水。你来翻译翻译,什么叫干掉蜘蛛网。赛博朋克2077热度强势回归,玩家日活量居高不下哈喽啊,大家好我是冲锋弟弟,大家还记得2021年跳票了两次的的游戏赛博朋克2077吗?就是那个电脑要求配置高,而且还跳票了两次的2077,但是就在最近2077的热度强势回归,这到底科学家首次发现昆虫也会玩游戏游戏是人类的天性,这一本能在哺乳动物和鸟类中均有发现。英国科学家发现,大黄蜂在实验中会自发滚动小球,该研究首次证明昆虫也存在游戏行为。相关论文10月19日发表于动物行为。2017年
马斯克的饼,越画越大狂人马斯克的饼,越画越大。当地时间3月1日(北京时间3月2日凌晨)特斯拉投资者日上,特斯拉CEO马斯克直言地球当前是一个肮脏的长满铁锈的星球,但未来将成为一颗依靠可再生能源驱动的行长三角下一个千万人口之城,会是谁?作为衡量城市能级的重要标准之一,常住人口破千万是一个重要门槛,意味着更为明显的资源集聚效应。目前,全国共有17座城市人口超千万大关,长三角拥有上海苏州杭州三座千万人口之城,谁将成为中国经济稳步复苏提振全球信心中国全国两会将在北京开幕,届时发布的政府工作报告将公布2023年中国宏观调控的主要策略和政策力度,而疫情后中国经济恢复的程度无疑将影响到两会的宏观经济决策方向。当下中国经济活力恢复塞尔维亚不是中国,俄罗斯没想到,自己的滴水恩,不会有涌泉报俄罗斯的朋友越来越少了。塞尔维亚向乌克兰提供武器,俄方要求必须澄清,看得出来俄方很恼火。连塞尔维亚这种小弟,也敢背后捅刀了。但在俄罗斯失势之下,塞尔维亚也没办法,弱国无外交,没权没浙江商业航天再添新力量地卫二下月将发望江山智能星座首星浙江商业航天再添新力量。3月3日,以全息地球数字中国为主题,地卫二空间技术(杭州)有限公司(简称地卫二)举办SSEC太空探索发布会,展示了其人工智能航天卫星技术在数字中国场景建设的爱奇艺又坑会员?2月明明只有28天,被批仍然按整月收费!这合理吗?继限制电视投屏之后,爱奇艺又成为了网络热点,而且仍然是负面的那种。这次原因是有网友发现,虽然今年2月只有28天,但爱奇艺仍然按30天一个月的标准收取包月会员费,并没有打折富士康新车读作麻豆B被吐槽骂人,与纳智捷携手侧面酷似ID。3特斯拉投资者大会终于来了,不过马斯克在本次大会上并没有带来之前传闻已久只要十来万的入门电动车ModelQ。但是你也不用担心,有一家车企已经替特斯拉提前发布了,它就是富士康推出的Mo酷比魔方XPad新消息官方今日预热酷比魔方XPad,将搭载11英寸高色域IPS屏,采用inCell全贴合工艺打造,分辨率为2176x1600,89NTSC宽广色域。酷比魔方XPad处理器采用的是联发科天华为3月迎来大爆发,P60系列手表耳机全都有,你期待吗?据博主看山的叔叔爆料,华为将在3月份迎来新品大爆发,包括华为P60系列畅享60系列麦芒20系列耳机手表平板等新品集中亮相,此外还可能包含MateX3新款折叠屏。正在大家翘首以盼新旗又被工信部点名,区块链究竟是个啥?一句话解读给你在国新办举行的权威部门话开局系列主题新闻发布会上,工业和信息化部点名区块链要加快人工智能大数据区块链云计算网络安全等新兴数字产业发展,引导支持企业加大研发投入。发布会现场区块链究竟运动控制器PSO视觉飞拍与精准输出的C开发(二)多轴PSO输出本文主要介绍正运动技术EtherCAT控制器在VS平台采用C语言实现的各种PSO功能。正运动提供多种PSO模式供用户搭配不同的场景使用。上节讲解了采用TABLE寄存器存储的数据表触