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