跳至主要内容

What Is DirectBuffer?

What Is DirectBuffer?

This blog is the translation of my answer of this Chinese question:

Java NIO:
The DirectBuffer resides in user process address space or kernel?
In FileChannel#read(ByteBuffer dst), write(ByteBuffer src),if the class of parameter is HeapBuffer, it will allocate a temp DirectBuffer to assist the read/write rather than using the parameter HeapBuffer, why?

User or Kernel

From ByteBuffer (Java Platform SE 8 ):

The contents of direct buffers may reside outside of the normal garbage-collected heap

The doc tells us that the direct buffer may reside outside heap, and that means this is rely on JVM implementation, which is not coerced by JLS.

Back to the question, if the direct buffer resides outside of heap, does it belongs to user or kernel?

First, we all know every process has its own address space (virtual address), i.e. for a Java process, it can only read/write its own memory. But, kernel is different. It has a global view: it can read/write all physical address, which includes all virtual address space. (If you are interested in details, you may would like to refer to my simple linux-like os implementation).

So from this point of view, DirectBuffer belongs to that Java process, but also belongs to kernel.

A Temp DirectBuffer

Cited from Ron Hitches Java NIO

In the JVM, an array of bytes may not be stored contiguously in memory, or the Garbage Collector could move it at any time. Arrays are objects in Java, and the way data is stored inside that object could vary from one JVM implementation to another.

For this reason, the notion of a direct buffer was introduced. Direct buffers are intended for interaction with channels and native I/O routines. They make a best effort to store the byte elements in a memory area that a channel can use for direct, or raw, access by using native code to tell the operating system to drain or fill the memory area directly.
Direct byte buffers are usually the best choice for I/O operations. By design, they support the most efficient I/O mechanism available to the JVM. Nondirect byte buffers can be passed to channels, but doing so may incur a performance penalty. It’s usually not possible for a nondirect buffer to be the target of a native I/O operation. If you pass a nondirect ByteBuffer object to a channel for write, the channel may implicitly do the following on each call:

  1. Create a temporary direct ByteBuffer object.
  2. Copy the content of the nondirect buffer to the temporary buffer.
  3. Perform the low-level I/O operation using the temporary buffer.
  4. The temporary buffer object goes out of scope and is eventually garbage collected.

In Short:
The OS like to manipulate a continuous area of memory for IO operation (Why? e.g. the minimal read unit for disk is 512B. If the application just read one byte, most of time is wasted; addressing in memory also takes some time), but in JVM:

  • Byte array is not necessarily adjacent (note: in JLS chapter 10 Arrays, there is no requirement that array element have to be adjacent, so author said that. But, in real implementations, I guess, no JVM will split array element.);
  • GC may move the array around;
  • Different JVM may have different implementations of array, which may have different structure;

So, JVM introduce DirectBuffer to deal with upper problems.

And author said:

It’s usually not possible for a nondirect buffer to be the target of a native I/O operation.

But why?

Non-DirectBuffer must resides in heap of JVM, and its life cycle is managed by GC. GC algorithms has two main kinds:

  • mark and copying – used in most eden area;
  • mark and compact – used in old area of Serial/Parallel GC;
  • mark and sweep – used in old area of CMS;

img

So using Non-DirectBuffer will cause more work of GC, because:

  • mark: in the mark phase, using a JNI referred byte array will cause more work
  • compact: if the algo is compcation:
    • need to move byte array:
      • which should update the address of byte array in JNI
      • which should stop IO operation
    • or pin the byte array, not move that part
    • or not GC when IO (which is not possible for it may cause OOM)

So ByteBuffer document (Java Platform SE 8 ) says:

Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer’s content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system’s native I/O operations.

In other words, even FileChannel doesn’t make a temp array, the JVM may still need copy content to intermediate buffer, which is even slower. And DirectBuffer is managed by JVM, which is easy to optimize and reuse.

Conclusion

In conclusion, all of above content are analysis in theory, you need to test to choose the right way. Conclude with that famous saying:

First make it right, then make it fast.


这篇博客来自于我在这个问题的答案

DirectBuffer 属于堆外存,那应该还是属于用户内存,而不是内核内存?

来自 ByteBuffer (Java Platform SE 8 )

The contents of direct buffers may reside outside of the normal garbage-collected heap

所以DirectBuffer是否真的是堆外存并不是强制的,是依赖JVM实现的。

回归正题:DirectBuffer属于用户内存还是内核内存?

首先,我们知道,每个进程是有独立的地址空间的,也就是说对于一个Java进程来说,他只能看到自己的内存,他只能读写自己的内存。而内核其实是有上帝视角的,如早期的linux,内核是能看到并读写所有物理内存的(关于这一点,有兴趣的可以参看我的简写版linux的实现:kvm.c)。
所以从这个角度上讲,DirectBuffer只是那个Java 进程的内存,但是对于内核来说,他也是能看到并读写的。所以到底属于谁,这个问题本身有点模糊。

FileChannel 的read(ByteBuffer dst)函数,write(ByteBuffer src)函数中,如果传入的参数是HeapBuffer类型,则会临时申请一块DirectBuffer,进行数据拷贝,而不是直接进行数据传输,这是出于什么原因?

Ron Hitches Java NIO:

In the JVM, an array of bytes may not be stored contiguously in memory, or the Garbage Collector could move it at any time. Arrays are objects in Java, and the way data is stored inside that object could vary from one JVM implementation to another.


For this reason, the notion of a direct buffer was introduced. Direct buffers are intended for interaction with channels and native I/O routines. They make a best effort to store the byte elements in a memory area that a channel can use for direct, or raw, access by using native code to tell the operating system to drain or fill the memory area directly.

Direct byte buffers are usually the best choice for I/O operations. By design, they support the most efficient I/O mechanism available to the JVM. Nondirect byte buffers can be passed to channels, but doing so may incur a performance penalty. It's usually not possible for a nondirect buffer to be the target of a native I/O operation. If you pass a nondirect ByteBuffer object to a channel for write, the channel may implicitly do the following on each call:

1. Create a temporary direct ByteBuffer object.

2. Copy the content of the nondirect buffer to the temporary buffer.

3. Perform the low-level I/O operation using the temporary buffer.

4. The temporary buffer object goes out of scope and is eventually garbage collected.

翻译一下:
操作系统通常喜欢一次读很多并且一整块内存进行io操作(为什么?比如对于硬盘来说,最小的读取单位是512B,如果应用只读一个byte的话,是不是很浪费–这也是旧的IO包比较慢的一个原因;内存寻址也是要时间,所以连续内存就会比random access要快),但是在JVM中:

  • byte数组不一定连续
  • GC会移动数组
  • 数组实现在不同JVM中可能会不同

所以在JVM中引入DirectBuffer用于解决如上问题。

书中说 “一般来说,Non-DirectBuffer是不适合用于native的IO”, 所以需要复制。但是为什么Non-DirectBuffer不适合呢?
Non-DirectBuffer肯定是JVM heap中的,所以他肯定是要归GC管理的。GC算法有两个流派

mark and copying -- 新生代常用算法
mark and compact -- 老年代常用算法,Serial GC, Parallel GC
mark and sweep -- 老年代常用算法,CMS

所以使用Non-DirectBuffer必然导致GC更多的工作:

  • mark:不管是mark-and-compact 还是mark-and-swap,使用一个JNI引用的buffer(因为IO操作是JNI实现的,所以IO routine是会),都是会引起GC的mark阶段更多的工作
  • compact:毋庸置疑,如果Non-DirectBuffer是使用compaction方式
    • 需要在gc之后移动byte array。这样的话(很复杂):
      • 需要更新JNI那里的byte array地址
      • 需要暂停IO
    • 或者如学长所说,pin住byte array
    • 或者是在IO时,不要进行GC(显然是不合适的,如果IO时间比较长,heap岂不是要out of memory)

所以 ByteBuffer (Java Platform SE 8 ) 才会说:

Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations.

也就是说,即使FileChannel#read中不进行复制,JVM中的其它地方也是可能复制的,所以这样在很多情况下是有效率优势的。并且directBuffer由于是JVM直接管理的,使得它可以轻松的优化和复用。

Ref

Written with StackEdit.

评论

  1. Emm,when execute method: channel.write(DirectByteBuffer).Date of DirectByteBuffer need to copy from user spaceto kernel space?

    回复删除

发表评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation

Implement isdigit

It is seems very easy to implement c library function isdigit , but for a library code, performance is very important. So we will try to implement it and make it faster. Function So, first we make it right. int isdigit ( char c) { return c >= '0' && c <= '9' ; } Improvements One – Macro When it comes to performance for c code, macro can always be tried. #define isdigit (c) c >= '0' && c <= '9' Two – Table Upper version use two comparison and one logical operation, but we can do better with more space: # define isdigit(c) table[c] This works and faster, but somewhat wasteful. We need only one bit to represent true or false, but we use a int. So what to do? There are many similar functions like isalpha(), isupper ... in c header file, so we can combine them into one int and get result by table[c]&SOME_BIT , which is what source do. Source code of ctype.h : # define _ISbit(bit) (1 << (