阅读更多

7顶
0踩

编程语言

转载新闻 Java高速、多线程虚拟内存的实现

2014-07-04 10:00 by 正式记者 newer7 评论(10) 有26907人浏览
本文作者Alex已经从事Java开发15年了,最近帮助开发了COBOL和Magik语言的JVM 。当前,他正致力于Micro Focus的Java性能测试工具。在本文中,他阐述了在标准硬件中实现高速、多线程虚拟内存的可能性及方案。原文内容如下。

你想在标准硬件上运行TB级甚至PB级内存的JVM吗?你想与内存交互一样读写文件,且无需关心文件的打开、关闭、读、写吗?

JVM的64位地址空间使这些成为可能。

首先,不要在观念上将内存和磁盘进行区分,而是统一处理为内存映射文件。在32位地址空间时,内存映射文件只是为了高速访问磁盘;因为受限于虚拟机的有限地址空间,并不支持大规模的虚拟内存或大文件。如今JVM已经发展为64位,而且可以在64位操作系统上运行。在一个进程的地址空间中,内存映射文件大小就可以达到TB甚至PB。

进程无需关心内存是在RAM或是磁盘上。操作系统会负责处理,而且处理得非常高效。

我们可以使用Java的MappedByteBuffer类访问内存映射文件。该类的实例对象与普通的ByteBuffer一样,但包含的内存是虚拟的——可能是在磁盘上,也可能是在RAM中。但无论哪种方式,都是由操作系统负责处理。因为的ByteBuffer的大小上限是Intger.MAX_VALUE,我们需要若干个ByteBuffer来映射大量内存。在这个示例中,我映射了40GB。

这是因为我的Mac只有16GB内存,下面代码证明了这一点!

public MapperCore(String prefix, long size) throws IOException {
    coreFile = new File(prefix + getUniqueId() + ".mem");
    // This is a for testing - to avoid the disk filling up
    coreFile.deleteOnExit();
    // Now create the actual file
    coreFileAccessor = new RandomAccessFile(coreFile, "rw");
    FileChannel channelMapper = coreFileAccessor.getChannel();
    long nChunks = size / TWOGIG;
    if (nChunks > Integer.MAX_VALUE)
        throw new ArithmeticException("Requested File Size Too Large");
    length = size;
    long countDown = size;
    long from = 0;
    while (countDown > 0) {
        long len = Math.min(TWOGIG, countDown);
        ByteBuffer chunk = channelMapper.map(MapMode.READ_WRITE, from, len);
        chunks.add(chunk);
        from += len;
        countDown -= len;
    }
}


上面的代码在虚拟内存创建了40GB MappedByteBuffer对象列表。读取和写入时只需要注意处理两个内存模块的跨越访问。完整代码的可以在这里找到。

线程

一个极其强大且简单易用的方法就是线程。但是普通的Java IO简直就是线程的噩梦。两个线程无法在不引起冲突的情况下同时访问相同的数据流或RandomAccessFile 。虽然可以使用非阻塞IO,但是这样做会增加代码的复杂性并对原有的代码造成侵入。

与其他的内存线程一样,内存映射文件也是由操作系统来处理。可以根据读写需要,在同一时刻尽可能多的使用线程。我的测试代码有128个线程,而且工作得很好(虽然机器发热比较大)。唯一重要的技巧是复用MappedByteBuffer对象,避免自身位置状态引发问题。

现在可以执行下面的测试:

@Test
public void readWriteCycleThreaded() throws IOException {
final MapperCore mapper = new MapperCore("/tmp/MemoryMap", BIG_SIZE);
final AtomicInteger fails = new AtomicInteger();
final AtomicInteger done = new AtomicInteger();
Runnable r = new Runnable() {
    public void run() {
        try {
            // Set to 0 for sequential test
            long off = (long) ((BIG_SIZE - 1024) * Math.random());
            System.out.println("Running new thread");
            byte[] bOut = new byte[1024];
            double counts = 10000000;
            for (long i = 0; i < counts; ++i) {
                ByteBuffer buf = ByteBuffer.wrap(bOut);
                long pos = (long) (((BIG_SIZE - 1024) * (i / counts)) + off)
                        % (BIG_SIZE - 1024);
                // Align with 8 byte boundary
                pos = pos / 8;
                pos = pos * 8;
                for (int j = 0; j < 128; ++j) {
                    buf.putLong(pos + j * 8);
                }
                mapper.put(pos, bOut);
                byte[] bIn = mapper.get(pos, 1024);
                buf = ByteBuffer.wrap(bIn);
                for (int j = 0; j < 128; ++j) {
                    long val = buf.getLong();
                    if (val != pos + j * 8) {
                        throw new RuntimeException("Error at " + (pos + j * 8) + " was " + val);
                    }
                }
            }
            System.out.println("Thread Complete");
        } catch (Throwable e) {
            e.printStackTrace();
            fails.incrementAndGet();
        } finally {
            done.incrementAndGet();
        }
    }
};
int nThreads = 128;
for (int i = 0; i < nThreads; ++i) {
    new Thread(r).start();
    }
while (done.intValue() != nThreads) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // ignore
    }
    }
if (fails.intValue() != 0) {
    throw new RuntimeException("It failed " + fails.intValue());
    }
}


我曾尝试进行其他形式的IO,但是只要像上面那样运行128个线程,性能都不如上面的方法。我在四核、超线程I7 Retina MacBook Pro上尝试过。代码运行时会启动128个线程,超出CPU的最大负载(800%),直到操作系统检测到该进程的内存不足。在这个时候,系统开始对内存映射文件的读写进行分页。为实现这一目标,内核会占用一定的CPU,Java进程的性能会下降到650~750%。Java无需关心读取、写入、同步或类似的东西——操作系统会负责处理。

结果会有所不同

如果读取和写入点不是连续而是随机的,性能下降有所区别(带有交换时会达到750%,否则会达到250%)。我相信这种方式可能更适合处理少量的大数据对象,而不适用于大量的小数据对象。对于后者,可能的处理办法是预先将大量小数据对象加载到缓存中,再将其映射到虚拟内存。

应用程序

到目前为止,我使用的技术都是虚拟内存系统。在示例中,一旦与虚拟内存交互完成,就会删除底层文件。但是,这种方法可以很容易地进行数据持久化。

例如,视频编辑是一个非常具有挑战性的工程问题。一般来说,有两个有效的方法:无损耗存储整个视频,并编辑存储的信息;或根据需要重新生成视频。因为RAM的制约,后一种方法越来越普遍。然而,视频是线性的——这是一种理想的数据类型,可用来存储非常大的映射虚拟内存。由于在视频算法上取得的进步,可以将它作为原始字节数组访问。操作系统会根据需要将磁盘到虚拟内存的缓冲区进行分页处理。

另一个同样有效的应用场景是替代文档服务中过度设计的RAM缓存解决方案。想想看,我们有一个几TB的中等规模的文档库。它可能包含图片、短片和PDF文件。有一种常见的快速访问磁盘的方法,使用文件的RAM缓存弱引用或软引用。但是,这会对JVM垃圾收集器产生重大影响,并且增加操作难度。如果将整个文档映射到虚拟内存,可以更加简单地完成同样的工作。操作系统会根据需要将数据读入内存。更重要的是,操作系统将尽量保持RAM中最近被访问的内存页。这意味着内存映射文件就像RAM缓存一样,不会对Java或JVM垃圾收集器产生任何影响。

最后,内存映射文件在科学计算和建模等应用中非常有效。在用来处理代表真实世界系统的计算模型时,经常需要大量的数据才能正常工作。在我的音频处理系统Sonic Field中,通过混合和处理单一声波,可以模拟真实世界中的音频效果。例如,创建原始音频副本是为模拟从硬表面反射的声波,并将反射回来的声波与原声波混合。这种方法需要大量的存储空间,这时就可以把音频信号放在虚拟内存中(也是这项工作的最初动机)。

原文链接: jaxenter 翻译: ImportNew.com - MarkGZ
译文链接: http://www.importnew.com/9270.html
来自: importnew
7
0
评论 共 10 条 请登录后发表评论
10 楼 2007202070 2015-01-23 17:28
  不懂
9 楼 xiaoZ5919 2014-07-18 13:58
ITEYE啊 也不先自己分析一下,无论mmap还是write,底层都是buffered IO,写到page cache,然后再由内核刷都磁盘,所以两者性能应该差不了多少。mmap只是内核的page cache,少了一些用户态和内核态的copy。再者多线程写文件会快,不要忘了瓶颈在磁盘,多线程就变成随机IO了,多线程随机IO不一定比顺序IO好
8 楼 Tyrion 2014-07-14 16:11
iwindyforest 写道
学jvm的时候记得MappedByteBuffer及其子类使用本机直接内存,通过-XX:MaxDirectMemorySize指定,默认与-Xmx大小一样, 但不受JVM本身管理, 而且不预先向操作系统申请, 因此OOM了是比较难发现的. 因为总内存是固定的, 直接内存的使用肯定会影响到JVM实际可用内存.


然,所以使用MappedByteBuffer时这方法一定要注意。
7 楼 丑鑫鑫 2014-07-11 15:44
mmap系统调用
 #include <sys/mman.h>

       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
       int munmap(void *addr, size_t length);
6 楼 ray_linn 2014-07-08 12:47
nio的原理更应该理解为操作系统对资源一个处理方式,通过内核事件来通知资源就绪的情况, 这个处理模式极大的提高了系统的高并发处理能力和CPU利用效率, 仅此而已。
5 楼 ray_linn 2014-07-08 11:55
z1050334 写道
ray_linn 写道
貌似操作系统处理了一切,不需要太惊叹吧?

好像你很牛逼的样子


我牛比的时候,你还在高中拖鼻涕呢。
4 楼 z1050334 2014-07-08 08:52
ray_linn 写道
貌似操作系统处理了一切,不需要太惊叹吧?

好像你很牛逼的样子
3 楼 ClyenLiang 2014-07-06 11:05
有点小深奥
2 楼 iwindyforest 2014-07-04 14:05
学jvm的时候记得MappedByteBuffer及其子类使用本机直接内存,通过-XX:MaxDirectMemorySize指定,默认与-Xmx大小一样, 但不受JVM本身管理, 而且不预先向操作系统申请, 因此OOM了是比较难发现的. 因为总内存是固定的, 直接内存的使用肯定会影响到JVM实际可用内存.
1 楼 ray_linn 2014-07-04 10:46
貌似操作系统处理了一切,不需要太惊叹吧?

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • jQuery选择器之属性筛选选择器用法详解

    在这么多属性选择器中[attr=”value”]和[attr*=”value”]是最实用的 [attr=”value”]能帮我们定位不同类型的元素,特别是表单form元素的操作,比如说input[type=”text”],input[type=”checkbox”]等 [attr*=”...

  • JQuery选择器中的属性筛选

    属性选择器让你可以基于属性来定位一个元素。可以只指定该元素的某个属性,这样所有使用该属性而不管它的值,这个元素...其实,在这么多属性选择器中[attr=”value”]和[attr*=”value”]是最常用最实用的。[attr=”va

  • jQuery属性选择器用法示例

    本文实例讲述了jQuery属性选择器用法。分享给大家供大家参考,具体如下: &lt;html&gt; &lt;head&gt; &lt;meta http-equiv=Content-Type content=text/html; charset=utf-8 /&gt; &lt;title&gt;&lt;/title&gt; ...

  • jQuery操作元素属性、jQuery循环

    jQuery操作元素属性、jQuery循环1、prop() 取出或设置某个属性的值2、html() 取出或设置html内容代码如下所示:3、 手风琴效果实现:4、jQuery的循环: 1、prop() 取出或设置某个属性的值 代码如下所示: &lt;!...

  • 如何使用jQuery删除“disabled”属性?

    I have to disable inputs at first and then on click of a link to enable them. 我必须首先禁用输入,然后单击链接以启用它们

  • JQuery获取设置内容及属性

    JQuery捕获内容和属性 ①获取文本内容——text()、html()、val() text():设置或返回所选元素的文本内容 html():设置或返回所选元素的内容(包括html标记) val():设置或返回表单字段的值 实例: ...

  • jQuery——jQuery特殊属性操作

    文章目录jQuery特殊属性操作val方法案例:京东搜索.htmlhtml方法与text方法width方法与height方法scrollTop与scrollLeft案例:仿腾讯固定菜单栏案例案例:小火箭返航案例offset方法与position方法 jQuery特殊属性...

  • jquery增加和移除属性

    &lt;input id="artLink" type="text" name="art_link" placeholder="地址"&gt; 内容 &lt;textarea name="" id="txt" cols="30" rows="10" disabled&gt;&lt;/textarea&gt; 2.js $(...

  • js、jquery获取、设置元素属性与样式

    只能获取写在元素标签中style属性中的值, 无法获取写在、和加载进来的样式属性 2:window.getComputedStyle("元素","伪类") 伪类eg: :before,没有则写为null 3:ele.currentStyle 是IE浏览器的一个属性,返回最终css...

  • 【回炉重造】JQuery 获取和设置元素内容/属性

    JQuery 获取和设置元素内容/属性获取和设置元素内容1. html方法的使用2. 小结获取和设置元素属性1. prop方法的使用2. 示例代码3. 小结 获取和设置元素内容 学习目标 能够知道获取和设置元素内容的操作 1. html...

  • jquery选择器连续选择_JQuery中的选择器

    jquery选择器连续选择It's time to write some JQuery now. Do check out the introductory article on JQuery first in case you haven't. Before we move to Selectors in JQuery, let's talk a bit about the ...

  • a2x-text-counter:jQuery文本计数器插件

    a2x-text-counter是一个简单的jQuery插件,它计算您在textarea或input [text]元素中插入的符号的数量。 有两种常见的策略。 如果您的textarea具有maxlength属性,则该插件将充当倒数计数器。 在其他地方,它计算...

  • jquery源码_详细中文注释

    * jQuery JavaScript Library v1.10.2 * http://jquery.com/ * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license *...

  • JQuery学习05篇(属性选择器)

    直接po截图和代码 &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt;...JQuery学习05篇(属性选择器)&lt;...link rel="stylesheet" type="text/css" href="inputAndDiv...

  • JQuery学习13篇(操作节点的布尔属性)

    &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;...JQuery学习13篇(操作节点的布尔属性)&lt;...link rel="stylesheet" type="text/css" href="inputAndDiv.cs...

  • data-link属性的用法

    $('.data-span').data('linkId');

  • 在jquery 中创建新标签和属性的方法 手记

    在jquery 中创建新标签和属性的方法 手记

  • jQuery的属性与样式之html()及.text()

    读取、修改元素的html结构或者元素的文本内容是常见的DOM操作,jQuery针对这样的处理提供了2个便捷的方法.html()与.text() .html()方法 获取集合中第一个匹配元素的HTML内容 或 设置每一个匹配元素的html内容,...

  • JQuery学习11篇(操作节点普通属性)

    &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;...JQuery学习11篇(操作节点普通属性)&lt;...link rel="stylesheet" type="text/css" href="inputAndDiv.css"&...

  • jQuery中DOM操作方法 之 html,text,val

    jQuery内核 DOM操作方法 之 html,text,val 回顾下几组DOM插入有关的方法 innerHTML 设置或获取位于对象起始和结束标签内的 HTML  outerHTML 设置或获取对象及其内容的 HTML 形式 看图对照区别   ...

Global site tag (gtag.js) - Google Analytics