提到了SocketChannel#write的实现原理。
通过IOUtil#write将java堆内存拷贝到了直接内存,然后再把地址传给了I/O函数。 那么 BIO 是怎么实现往socket里面写数据的呢?BIO
Socket#getOutputStream()获得SocketOutputStream
三个write方法最后都会调用native方法SocketOutputStream#socketWrite0 SocketOutputStream.c#Java_java_net_SocketOutputStream_socketWrite0方法/* * Class: java_net_SocketOutputStream * Method: socketWrite * Signature: (Ljava/io/FileDescriptor;[BII)V */JNIEXPORT void JNICALLJava_java_net_SocketOutputStream_socketWrite0(JNIEnv *env, jobject this, jobject fdObj, jbyteArray data, jint off, jint len) { char *bufP; //中间临时缓冲区 char BUF[MAX_BUFFER_LEN]; //如果堆栈可以存下,直接使用堆栈内存 int buflen; //缓冲区大小,就是需要发送的数据大小 int fd; /* 省略,入参校验*/ /* * 尽可能使用堆栈分配缓冲区。 * 对于large sizes,我们从堆中分配一个中间缓冲区(达到最大值)。 * 如果堆不可用,我们只使用的堆栈缓冲区。 */ if (len <= MAX_BUFFER_LEN) { bufP = BUF; buflen = MAX_BUFFER_LEN; } else { buflen = min(MAX_HEAP_BUFFER_LEN, len); bufP = (char *)malloc((size_t)buflen); if (bufP == NULL) { bufP = BUF; buflen = MAX_BUFFER_LEN; } } while(len > 0) { int loff = 0; int chunkLen = min(buflen, len); int llen = chunkLen; int retry = 0; /* * 这个方法复制了java数组到native堆中!!! */ (*env)->GetByteArrayRegion(env, data, off, chunkLen, (jbyte *)bufP); /* * 由于Windows套接字中的错误(在NT和Windows 2000上观察到),可能需要重试发送。 */ while(llen > 0) { /* 循环调用发送*/ int n = send(fd, bufP + loff, llen, 0); if (n > 0) { llen -= n; loff += n; continue; } /* * 由于Windows套接字中的错误(在NT和Windows 2000上观察到),可能需要重试发送。 */ if (WSAGetLastError() == WSAENOBUFS) { /* 省略,失败重试机制*/ } /* * 发送失败 - 可能由关闭或写入错误引起。 */ if (WSAGetLastError() == WSAENOTSOCK) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else { NET_ThrowCurrent(env, "socket write error"); } /* 释放临时缓冲区内存*/ if (bufP != BUF) { free(bufP); } return; } len -= chunkLen; off += chunkLen; } /* 释放临时缓冲区内存*/ if (bufP != BUF) { free(bufP); }}复制代码
jni.cpp宏定义
#define DEFINE_GETSCALARARRAYREGION(ElementTag,ElementType,Result, Tag) \ DT_VOID_RETURN_MARK_DECL(Get##Result##ArrayRegion);\\JNI_ENTRY(void, \jni_Get##Result##ArrayRegion(JNIEnv *env, ElementType##Array array, jsize start, \ jsize len, ElementType *buf)) \ /* 省略,动态判断应该去调用byte、int等哪个方法;还有一些动态追踪的逻辑?*/ typeArrayOop src = typeArrayOop(JNIHandles::resolve_non_null(array)); \ if (start < 0 || len < 0 || ((unsigned int)start + (unsigned int)len > (unsigned int)src->length())) { \ THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); \ } else { \ if (len > 0) { \ int sc = TypeArrayKlass::cast(src->klass())->log2_element_size(); \ /* 内存拷贝*/ memcpy((u_char*) buf, \ (u_char*) src->Tag##_at_addr(start), \ len << sc); \ } \ } \JNI_END复制代码
所以除了直接使用ByteBuffer#allocateDirect分配堆外内存之外,不管是BIO和NIO都需要将java堆内存拷贝到native堆(堆外内存)。
当然都不能避免从native堆拷贝到socket buffer(SO_RCVBUF和SO_SNDBUF)。