下面是和内存映射相关的核心代码:
- public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
- int pagePosition = (int)(position % allocationGranularity);
- long mapPosition = position - pagePosition;
- long mapSize = size + pagePosition;
- try {
- addr = map0(imode, mapPosition, mapSize);
- } catch (OutOfMemoryError x) {
- System.gc();
- try {
- Thread.sleep(100);
- } catch (InterruptedException y) {
- Thread.currentThread().interrupt();
- }
- try {
- addr = map0(imode, mapPosition, mapSize);
- } catch (OutOfMemoryError y) {
- throw new IOException("Map failed", y);
- }
- }
- int isize = (int)size;
- Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
- if ((!writable) || (imode == MAP_RO)) {
- return Util.newMappedByteBufferR(isize, addr + pagePosition, mfd, um);
- } else {
- return Util.newMappedByteBuffer(isize, addr + pagePosition, mfd, um);
- }
- }
- 文件映射需要在 Java 堆中创建一个 MappedByteBuffer 的实例 。如果第一次文件映射导致 OOM,则手动触发垃圾回收,休眠 100ms 后再尝试映射,如果失败则抛出异常 。
- 通过 Util 的 newMappedByteBuffer(可读可写)方法或者 newMappedByteBufferR(仅读)方法反射创建一个 DirectByteBuffer 实例,其中 DirectByteBuffer 是 MappedByteBuffer 的子类 。
这样一定程度上替代了 read() 或 write() 方法,底层直接采用 sun.misc.Unsafe 类的 getByte() 和 putByte() 方法对数据进行读写 。
- private native long map0(int prot, long position, long mapSize) throws IOException;
这个 native 函数(Java_sun_nio_ch_FileChannelImpl_map0)的实现位于 JDK 源码包下的 native/sun/nio/ch/FileChannelImpl.c 这个源文件里面 。
- JNIEXPORT jlong JNICALL
- Java_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this,
- jint prot, jlong off, jlong len)
- {
- void *mapAddress = 0;
- jobject fdo = (*env)->GetObjectField(env, this, chan_fd);
- jint fd = fdval(env, fdo);
- int protections = 0;
- int flags = 0;
- if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) {
- protections = PROT_READ;
- flags = MAP_SHARED;
- } else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) {
- protections = PROT_WRITE | PROT_READ;
- flags = MAP_SHARED;
- } else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) {
- protections = PROT_WRITE | PROT_READ;
- flags = MAP_PRIVATE;
- }
- mapAddress = mmap64(
- 0, /* Let OS decide location */
- len, /* Number of bytes to map */
- protections, /* File permissions */
- flags, /* Changes are shared */
- fd, /* File descriptor of mapped file */
- off); /* Offset into file */
- if (mapAddress == MAP_FAILED) {
- if (errno == ENOMEM) {
- JNU_ThrowOutOfMemoryError(env, "Map failed");
- return IOS_THROWN;
- }
- return handle(env, -1, "Map failed");
- }
- return ((jlong) (unsigned long) mapAddress);
- }
- #include <sys/mman.h>
- void *mmap64(void *addr, size_t len, int prot, int flags, int fd, off64_t offset);
addr:文件在用户进程空间的内存映射区中的起始地址,是一个建议的参数,通常可设置为 0 或 NULL,此时由内核去决定真实的起始地址 。
当 flags 为 MAP_FIXED 时,addr 就是一个必选的参数,即需要提供一个存在的地址 。
len:文件需要进行内存映射的字节长度 。
prot:控制用户进程对内存映射区的访问权限:
- PROT_READ:读权限 。
- PROT_WRITE:写权限 。
- PROT_EXEC:执行权限 。
- PROT_NONE:无权限 。
- MAP_PRIVATE:对内存映射区数据的修改不会反映到真正的文件,数据修改发生时采用写时复制机制 。
- MAP_SHARED:对内存映射区的修改会同步到真正的文件,修改对共享此内存映射区的进程是可见的 。
推荐阅读
- 淘宝从百万到千万级并发的14次服务端架构演进之路
- 几百万扔进水?买二手房千万避开这几类房源
- 分布式、高并发、多线程,这些概念还傻傻分不清吗?
- Java 并发编程:如何保证共享变量的原子性?
- 硬核!如何模拟 5w+ 的并发用户?
- 格鲁吉亚中国茶王刘峻周诞辰150周年之际获百万赔偿
- 安徽省科技厅强化科技支撑推进精准扶贫
- PHP导出百万条数据方法
- 重金寻人 , 你是我们要找的百万英雄吗 内含福利
- 带你深入了解高并发架构
