设为首页 加入收藏

TOP

JDK 源码阅读 : FileDescriptor(二)
2018-06-07 09:21:34 】 浏览:729
Tags:JDK 源码 阅读 FileDescriptor

st_mode & S_IFMT) == S_IFDIR) { errno = EISDIR; ::close(fd); return -1; } } else { ::close(fd); return -1; } } #ifdef FD_CLOEXEC { int flags = ::fcntl(fd, F_GETFD); if (flags != -1) ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } #endif if (o_delete != 0) { ::unlink(path); } return fd; }

可以看到JVM最后使用open64这个方法打开文件,网上对于open64这个资料还是很少的,我找到的是man page for open64 (all section 2) – Unix & Linux Commands,从中可以看出,open64是为了在32位环境打开大文件的系统调用,但是不是标志的一部分。

这里的open不是我们以前学C语言时打开文件用的fopen函数,fopen是C标准库里的函数,而open不是,open是POSIX规范中的函数,是不带缓冲的I/O,不带缓冲的I/O相关的函数还有read,write,lseek,close,不带缓冲指的是这些函数都调用内核中的一个系统调用,而C标准库为了减少系统调用,使用了缓存来减少read,write的内存调用。(参考《UNIX环境高级编程》)

通过上面的代码跟踪,我们知道了FileInputStream#open是使用open系统调用来打开文件,得到文件句柄,现在我们的问题要回到这个文件句柄是如何最终设置到FileDescriptor#fd,我们来看/jdk/src/solaris/native/java/io/io_util_md.c:fileOpen的关键代码:

fd = handleOpen(ps, flags, 0666);
if (fd != -1) {
    SET_FD(this, fd, fid);
} else {
    throwFileNotFoundException(env, path);
}

如果文件描述符fd正确,通过SET_FD这个红设置到fid对应的成员变量上:

#define SET_FD(this, fd, fid) \
    if ((*env)->GetObjectField(env, (this), (fid)) != NULL) \
        (*env)->SetIntField(env, (*env)->GetObjectField(env, (this), (fid)),IO_fd_fdID, (fd))

SET_FD宏比较简单,获取FileInputStream上的fid这个字段ID对应的字段,然后设置这个字段的IO_fd_fdID对应的字段(FileDescriptor#fd)为文件描述符。

那这个fidIO_fd_fdID是哪里来的呢?在/jdk/src/share/native/java/io/FileInputStream.c的开头,可以看到这样的代码:

jfieldID fis_fd; /* id for jobject 'fd' in java.io.FileInputStream */
/**************************************************************
 * static methods to store field ID's in initializers
 */
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) {
    fis_fd = (*env)->GetFieldID(env, fdClass, "fd", "Ljava/io/FileDescriptor;");
}

Java_java_io_FileInputStream_initIDs对应FileInputStream中static块调用的initIDs函数:

public class FileInputStream extends InputStream
{
    /* File Descriptor - handle to the open file */
    private final FileDescriptor fd;
    static {
        initIDs();
    }
    private static native void initIDs();
    // ...
}

还有jdk/src/solaris/native/java/io/FileDescriptor_md.c开头:

/* field id for jint 'fd' in java.io.FileDescriptor */
jfieldID IO_fd_fdID;
/**************************************************************
 * static methods to store field ID's in initializers
 */
JNIEXPORT void JNICALL
Java_java_io_FileDescriptor_initIDs(JNIEnv *env, jclass fdClass) {
    IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
}

Java_java_io_FileDescriptor_initIDs对应FileDescriptor中static块调用的initIDs函数:

public final class FileDescriptor {
    private int fd;
    static {
        initIDs();
    }
    /* This routine initializes JNI field offsets for the class */
    private static native void initIDs();
}

从代码可以看出这样的一个流程:

  1. JVM加载FileDescriptor类,执行static块中的代码
  2. 执行static块中的代码时,执行initIDs本地方法
  3. initIDs本地方法只做了一件事情,就是获取fd字段ID,并保存在IO_fd_fdID变量中
  4. JVM加载FileInputStream类,执行static块中的代码
  5. 执行static块中的代码时,执行initIDs本地方法
  6. initIDs本地方法只做了一件事情,就是获取fd字段ID,并保存在fis_fd变量中
  7. 后续逻辑直接使用IO_fd_fdID和fis_fd

为什么会有这样一个奇怪的初始化过程呢,为什么要专门弄一个initIDs方法来提前保存字段ID呢?这是因为特定类的字段ID在一次Java程序的声明周期中是不会变化的,而获取字段ID本身是一个比较耗时的过程,因为如果字段是从父类继承而来,JVM需要遍历继承树来找到这个字段,所以JNI代码的最佳实践就是对使用到的字段ID做缓存。
JDK 源码阅读 : FileDescriptor(二) https://www.cppentry.com/bencandy.php?fid=76&id=170232

首页 上一页 1 2 3 4 5 下一页 尾页 2/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Spring Boot 自动配置的 “魔法”.. 下一篇RocketMQ 源码学习 4 : 消息发送