Java直接内存原理提到了SocketChannel#write的实现原理。
经过IOUtil#write将java堆内存拷贝到了直接内存,而后再把地址传给了I/O函数。
那么 BIO 是怎么实现往socket里面写数据的呢?java
Socket#getOutputStream()得到SocketOutputStream
三个write方法最后都会调用native方法SocketOutputStream#socketWrite0 SocketOutputStream.c#Java_java_net_SocketOutputStream_socketWrite0方法c#
/* * Class: java_net_SocketOutputStream * Method: socketWrite * Signature: (Ljava/io/FileDescriptor;[BII)V */
JNIEXPORT void JNICALL Java_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)。socket