摘要:這里以為例,它實(shí)際上就是個(gè)使用的一系列方法,比如等,操作文件描述符是什么本身只是獲取通信的服務(wù)和端口的一個(gè)實(shí)現(xiàn)類,對(duì)于服務(wù)的連接,是通過自身的屬性來處理。
Java NIO服務(wù)端代碼的hello world怎么寫?
public class NBTimeServer { public static void main(String[] args) { try { Selector acceptSelector = SelectorProvider.provider().openSelector(); //創(chuàng)建一個(gè)新的server socket,設(shè)置為非阻塞模式 ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); // 綁定server sokcet到本機(jī)和對(duì)應(yīng)的端口 InetAddress lh = InetAddress.getLocalHost(); InetSocketAddress isa = new InetSocketAddress(lh, 8900); ssc.socket().bind(isa); //通過selector注冊(cè)server socket,這里即告訴selector,當(dāng)accept發(fā)生的時(shí)候,socket會(huì)被放在reday隊(duì)列 SelectionKey acceptKey = ssc.register(acceptSelector, SelectionKey.OP_ACCEPT); int keysAdded = 0; // 當(dāng)任何一個(gè)注冊(cè)事件發(fā)生的時(shí)候,select就會(huì)返回 while ((keysAdded = acceptSelector.select()) > 0) { // 獲取已經(jīng)準(zhǔn)備好的selectorkey Set readyKeys = acceptSelector.selectedKeys(); Iterator i = readyKeys.iterator(); while (i.hasNext()) { SelectionKey sk = (SelectionKey)i.next(); i.remove(); ServerSocketChannel nextReady = (ServerSocketChannel)sk.channel(); Socket s = nextReady.accept().socket(); PrintWriter out = new PrintWriter(s.getOutputStream(), true); Date now = new Date(); out.println(now); out.close(); } } } catch(Exception e) { e.printStackTrace(); } } }1: 獲取selector。
SelectorProvider提供的所有provider都是同一個(gè)對(duì)象。如果沒有,它會(huì)通過AccessController.doPrivileged來給獲取provider的代碼最高的權(quán)限,執(zhí)行邏輯是:
java.nio.channels.spi.SelectorProvider 是否有配置,有就通過反射創(chuàng)建(本例沒有)
是不是在jar中已經(jīng)實(shí)例化了 java.nio.channels.spi.SelectorProvider,并且他能夠通過getSystemClassLoader加載,就是用第一個(gè)獲取到的SelectorProvider(本例沒有)
最終通過sun.nio.ch.DefaultSelectorProvider類來創(chuàng)建,它在不同的操作系統(tǒng)下有著不同的實(shí)現(xiàn)
以solaris的實(shí)現(xiàn)為例,創(chuàng)建的provider會(huì)根據(jù)操作系統(tǒng)的版本和操作系統(tǒng)的名字分別創(chuàng)建不同的實(shí)例
if ("SunOS".equals(osname)) { return new sun.nio.ch.DevPollSelectorProvider(); } if("Linux".equals(osname)){ if (major > 2 || (major == 2 && minor >= 6)) { return new sun.nio.ch.EPollSelectorProvider(); } } return new sun.nio.ch.PollSelectorProvider(); //默認(rèn)返回
代碼存在縮減,只取核心
類之間的關(guān)系如下
下面只關(guān)注Epoll和Poll
拿到provider之后,開始執(zhí)行openSelector,獲取真正的selector。
對(duì)于poll,返回的實(shí)例是PollSelectorImpl,對(duì)于Epoll返回的實(shí)例則是EpollSelectorImpl。
file descriptor :unix設(shè)計(jì)哲學(xué)就是一切都是文件,它可能是一個(gè)網(wǎng)絡(luò)連接、一個(gè)終端等等。它本身就是一個(gè)數(shù)值,在系統(tǒng)中會(huì)維護(hù)文件描述符和它對(duì)應(yīng)文件的一個(gè)指針,從而找到對(duì)應(yīng)的文件操作
fd0的獲取主要是調(diào)用Native方法實(shí)現(xiàn)
long pipeFds = IOUtil.makePipe(false); fd0 = (int) (pipeFds >>> 32); // >>> 表示無符號(hào)右移,最高位補(bǔ)0,這里即獲取讀文件描述符 fd1 = (int) pipeFds; //截掉了高位,存儲(chǔ)的是讀文件描述符
IOUtil針對(duì)不同的操作系統(tǒng)有不同的實(shí)現(xiàn),以solaris為例,它的實(shí)現(xiàn)在IOUtil.c中,主要實(shí)現(xiàn)即通過Linux pipe方法和Linux fcntl方法 (代碼有刪減)
int fd[2]; if (pipe(fd) < 0) // 獲取讀和寫的文件符 if ((configureBlocking(fd[0], JNI_FALSE) < 0) //標(biāo)注為非阻塞 || (configureBlocking(fd[1], JNI_FALSE) < 0)) return ((jlong) fd[0] << 32) | (jlong) fd[1]; //讀的文件描述符放在高位,寫的文件描述符放在低位configureBlocking本身的實(shí)現(xiàn)在IOUtil.c中
static int configureBlocking(int fd, jboolean blocking) //設(shè)置為非阻塞狀態(tài) { int flags = fcntl(fd, F_GETFL); int newflags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); return (flags == newflags) ? 0 : fcntl(fd, F_SETFL, newflags); }pipe實(shí)際是創(chuàng)建了一個(gè)進(jìn)程間通信的單向數(shù)據(jù)管道,參數(shù)中的fd[0]表示管道讀取端的結(jié)尾,fd[1]表示管道寫端的結(jié)尾;fcntl則主要是根據(jù)第二個(gè)參數(shù),如源碼中的F_GETFL和F_SETFL,對(duì)第一個(gè)參數(shù)執(zhí)行對(duì)應(yīng)的操作;
新建EPollArrayWrapper,部分字段如下
pollWrapper = new EPollArrayWrapper(); pollWrapper.initInterrupt(fd0, fd1);
epfd:通過Native方法去構(gòu)建,對(duì)應(yīng)的實(shí)現(xiàn)在EPollArrayWrapper.c中,方法為:Java_sun_nio_ch_EPollArrayWrapper_epollCreate,主要的實(shí)現(xiàn)邏輯是int epfd = (*epoll_create_func)(256);而epoll_create_func在Java_sun_nio_ch_EPollArrayWrapper_init執(zhí)行的時(shí)候已經(jīng)是執(zhí)行了初始化,對(duì)應(yīng)的是Linux epoll_create ,返回既是一個(gè)epoll實(shí)例,它實(shí)質(zhì)也是一個(gè)文件描述符
epoll_create_func = (epoll_create_t) dlsym(RTLD_DEFAULT, "epoll_create"); epoll_ctl_func = (epoll_ctl_t) dlsym(RTLD_DEFAULT, "epoll_ctl"); epoll_wait_func = (epoll_wait_t) dlsym(RTLD_DEFAULT, "epoll_wait");pollArray:一個(gè)用來存儲(chǔ)從epoll_wait中得到結(jié)果的數(shù)組,它的大小為 NUM_EPOLLEVENTS * SIZE_EPOLLEVENT,其中的NUM_EPOLLEVENTS則是去的文件描述符限制和8192相比的最小值Math.min(fdLimit(), 8192);詳見Linux getrlimit,實(shí)質(zhì)是AllocatedNativeObject
initInterrupt:出了存儲(chǔ)對(duì)應(yīng)的文件描述符之外,還執(zhí)行了epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);,即把fd0注冊(cè)到epfd上,將epfd上的EPOLLIN事件關(guān)聯(lián)到fd0上,詳見Linux epoll_ctl
新建PollArrayWrapper,部分字段如下
pollWrapper = new PollArrayWrapper(INIT_CAP); //初始為10 pollWrapper.initInterrupt(fd0, fd1);
pollArray:它的大小為(10+1)*SIZE_POLLFD(SIZE_POLLFD取值為8),實(shí)質(zhì)是AllocatedNativeObject
AllocatedNativeObject
NativeObject是用來操作本地內(nèi)存的一個(gè)代理,所有的操作通過Unsafe來實(shí)現(xiàn),它本身是一個(gè)單例2: 開啟服務(wù)端socket的channel
它還是會(huì)去獲取系統(tǒng)級(jí)別的provider,由于已經(jīng)在拿selector的時(shí)候初始化,不再新建。同樣會(huì)通過PollSelectorProvider或者是EPollSelectorProvider來開啟服務(wù)端的socket的channel,而二者的實(shí)現(xiàn)均是通過父類SelectorProviderImpl,創(chuàng)建一個(gè)ServerSocketChannelImpl實(shí)例
channel:代表與硬件、文件、網(wǎng)絡(luò)socket或者是程序組件等能夠進(jìn)行一些I/O操作(讀和寫)的實(shí)體的連接Closeable:是關(guān)閉與流相關(guān)的系統(tǒng)資源
AutoCloseable:從1.7開始的支持的語法糖try-with-resources結(jié)構(gòu),實(shí)現(xiàn)自動(dòng)關(guān)閉資源
SelectableChannel:支持通過selector復(fù)用的Channel,提供對(duì)channel的注冊(cè),返回對(duì)應(yīng)的SelectionKey,可以工作在阻塞(默認(rèn))和非阻塞模式下
NetworkChannel:對(duì)應(yīng)網(wǎng)絡(luò)socket的channel,提供將socket綁定到本機(jī)地址的bind方法
fd是使用IOUtil.newFD創(chuàng)建,創(chuàng)建過程如下:
調(diào)用 Native方法 Net.socket0
Net.scoket0 方法對(duì)應(yīng)的實(shí)現(xiàn)為Net.c中的Java_sun_nio_ch_Net_socket0,從頭文件的引入 #include可以看到,socket0的內(nèi)部很多實(shí)現(xiàn)都依賴于操作系統(tǒng)本身,操作系統(tǒng)不一樣,就會(huì)有不同的調(diào)用結(jié)果。關(guān)鍵實(shí)現(xiàn)如下
fd = socket(domain, type, 0); setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg,sizeof(arg))socket(family, type, protocol):其中family指的是要在解釋名稱時(shí)使用的地址格式(AF_INET6/AF_INET等),type指定的是通信的語義(SOCK_STREAM/SOCK_DGRAM等),protocol執(zhí)行通信用的協(xié)議,0意味著使用默認(rèn)的。它返回的就是socket file descriptor。詳見Linux socketAPI [solaris 下存在兩套實(shí)現(xiàn),BSD風(fēng)格socket庫-3SOCKET和 GNU/Linux軟件使用這個(gè)庫 XNET ]
setsockop:給文件描述符fd設(shè)置socket的選項(xiàng),返回值小于0表示出了異常,詳見Linux setsocketopt
新建java對(duì)象FileDescriptor ,將1中返回值和新建對(duì)象一起交給IOUtil的Native方法setfdVal執(zhí)行
在IOUtil.c中存在方法 Java_sun_nio_ch_IOUtil_setfdVal,它就是調(diào)用JNI的方法將獲取的值存入到j(luò)ava對(duì)象FileDescriptor中取
FileDescriptor的實(shí)例是用來表示1個(gè)打開的文件,或者是一個(gè)打開的socket或者類似的字節(jié)源
fdVal的賦值則是使用創(chuàng)建好的fd調(diào)用JNI中的(*env)->GetIntField(env, fdo, fd_fdID);實(shí)現(xiàn)
3:獲取socket本質(zhì)是通過ServerSocketAdaptor創(chuàng)建一個(gè)實(shí)例返回
ServerSocket本質(zhì)是一個(gè)對(duì)SocketImpl的包裝類,相關(guān)的請(qǐng)求處理都是由impl來處理
SocksSocketImpl是按照SOCKS協(xié)議的TCP socket實(shí)現(xiàn),而PlainSocketImpl則是一個(gè)‘平凡’的socket實(shí)現(xiàn),它不對(duì)防火墻或者代理做任何的突破。
SocketImpl是所有實(shí)現(xiàn)socket的父抽象類,用來創(chuàng)建客戶端和服務(wù)端的socket
4:綁定服務(wù)器和它的端口Socket類是兩臺(tái)機(jī)器之間通信的端點(diǎn),端點(diǎn)(endpoint)指的是 服務(wù)IP和它的端口,它的實(shí)際操作還是由SocketImpl來實(shí)現(xiàn)。
SOCKS4(SOCKets縮寫)是一個(gè)網(wǎng)絡(luò)協(xié)議,它主要負(fù)責(zé)在防火墻上中繼TCP會(huì)話,以便應(yīng)用用戶能夠透過防火墻進(jìn)行訪問。它主要定義了兩個(gè)操作:CONNECT和BIND。
需要CONNECT時(shí),客戶端發(fā)送一個(gè)CONNECT請(qǐng)求給SOCKS服務(wù)器,請(qǐng)求包含要連接的目的端口和目的主機(jī)等信息,SOCKS服務(wù)器會(huì)做一些服務(wù)權(quán)限的校驗(yàn),驗(yàn)證成功SOCKS服務(wù)器建立與目標(biāo)主機(jī)指定端口的連接(即應(yīng)用服務(wù)器),然后發(fā)送反饋包給客戶端,反饋包通過CD的值來標(biāo)識(shí)CONNECT請(qǐng)求的結(jié)果,CONNECT成功,SOCKS就可以在兩個(gè)方向上轉(zhuǎn)發(fā)流量了
BIND必須發(fā)生在CONNECT之后,它實(shí)際包括一系列的步驟:1 獲取socket;2 拿到scoket對(duì)應(yīng)的端口和ip地址;3 開始監(jiān)聽,準(zhǔn)備接收來自應(yīng)用服務(wù)器的調(diào)用 4:使用主連接通知應(yīng)用服務(wù)器它需要連接的IP地址和端口 5:接收一個(gè)來自應(yīng)用服務(wù)器的連接
SOCKS5相對(duì)于SOCKS4做了功能擴(kuò)展,支持UDP、IPV6、鑒定的支持
ServerSocketChannelImpl的bind方法。
1: 看看當(dāng)前channel是不是已經(jīng)綁定或者關(guān)閉,如果完成,拋出相關(guān)異常
2: 看看是否有分配服務(wù)器,沒有就隨便建一個(gè)
public InetSocketAddress(int port) { this(InetAddress.anyLocalAddress(), port); }
3: 獲取系統(tǒng)的SecurityManager,獲取成功,就去檢查線程是否有權(quán)限來操作端口等待連接到來,不行則拋出SecurityException
4: NetHooks.beforeTcpBind ,如果使用了com.sun.sdp.conf配置,那么將會(huì)把Tcp Socket包裝成Sdp Socket(Hello world沒有啟用)
5: 執(zhí)行綁定,實(shí)際執(zhí)行Native方法Net.bind0,對(duì)應(yīng)Net.c中的Java_sun_nio_ch_Net_bind0方法,關(guān)鍵代碼如下
//將傳入的java對(duì)象的InetAddress和端口轉(zhuǎn)換為結(jié)構(gòu)體:sockaddr_in或者sockaddr_in6 NET_InetAddressToSockaddr(env, iao, port, (struct sockaddr *)&sa, &sa_len, preferIPv6); rv = NET_Bind(fdval(env, fdo), (struct sockaddr *)&sa, sa_len);
bind對(duì)于windows系統(tǒng)和linux系統(tǒng)有不同的實(shí)現(xiàn),以Linux為例,它實(shí)際執(zhí)行的就是Linux bind,所做的操作就是把指定的地址(SocketAddress)分配給socket文件描述符,對(duì)于Hello world的實(shí)現(xiàn)來說就是它的字段fd
6: 監(jiān)聽,實(shí)際為Linux listen,表明這個(gè)socket將會(huì)用來接收即將到來的連接請(qǐng)求
5:通過selector注冊(cè)channel
注冊(cè)事件在實(shí)質(zhì)上就是維護(hù)新建channel的文件描述符和SelectionKey的關(guān)系,就實(shí)現(xiàn)上而言, Poll用的是數(shù)組,Epoll用的是HashMap
合法的操作為SelectionKey.OP_READ、SelectionKey.OP_WRITE、SelectionKey.OP_CONNECT6:從selector獲取任何已經(jīng)注冊(cè)好并發(fā)生的事件
根據(jù)是Poll還是Epoll有不同的實(shí)現(xiàn)。select的實(shí)質(zhì)就是去獲取poll和epoll的結(jié)果,然后更新自身維護(hù)的selector結(jié)構(gòu)對(duì)應(yīng)的狀態(tài)
在非阻塞模式下,accept會(huì)立馬返回
Linux accept 實(shí)際上就是從監(jiān)聽狀態(tài)的socketfd的連接等待隊(duì)列中獲取第一個(gè)連接請(qǐng)求,然后新建一個(gè)socket返回。
這里新建的SocketChannelImpl,而之前使用的是ServerSocketChannelImpl。區(qū)別在于 SocketChannelImpl支持讀寫數(shù)據(jù),而ServerSocketChannelImpl則更多的用于等待連接的到來,充當(dāng)服務(wù)端
接下來,獲取的socket方式同第3步中新建socket
8:從socket中獲取outputStreamoutpusStream通過Channels.newOutputStream新建,它會(huì)持有accept處新建的SocketChannelImpl,它實(shí)際上就是新建OutputStream并重寫它的write方法
9:回寫數(shù)據(jù)printWriter的print經(jīng)過BufferWriter到OutputStreamWriter,再到它的StreamEncoder到它的方法writeBytes執(zhí)行 out.write(bb.array(), bb.arrayOffset() + pos, rem);即socket中重寫的write方法,它的主要實(shí)現(xiàn)是調(diào)用Channels.writeFully,然后調(diào)用Channel自己的SocketChannelImpl.write方法,它核心在于 n = IOUtil.write(fd, buf, -1, nd, writeLock);
static int write(FileDescriptor fd, ByteBuffer src, long position, NativeDispatcher nd, Object lock) throws IOException { //判斷是否是直接內(nèi)存 if (src instanceof DirectBuffer) return writeFromNativeBuffer(fd, src, position, nd, lock); // Substitute a native buffer int pos = src.position(); int lim = src.limit(); assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); //申請(qǐng)一個(gè)DirectBuffer,即通過ByteBuffer.allocateDirect來申請(qǐng)直接內(nèi)存; ByteBuffer bb = Util.getTemporaryDirectBuffer(rem); try { bb.put(src); bb.flip(); // Do not update src until we see how many bytes were written src.position(pos); //寫數(shù)據(jù),實(shí)際上執(zhí)行的是FileDispatcherImpl的Native方法writ0 int n = writeFromNativeBuffer(fd, bb, position, nd, lock); if (n > 0) { // now update src src.position(pos + n); } return n; } finally { Util.offerFirstTemporaryDirectBuffer(bb); } }
可以看到這里有一段從JVM的Buffer拷貝到NativeBuffer中,也就是說NIO的數(shù)據(jù)寫肯定是從直接內(nèi)存發(fā)送出去的,如果本身不是直接內(nèi)存則會(huì)經(jīng)過一次內(nèi)存拷貝。
JNIEXPORT jint JNICALL Java_sun_nio_ch_FileDispatcherImpl_write0(JNIEnv *env, jclass clazz, jobject fdo, jlong address, jint len) { jint fd = fdval(env, fdo); void *buf = (void *)jlong_to_ptr(address); return convertReturnVal(env, write(fd, buf, len), JNI_FALSE); }
最終的寫可以看到用的就是Linux write
Java NIO的本質(zhì)是什么? 為什么一個(gè)Selector管理了多個(gè)Channel?SelectionKey會(huì)持有各自操作系統(tǒng)下的SelectorImpl對(duì)象,對(duì)于PollSelectorImpl的channel注冊(cè)內(nèi)部實(shí)際是通過數(shù)組存儲(chǔ)了文件描述符和Selector的關(guān)系,EpollSelectorImpl的channel注冊(cè)則是內(nèi)部用的HashMap存儲(chǔ)文件描述符和Selector的關(guān)系。當(dāng)讀取到事件的時(shí)候,就通過輪詢的方式拿到所有準(zhǔn)備好的事件返回,一個(gè)個(gè)的處理
NIO是如何實(shí)現(xiàn)的?它依賴于操作系統(tǒng)本身,對(duì)于windows/mac/linux均有不同的版本實(shí)現(xiàn)。這里以Liunx為例,它實(shí)際上就是個(gè)使用Linux的一系列方法,比如 read/write/accept等,操作文件描述符
socket是什么?socket本身只是獲取通信的服務(wù)和端口的一個(gè)實(shí)現(xiàn)類,對(duì)于服務(wù)的連接,是通過自身的屬性來處理。而這個(gè)屬性impl實(shí)際也就是對(duì)SOCKS協(xié)議的實(shí)現(xiàn)。來提供連接和綁定服務(wù)。
Java 阻塞IO服務(wù)端代碼的hello world怎么寫?public class TimeServer { private static Charset charset = Charset.forName("US-ASCII"); private static CharsetEncoder encoder = charset.newEncoder(); public static void main(String[] args) throws IOException { ServerSocketChannel ssc = ServerSocketChannel.open(); InetSocketAddress isa = new InetSocketAddress(InetAddress.getLocalHost(), 8013); ssc.socket().bind(isa); for (;;) { SocketChannel sc = ssc.accept(); try { String now = new Date().toString(); sc.write(encoder.encode(CharBuffer.wrap(now + " "))); System.out.println(sc.socket().getInetAddress() + " : " + now); sc.close(); } finally { // Make sure we close the channel (and hence the socket) sc.close(); } } } }
它與NIO的區(qū)別主要區(qū)別在于在于,NIO通過configureBlocking設(shè)置為false,會(huì)把它自身的fd設(shè)置為非阻塞,而阻塞IO則沒有,默認(rèn)阻塞。
Java客戶端的hello world怎么寫?public class TimeQuery { // Charset and decoder for US-ASCII private static Charset charset = Charset.forName("US-ASCII"); private static CharsetDecoder decoder = charset.newDecoder(); // Direct byte buffer for reading private static ByteBuffer dbuf = ByteBuffer.allocateDirect(1024); public static void main(String[] args) { try { InetSocketAddress isa = new InetSocketAddress(InetAddress.getLocalHost(), 8900); SocketChannel sc = null; try { // Connect sc = SocketChannel.open(); sc.connect(isa); // Read the time from the remote host. For simplicity we assume // that the time comes back to us in a single packet, so that we // only need to read once. dbuf.clear(); sc.read(dbuf); // Print the remote address and the received time dbuf.flip(); CharBuffer cb = decoder.decode(dbuf); System.out.print(isa + " : " + cb); } finally { // Make sure we close the channel (and hence the socket) if (sc != null) sc.close(); } } catch (IOException x) { System.err.println( x); } } }
真實(shí)的執(zhí)行實(shí)際上也就是Linux connect和Linux read
附錄jdk 7 源碼地址
NIO服務(wù)端 源碼地址
IO服務(wù)端 源碼地址
客戶端 源碼地址
如何讀open jdk native 源碼
java JNI簡介
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/71591.html
摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術(shù)并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問題在面試中經(jīng)常會(huì)被提到。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 JVM 內(nèi)存溢出實(shí)例 - 實(shí)戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細(xì)介紹 Java 注解的使用,有利于學(xué)習(xí)編譯時(shí)注解 Java 程序員快速上手 Kot...
摘要:基礎(chǔ)知識(shí)復(fù)習(xí)后端掘金的作用表示靜態(tài)修飾符,使用修飾的變量,在中分配內(nèi)存后一直存在,直到程序退出才釋放空間。將對(duì)象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對(duì)象稱之為反序列化。 Java 學(xué)習(xí)過程|完整思維導(dǎo)圖 - 后端 - 掘金JVM 1. 內(nèi)存模型( 內(nèi)存分為幾部分? 堆溢出、棧溢出原因及實(shí)例?線上如何排查?) 2. 類加載機(jī)制 3. 垃圾回收 Java基礎(chǔ) 什么是接口?什么是抽象...
摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實(shí)現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點(diǎn)如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計(jì)模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點(diǎn)點(diǎn)滴滴,依然清楚的記得當(dāng)初愛情萌芽的模樣…… Java 進(jìn)階面試問題列表 -...
閱讀 1171·2021-11-22 15:22
閱讀 3841·2021-10-19 13:13
閱讀 3584·2021-10-08 10:05
閱讀 3298·2021-09-26 10:20
閱讀 2987·2019-08-29 14:21
閱讀 2194·2019-08-27 10:55
閱讀 1876·2019-08-26 10:31
閱讀 2583·2019-08-23 16:47