帶著新人看java虛擬機(jī)01(推薦)
1.前言(基于JDK1.7)
最近想把一些java基礎(chǔ)的東西整理一下,但是又不知道從哪里開始!想了好久,還是從最基本的jvm開始吧!這一節(jié)就簡(jiǎn)單過一遍基礎(chǔ)知識(shí),后面慢慢深入。。。
水平有限,我自己也是很難把jvm將清楚的,我參考一本書《深入java虛擬機(jī)第二版》(版本比較老,其實(shí)很多大佬的博客都是參考的這本書的內(nèi)容。。。),電子檔pdf文件鏈接:https://pan.baidu.com/s/1bxs4i0gnVpz7Lkjl2fxS9g 提取碼:n5ou ,有興趣的小伙伴可以自己下載自己好好看看;
所謂jvm,又名java虛擬機(jī)。我們平常寫java程序的時(shí)候幾乎是感覺不到j(luò)vm的存在的,我們只需要根據(jù)java規(guī)范去編寫類,然后就可以運(yùn)行程序了,當(dāng)然只有我們程序出現(xiàn)bug了,我們才有可能在控制臺(tái)上看到一些jvm報(bào)錯(cuò)的信息,比如內(nèi)存溢出異常等。
java之所以能夠跨平臺(tái),就是因?yàn)閖vm屏蔽了各個(gè)操作系統(tǒng)之間的差異,舉個(gè)形象的例子,我們手機(jī)要充電吧,但是充電的方式有很多種,你可以直接數(shù)據(jù)線插到插座充電,也可以用數(shù)據(jù)線插到電腦USB口充電,一個(gè)是電腦一個(gè)是插座,為什么都能給手機(jī)充電呢?原因就是有數(shù)據(jù)線屏蔽了插座和電腦的差異,對(duì)于手機(jī)來(lái)說,它是看不到數(shù)據(jù)線另外一頭連接的是什么設(shè)備,只知道有電通過數(shù)據(jù)線向自己傳過來(lái)就ok了,順便一提,這也是所謂的適配器的原理!
開始之前首先要明確一點(diǎn),每一個(gè)java程序運(yùn)行就會(huì)創(chuàng)建一個(gè)jvm實(shí)例!比如我同時(shí)在eclipse中同時(shí)運(yùn)行三個(gè)程序,那么就會(huì)創(chuàng)建三個(gè)jvm實(shí)例,三個(gè)程序運(yùn)行于自己的jvm中,互不干擾,當(dāng)程序運(yùn)行完畢,那么jvm也會(huì)銷毀。
2.簡(jiǎn)單看看類加載過程
大家知道一個(gè)類加載到j(luò)vm大概是經(jīng)過了幾個(gè)步驟的吧!編譯成字節(jié)碼文件,加載,鏈接(驗(yàn)證,準(zhǔn)備,解析),初始化....,我就簡(jiǎn)單的用下面這個(gè)圖一起看看;
在這里,我們重點(diǎn)看看字節(jié)碼文件到j(luò)vm這一段,為什么字節(jié)碼文件能夠被加載到j(luò)vm中呢?類加載器又是什么呢?加載的具體過程又是什么呢?鏈接,初始化又具體的是在做些什么事呢?Class對(duì)象又是什么鬼?jvm中的具體結(jié)構(gòu)又是什么樣子的,各有什么用處?假如執(zhí)行一個(gè)類中的方法,在jvm中到底是什么流程呢?等等很多問題
這些問題有的是了解一點(diǎn),有的是真不知道,反正就是迷迷糊糊的一個(gè)類就加載成功了,然后我們就能成功調(diào)用那些方法了,平常用起來(lái)很舒服,但是細(xì)細(xì)想來(lái)難道不覺得奇怪嗎?
反正我最初看到j(luò)vm的時(shí)候,最想吐槽的一句話就是:瑪?shù)拢瑸槭裁窗??我感覺我已經(jīng)要化身成十萬(wàn)個(gè)為什么了,咳咳,不說廢話了,開始往后學(xué)吧!
下面我大概說一下這些步驟到底是做了什么事,有個(gè)大概的流程,然后我們慢慢的深入探究每一個(gè)步驟到底是干了什么事!
2.1 編譯器編譯
這個(gè)沒什么好說的,由于java是靜態(tài)語(yǔ)言,在執(zhí)行java程序之前會(huì)先把我們寫的java文件給轉(zhuǎn)化成特殊的二進(jìn)制碼的形式,編譯器就是做這個(gè)轉(zhuǎn)化的工作的工具,而且在我們寫代碼的時(shí)候,還沒運(yùn)行程序之前,就會(huì)報(bào)錯(cuò),在某處代碼下面會(huì)有紅線標(biāo)識(shí),做這個(gè)工作的就是編譯器,還有最重要的源文件中泛型,是會(huì)在編譯器編譯這個(gè)階段就會(huì)進(jìn)行擦除,所以字節(jié)碼文件中是沒有任何泛型信息的;
順便提一下動(dòng)態(tài)語(yǔ)言,比如Python,我們寫一個(gè)python程序運(yùn)行,是不需要進(jìn)行編譯的,會(huì)讀取第一行源文件中代碼就運(yùn)行這一行的代碼,然后讀取第二行代碼,運(yùn)行第二行代碼...
2.2 類加載器的分類和加載順序
什么是類加載器呢?我有一個(gè)很生動(dòng)很形象的例子:假如字節(jié)碼文件是一個(gè)人,而jvm就是地府,你說人死了會(huì)怎么進(jìn)入地府呢?自己肯定找不到地府的位置,于是要讓黑白無(wú)常請(qǐng)你過去了,類加載器在這里就是黑白無(wú)常!
大概了解類加載器的用處之后,我們就隨意看看類加載器的種類和運(yùn)行原理;
順便提一下,我們還記得最開始配置的jdk環(huán)境變量吧!我的JAVA_HOME=D:\java\jdk1.7;
話說大家知道jar包到底是什么嗎?其實(shí)就是一種壓縮文件的格式,跟zip,gz等壓縮格式?jīng)]有多大區(qū)別,可以用360壓縮打開。。。
進(jìn)入正題,類加載器分為四種,啟動(dòng)類加載器(Bootstrap ClassLoader):最頂級(jí)的類加載器,還是用C++寫的;在我們編寫java程序的時(shí)候,編譯器會(huì)自動(dòng)的幫我們導(dǎo)入一下常用的jar包,用的就是這個(gè)類加載器,比如我們最熟悉的lang包下的Object,String,Integer等都是我們可以直接用的,而不需要我們手動(dòng)導(dǎo)入;具體的會(huì)導(dǎo)入哪些jar包呢,這就需要我們配置環(huán)境變量JAVA_HOME,編譯器會(huì)去環(huán)境變量中找%JAVA_HOME%\jre\lib ,這下面所有jar包然后進(jìn)行加載到內(nèi)存中,注意不是加載在JVM中;而且出于安全考慮,啟動(dòng)類加載器只加載包名為java、javax、sun等開頭的類
擴(kuò)展類加載器(Extension ClassLoader):父類加載器是啟動(dòng)類加載器,java語(yǔ)言實(shí)現(xiàn),負(fù)責(zé)加載%JAVA_HOME%\jre\lib\ext 路徑下的jar包,這個(gè)不會(huì)自動(dòng)加載,只有在需要加載的時(shí)候才去加載。
應(yīng)用類加載器(Application ClassLoader):父類加載器是擴(kuò)展類加載器,java語(yǔ)言實(shí)現(xiàn),也可以叫做系統(tǒng)類加載器(SystemClassLoader),這個(gè)類加載器主要是加載我們?cè)趯戫?xiàng)目時(shí)編寫的放在類路徑下的類,比如maven項(xiàng)目中src/main/java/所有類
自定義類加載器:需要我們自己實(shí)現(xiàn),當(dāng)特殊情況下我們需要自定義類加載器,只需要實(shí)現(xiàn)ClassLoader接口,然后重寫findClass()方法,我們就能夠自己實(shí)現(xiàn)一個(gè)類加載器,而且自己實(shí)現(xiàn)類加載器之后可以去加載任何地方的類。假如我新建一個(gè)類放在F盤的隨便一個(gè)角落里也可以指定類路徑去加載,有興趣的小伙伴可以去試試。
不考慮自定義類加載器,可以看到,啟動(dòng)、擴(kuò)展、應(yīng)用這三個(gè)加載器就像是爺爺,爸爸,兒子一樣的關(guān)系,所以要加載一個(gè)類的話,選用哪個(gè)類加載器呢?肯定是有什么好吃的先讓兒子吃呀,然而兒子又很有孝心,會(huì)把到手的好吃的給爸爸吃。爸爸又會(huì)給爺爺吃,爺爺會(huì)嘗試著吃,假如一看這東西糖分太高于是就又給爸爸吃,爸爸也嘗試著吃,發(fā)現(xiàn)這東西不好吃,于是最后還是給兒子吃....這就是類加載器的雙親委托機(jī)制,隨便找了一幅圖看看:
2.3.JVM內(nèi)部結(jié)構(gòu)
其實(shí)大多數(shù)人對(duì)JVM是很熟悉了,不就是那幾個(gè)塊嗎?本地方法棧,java棧,java堆,方法區(qū),pc計(jì)數(shù)器,我這里就先大概說一下這幾個(gè)部分的用處;
方法區(qū):類加載器其實(shí)就是將字節(jié)碼文件給丟到這里,并解析出字節(jié)碼文件中包含的一些信息,比如全類名,類變量,方法有關(guān)的信息,父類信息,是不是接口等等這類信息
由于方法區(qū)很重要,我就隨意畫個(gè)草圖:
常量池(屬于方法區(qū)):由于方法區(qū)比較厲害能把字節(jié)碼文件中很多信息給解析出來(lái),但其中可能有很多常量比如18,“helloworld”,以及一些符號(hào)引用,常量池就存這些東西;但是什么又是符號(hào)引用呢?我就大概說一下吧,假如兩個(gè)類Animal和Dog,在Animal類中有個(gè)方法里面是這樣的:Dog dog = new Dog();dog.run(); 這個(gè)時(shí)候問題來(lái)了,在加載Animal類的時(shí)候發(fā)現(xiàn)了要用到Dog類,肯定是要去加載Dog類的,那么有兩種做法,第一種先暫停Animal類的加載去加載Dog類,加載完之后再加載Dog類,第二種,Animal類繼續(xù)加載的同時(shí)順便加載Dog類,只是Animal中只要是用到了Dog類、方法、字段的所有地方我隨便用xxx來(lái)表示,等Dog類加載完之后我再把xxx指向方法區(qū)Dog類對(duì)應(yīng)的地址就ok了;我們當(dāng)然用第二種方法啦,并且在這里我們隨便用的xxx就是符號(hào)引用,而加載完成后方法區(qū)中的Dog類地址就是直接引用
java堆:根據(jù)方法區(qū)中存的這么豐富的信息,這里就會(huì)創(chuàng)建每一個(gè)類的Class對(duì)象,話說這個(gè)Class對(duì)象用的最多的就是反射,那么這個(gè)Class對(duì)象到底是個(gè)什么呢?其實(shí)不用想的太難理解了,你就把它看作字節(jié)碼文件在內(nèi)存中的另外一種形式唄,就好像大米,在電飯煲里的表現(xiàn)形式就是米飯,在高壓鍋里的表現(xiàn)形式就是粥了.....;假如程序運(yùn)行的話,還會(huì)在堆中創(chuàng)建對(duì)象并且存放在堆中,所有的同類型的類的實(shí)例對(duì)象共享一個(gè)Class對(duì)象,我也隨意畫了一個(gè)草圖來(lái)看看如下所示,所以同一個(gè)類的不同實(shí)例對(duì)象的xx.getClass()都是一樣的,而且根據(jù)獲得的Class對(duì)象可以利用反射創(chuàng)建新的對(duì)象和獲取其中的方法,可以說Class對(duì)象為我們程序員提供了一個(gè)操作堆中對(duì)象的一個(gè)安全通道
pc寄存器:對(duì)于多線程來(lái)說,你就可以把這個(gè)看作一個(gè)計(jì)數(shù)器,每個(gè)線程一個(gè),里面寫著1,2,3,4,5....記錄著各個(gè)線程執(zhí)行代碼的行號(hào),為什么要記這個(gè)行號(hào)呢?莫非是閑的蛋疼?當(dāng)然不是!因?yàn)閷?duì)于多線程來(lái)說,cpu首先執(zhí)行一號(hào)線程,然后停止,去執(zhí)行二號(hào)線程,又停止,又去執(zhí)行一號(hào)線程...這個(gè)時(shí)候問題來(lái)了,cpu怎么知道上一次一號(hào)線程執(zhí)行到哪里來(lái)了?于是啊,這個(gè)pc寄存器用處就來(lái)了,因?yàn)槊總€(gè)線程都有一個(gè),而且記錄著當(dāng)前執(zhí)行的行號(hào),下次cpu來(lái)了根據(jù)這個(gè)行號(hào)就可以接著執(zhí)行了?。?/p>
java棧:對(duì)象已經(jīng)創(chuàng)建完畢放在堆中,然后我們調(diào)用一個(gè)java方法,就會(huì)在java棧中開辟一小塊空間(就是所謂的壓棧),俗稱棧幀,棧幀可以有多個(gè),因?yàn)橐粋€(gè)方法中可以調(diào)用其他方法嘛!總之一個(gè)方法就對(duì)應(yīng)一個(gè)棧幀,棧幀里面放著我們這個(gè)要運(yùn)行方法內(nèi)的局部變量,方法返回值等等參數(shù),等這個(gè)方法執(zhí)行完之后這個(gè)棧幀就退出去了(這就是所謂的彈棧),然后棧就恢復(fù)原樣
本地方法棧:不知道大家有沒有打開JDK的一些類的源碼看看,很多類都有Native方法(本地方法),我的理解是就是調(diào)用操作系統(tǒng)中一些c語(yǔ)言實(shí)現(xiàn)的方法或者其他語(yǔ)言實(shí)現(xiàn)的方法....
2.4.加載
說了這么久的類加載器的種類還有類加載器的使用順序,然后也簡(jiǎn)單說了JVM內(nèi)部結(jié)構(gòu)以及各自的作用,現(xiàn)在就是選好了的類加載器去加載字節(jié)碼文件丟到JVM中的方法區(qū)中了。
用偽代碼隨便看看加載大概步驟,參數(shù)name就是我們傳進(jìn)去的類的全名:
public Class<?> loadClass(String name) { try { if (parent != null) { //如果存在父類加載器,就委派給父類加載器加載 c = parent.loadClass(name, false); } else { //如果不存在父類加載器,就檢查是否是由啟動(dòng)類加載器加載的類, 通過調(diào)用本地方法native findBootstrapClass0 c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // 如果父類加載器和啟動(dòng)類加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能 c = findClass(name); }
所以假如我自定義一個(gè)類加載器MyClassLoader,那么就可以用這種方式去加載我隨意放在F盤myclass目錄里面,com.wyq.test包下的一個(gè)Student類:
MyClassLoader myClassLoader=new MyClassLoader("F:\\myclass"); Class c=myClassLoader.loadClass("com.wyq.test.Student");
然后我們得到了這個(gè)類的Class對(duì)象就可以用反射對(duì)這個(gè)類為所欲為了,嘿嘿嘿嘿~
2.5.鏈接
鏈接中分為三步:驗(yàn)證,準(zhǔn)備,解析;
隨便說說這三步大概干些什么,驗(yàn)證:這一步其實(shí)沒什么大的用處,就是虛擬機(jī)會(huì)檢查一下我們的字節(jié)碼文件有沒有問題,具體的就是看看你字節(jié)碼文件格式有問題嗎?語(yǔ)法有沒有問題?等等
準(zhǔn)備:給類的靜態(tài)變量分配內(nèi)存空間,并設(shè)置初始值;大家都知道靜態(tài)變量是放在方法區(qū)中的吧,比如我java類中有個(gè)靜態(tài)變量static int age = 18 那么這這個(gè)階段首先會(huì)分配4個(gè)字節(jié)的內(nèi)存空間,然后設(shè)置初始值為0,八大基本數(shù)據(jù)類型都有初始值,可以了解了解
解析:比較專業(yè)一點(diǎn)的說法就是,在解析階段,JVM會(huì)把類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換為直接引用!這句話怎么理解請(qǐng)看上面介紹的常量池
2.6 初始化
還是用準(zhǔn)備階段那個(gè)靜態(tài)變量,根據(jù)字節(jié)碼文件,將準(zhǔn)備那個(gè)階段的初始值覆蓋成真正的值18;
順便說一句,加載、鏈接、初始化三個(gè)步驟不是一定要按照這個(gè)順序完成的,只是開始的順序是這個(gè),但是在執(zhí)行過程中可能會(huì)有彎道超車的現(xiàn)象
3.例子分析
這里我們寫一個(gè)最簡(jiǎn)單的例子來(lái)總結(jié)一下上面這么多知識(shí);
public class Animal{ private int age=18; public void run() {} } publci class Test{ public static void main(String[] args){ Animal animal = new Animal(); animal.run(); } }
運(yùn)行這個(gè)main方法的步驟:
1.首先是編譯器會(huì)將這兩個(gè)類都編譯成字節(jié)碼文件并放在你的項(xiàng)目存放路徑
2.Test這個(gè)類會(huì)以某種方式告訴JVM自己的類名“Test”,虛擬機(jī)就會(huì)以某種牛逼的方法可以找到你這個(gè)Test.class放在那個(gè)目錄下面
3.調(diào)用類加載器,采用雙親委托機(jī)制去加載這個(gè)類,最后不出意外應(yīng)該是應(yīng)用類加載器去加載這個(gè)Test.class,以二進(jìn)制流的形式加載進(jìn)JVM方法區(qū)
4.在加載之后會(huì)去驗(yàn)證這個(gè)Test.class是否符合規(guī)范,沒問題的話就會(huì)解析這個(gè)加載進(jìn)來(lái)的Test.class,將其中很多信息都保存下來(lái),常量和符號(hào)引用保存在常量池中,其他的比如訪問修飾符,全類名,直接父類的全類名,方法和字段信息,除了常量以外的所有靜態(tài)變量,以及指向類加載器和Class對(duì)象的指針等都存在常量池外面
5.通過保存在方法區(qū)中的字節(jié)碼,JVM可以執(zhí)行main()方法,在執(zhí)行這個(gè)方法的時(shí)候,會(huì)一直持有有一個(gè)指向Test的常量池的指針;
6.在執(zhí)行main方法的第一條指令的時(shí)候,就是告訴JVM為Test常量池的第一個(gè)類型分配足夠內(nèi)存;由于main方法一直持有執(zhí)行Test常量池指針于是很迅速的找到了常量池第一項(xiàng),發(fā)現(xiàn)它是一個(gè)對(duì)Animal類的符號(hào)引用,然后就會(huì)先檢查方法區(qū)看有沒有Animal類有沒有被加載,假如沒有的話就要去找到這個(gè)Animal類;這里就有了一個(gè)算法的小知識(shí),怎么才能夠讓虛擬機(jī)最快速度找到Animal類所在位置呢?可以用散列表,搜索樹等算法。
7.加載Animal.class到方法區(qū)并提取其中有用的信息保存在方法區(qū),然后替換Test常量池第一個(gè)類型的符號(hào)引用,變?yōu)橹苯右茫蛔⒁?,這個(gè)時(shí)候還沒有創(chuàng)建對(duì)象,直接引用指向的是方法區(qū)中Animal所在的地址
8.JVM在堆中為創(chuàng)建Animal對(duì)象分配足夠內(nèi)存,怎么確定這個(gè)內(nèi)存多大合適呢?其實(shí)JVM比較牛,已經(jīng)設(shè)好了可以根據(jù)方法區(qū)中存放的信息確定一個(gè)類創(chuàng)建對(duì)象要用到多少堆空間;
9.對(duì)象創(chuàng)建好了會(huì)設(shè)置Animal實(shí)例變量的默認(rèn)初始值:age = 0
10.創(chuàng)建一個(gè)棧幀(里面有一個(gè)指向Animal對(duì)象的引用),壓入java棧中,到此main方法第一條指令就執(zhí)行完畢;還記得一個(gè)方法一個(gè)棧幀么
11.然后根據(jù)這個(gè)棧幀調(diào)用java代碼,將age的值初始化為正確的值:18
12.通過這個(gè)棧幀執(zhí)行run()方法,又會(huì)開辟一個(gè)棧幀存放run()方法內(nèi)部的所有信息
13.run()方法執(zhí)行完畢,釋放這個(gè)棧幀;然后main()執(zhí)行完畢,釋放棧幀;然后就是程序執(zhí)行完畢,清理回收堆中所有對(duì)象以及方法區(qū)
大概就是這么一個(gè)流程,其中最后的那個(gè)清理回收過程其實(shí)很重要,由于java棧和方法區(qū)的清理內(nèi)存效率非常好,我們可以不用在意,重點(diǎn)是在堆中清理內(nèi)存,而且由于有的程序是會(huì)運(yùn)行很久的,不可能每次都等程序執(zhí)行完畢之后再一起清理,肯定是要一邊運(yùn)行程序一邊清理堆內(nèi)存中沒用的對(duì)象,那么又該怎么進(jìn)行處理呢?又會(huì)涉及到很多的算法以及堆內(nèi)部到底是什么結(jié)構(gòu),后面我們會(huì)逐漸挖掘...
以上所述是小編給大家介紹的java虛擬機(jī)詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
IntelliJ IDEA中顯示和關(guān)閉工具欄與目錄欄的方法
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA中顯示和關(guān)閉工具欄與目錄欄的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10關(guān)于ConditionalOnMissingBean失效問題的追蹤
這篇文章主要介紹了關(guān)于ConditionalOnMissingBean失效問題的追蹤方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03使用自定義注解和@Aspect實(shí)現(xiàn)責(zé)任鏈模式的組件增強(qiáng)的詳細(xì)代碼
責(zé)任鏈模式是一種行為設(shè)計(jì)模式,其作用是將請(qǐng)求的發(fā)送者和接收者解耦,從而可以靈活地組織和處理請(qǐng)求,本文講給大家介紹如何使用自定義注解和@Aspect實(shí)現(xiàn)責(zé)任鏈模式的組件增強(qiáng),文中有詳細(xì)的代碼示例供大家參考,感興趣的同學(xué)可以借鑒一下2023-05-05關(guān)于Spring Actuator的簡(jiǎn)單測(cè)試
這篇文章主要介紹了關(guān)于Spring Actuator的簡(jiǎn)單測(cè)試,Spring-Actuator 是spring下的程序監(jiān)控系統(tǒng),通過簡(jiǎn)單的配置就可以查看程序的相關(guān)信息,本文提供了相關(guān)配置,需要的朋友可以參考下2023-10-10Spring Boot JPA中java 8 的應(yīng)用實(shí)例
這篇文章主要介紹了Spring Boot JPA中java 8 的應(yīng)用實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02一文學(xué)透ApplicationContext繼承接口功能及與BeanFactory區(qū)別
這篇文章主要為大家介紹了ApplicationContext繼承接口功能及與BeanFactory區(qū)別示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04LeetCode程序員面試題之無(wú)重復(fù)字符的最長(zhǎng)子串
Java計(jì)算無(wú)重復(fù)字符的最長(zhǎng)子串是一種常見的字符串處理算法,它的目的是找出一個(gè)字符串中無(wú)重復(fù)字符的最長(zhǎng)子串。該算法可以很好地解決一些字符串處理問題,比如尋找字符串中重復(fù)字符的位置,以及計(jì)算字符串中無(wú)重復(fù)字符的最長(zhǎng)子串的長(zhǎng)度。2023-02-02Java Web項(xiàng)目中使用Socket通信多線程、長(zhǎng)連接的方法
很多時(shí)候在javaweb項(xiàng)目中我們需要用到Socket通信來(lái)實(shí)現(xiàn)功能,在web中使用Socket我們需要建立一個(gè)監(jiān)聽程序,在程序啟動(dòng)時(shí),啟動(dòng)socket監(jiān)聽。接下來(lái)通過本文給大家介紹Java Web項(xiàng)目中使用Socket通信多線程、長(zhǎng)連接的方法,感興趣的朋友一起學(xué)習(xí)2016-04-04