jvm進(jìn)程
JVM進(jìn)程可以由bin\jps查看。在命令行下輸入jps
- 由一個(gè)jdk文件系統(tǒng),可以產(chǎn)生很多jvm進(jìn)程(沒bin\java.exe執(zhí)行后產(chǎn)生一個(gè))
- JVM進(jìn)程有個(gè)pid,默認(rèn)情況下河他執(zhí)行的main所在的類相同
- bin\jconsole 可以監(jiān)視和管理java程序
- jvm的三大財(cái)產(chǎn)
- 內(nèi)存
內(nèi)存是JVM擁有的主要財(cái)產(chǎn)之一,內(nèi)存你看到了分堆和非堆,這兩個(gè)值是 在執(zhí)行命令java.exe是可以修改的,如
java.exe 類名 -Xmx3550m -Xms3550m -XX:MaxPermSize=16m
-Xmx堆內(nèi)存最大
-Xms堆內(nèi)存最小
-XX:MaxPermSize 非堆 (放class,static var)
一般在實(shí)際的過程中將Xms與 Xmx設(shè)置為一樣。應(yīng)為避免程序執(zhí)行后期內(nèi)存不夠或分配不及時(shí)。這兩個(gè)值的大小將直接影響程序的性能<br />
在eclipse中,可以在點(diǎn)Run configurations...后界面設(shè)置.
線程
程啟動(dòng)運(yùn)行時(shí)會(huì)有一個(gè)線程去啟動(dòng)main方法
除了main線程,其它都是JVM內(nèi)置的,我們自己沒有開啟.在實(shí)際運(yùn)行中,這里的線程太多和太少都不行,要維持在一個(gè)合理的范圍,而且也要時(shí)刻關(guān)注他們的狀態(tài)。如果程序中一個(gè)線程都沒有,那么進(jìn)程也死了。類
其實(shí)就是程序,包括JRE中的類,使用的第三方的jar
包和我們應(yīng)用中自己寫的程序,這些類加載進(jìn)入內(nèi)存,都放在非堆中。
所以我們JVM進(jìn)程是一個(gè)有身份證(pid),有姓名(名稱),擁有內(nèi)存,程類(程序)的一個(gè)靜態(tài)實(shí)體(CPU無法調(diào)度執(zhí)行)。
java類加載器基本知識(shí)
java web server 基本實(shí)現(xiàn)原理
- 遠(yuǎn)端服務(wù)器使用ServerSocket技術(shù)打開一個(gè)端口,等待請(qǐng)求的到來。
- 瀏覽器得到用戶輸入的地址(url)或者得到點(diǎn)擊聯(lián)接地址(url)。
- 瀏覽器看輸入的是IP還是域名,如果是域名,通過查找DNS服務(wù)找到此域名IP,并緩存到瀏覽器中,以加快下次查找速度。
- 瀏覽器組裝滿足HTTP協(xié)議的數(shù)據(jù)包(請(qǐng)求報(bào)文)。
- 瀏覽器請(qǐng)求在本機(jī)隨機(jī)開啟一個(gè)端口與服務(wù)端IP和服務(wù)端端口建立聯(lián)接 (TCP)。(本機(jī)IP + 本機(jī)端口 + 服務(wù)端IP + 服務(wù)端端口,用來唯一標(biāo)示一條TCP 聯(lián)接)
- 通過此聯(lián)接發(fā)送HTTP數(shù)據(jù)包。
- 服務(wù)端通過端口接收到數(shù)據(jù)之后,解析HTTP數(shù)據(jù)包,組裝成良好的格式,并調(diào)用程序處理。
- 服務(wù)程序處理完成之后,服務(wù)端組裝滿足HTTP協(xié)議的數(shù)據(jù)包(響應(yīng)報(bào)文)通過TCP聯(lián)接返回到請(qǐng)求端口上。
- 瀏覽器從請(qǐng)求端口得到數(shù)據(jù)解析響應(yīng)報(bào)文得到相應(yīng)數(shù)據(jù)后給瀏覽器軟件進(jìn)行解析渲染。
- 請(qǐng)求關(guān)閉聯(lián)接
先用socket技術(shù)實(shí)現(xiàn)一個(gè)main方法
public class WebServer {
//服務(wù)端Socket只要一個(gè),所以定義成static, 同一時(shí)間只能一個(gè)線程訪問(主線程)
private static ServerSocket ss;
public static void main(String[] args) throws IOException {
//綁定端口,只會(huì)執(zhí)行一次
ss = new ServerSocket(8999);
//主線程永遠(yuǎn)不死,所以用了個(gè)死循環(huán)
while (true) {
//這是一個(gè)IO阻塞式語句,也就是如果沒有請(qǐng)求(IO操作)進(jìn)來,當(dāng)前線程會(huì)在此等待,不再向下執(zhí)行
Socket socket = ss.accept();
//得到請(qǐng)求報(bào)文(內(nèi)容)
StringBuffer sb = new StringBuffer();
PrintWriter pw = null;
try {
InputStream socketIn = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(socketIn));
while(true) {
String msg = br.readLine();
sb.append(msg);
System.out.println(msg);
if (msg == null || msg.trim().equals("")) {
break;
}
}
//輸入響應(yīng)報(bào)文
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type: text/html;charset=UTF-8");
pw.println(); //如果注釋掉這句,下面的html不會(huì)打印出來,未出頭部
pw.write("html");
pw.flush();
} catch (IOException exception) {
//TODO 處理異常
} finally {
socket.close();
pw.close();
//socket = null;
}
}
}
}
啟動(dòng)程序后,在瀏覽器輸入http://127.0.0.1:8999/abc
查看報(bào)文。
但是上程序有一個(gè)問題,一個(gè)線程同一時(shí)間只能運(yùn)行一段代碼,在上面的例子中,邏輯處理是在主線程中運(yùn)行的(當(dāng)產(chǎn)生一個(gè)JVM進(jìn)程時(shí),同時(shí)會(huì)產(chǎn)生一個(gè)主線程,main方法中的代碼就是在主線程中執(zhí)行),當(dāng)一個(gè)請(qǐng)求還在處理邏輯和輸出時(shí),此時(shí)又來了一個(gè)請(qǐng)求,那么此請(qǐng)求將會(huì)被阻塞。所以我們可以把程序調(diào)整成如下樣子。
public class WebServer {
//服務(wù)端Socket只要一個(gè),所以定義成static, 同一時(shí)間只能一個(gè)線程訪問
private static ServerSocket ss;
public static void main(String[] args) throws IOException {
ss = new ServerSocket(8999);
//有線程永遠(yuǎn)不死,所以用了個(gè)死循環(huán)
while (true) {
Socket socket = ss.accept();
Thread thread = new Thread(new Handler(socket));
thread.start();
}
}
}
public class Handler implements Runnable {
private Socket socket;
public Handler(Socket socket){
this.socket=socket;
}
@Override
public void run() {
StringBuffer sb = new StringBuffer();
PrintWriter pw = null;
try {
InputStream socketIn = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(socketIn));
while(true) {
String msg = br.readLine();
sb.append(msg);
System.out.println(msg);
if (msg == null || msg.trim().equals("")) {
break;
}
}
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type: text/html;charset=UTF-8");
pw.println();
pw.write("html");
pw.flush();
} catch (IOException exception) {
//TODO 處理異常
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
pw.close();
//socket = null;
}
}
}
這中間的main方法相當(dāng)于一個(gè)公司的前臺(tái),將客戶(請(qǐng)求) 接入到公司,安排其他人來處理。
這樣每個(gè)請(qǐng)求一個(gè)線程,運(yùn)行完成后,線程就死了,這樣主線程負(fù)擔(dān)就輕了,不會(huì)發(fā)生阻塞了,但是新問題又來了,每個(gè)請(qǐng)求一個(gè)線程,那線程太多了,所以我們真正應(yīng)該使用線程池。
servlet注意點(diǎn)
servlet可以在web配置中設(shè)置容器一啟動(dòng)就被創(chuàng)建、初始化而不是第一個(gè)訪問后再被創(chuàng)建、初始化
<load-on-startup>number(1or2or3)</load-on-startup>
數(shù)字越小越早被創(chuàng)建、初始化init(servletconfig) 可以從web.xml中獲取初始化數(shù)值。
servlet 中變量要定義在方法內(nèi),不允許放在servlet 下,防止線程串行,線程就不安全(如果這樣,會(huì)產(chǎn)生用戶a的線程進(jìn)行到一半,用戶b的線程進(jìn)來將用戶a的信息替換,用戶a有可能登陸到用戶b的賬號(hào))
無狀態(tài)的對(duì)象(只有方法或變量為只讀)可以放在servlet第一層下。
有狀態(tài)的對(duì)象(變量可以改變)注意線程安全問題。
線程注意點(diǎn)
- 線程、進(jìn)程、程序之間的關(guān)系
CPU是所有進(jìn)程共同擁有的,所有進(jìn)程(包括所有JVM進(jìn)程和非JVM進(jìn)程)。大家都知道一個(gè)CPU在一個(gè)時(shí)間點(diǎn),只能運(yùn)行一行指令,也就是我們的程序,在Java中,所有程序都必須運(yùn)行在線程里,所以CPU是調(diào)度線程再運(yùn)行線程中的指令(程序),這些指令在運(yùn)行時(shí)會(huì)向它的進(jìn)程申請(qǐng)使用公共財(cái)產(chǎn)(堆內(nèi)存),同時(shí)線程也有自己的私有財(cái)產(chǎn)(棧內(nèi)存),這樣就構(gòu)成了”內(nèi)存(堆)”,”線程(棧內(nèi)存)”,”類(程序)”三者之間的關(guān)系,打個(gè)比方來說:
一個(gè)家庭有夫妻兩個(gè),他們都有自己的小金庫(kù),同時(shí)也有家庭共同的財(cái)產(chǎn),丈夫在做一件事情時(shí),他要審請(qǐng)家庭財(cái)產(chǎn),同時(shí)要使用自己的小金庫(kù)才可以完成。那么在這個(gè)例子中,家庭就是一個(gè)進(jìn)程,夫妻是兩個(gè)線程,共同的財(cái)產(chǎn)是堆內(nèi)存,小金庫(kù)是棧內(nèi)存,事情就是類(程序)。夫妻在家庭這個(gè)空間中生存,如果夫妻兩人不幸都死了,那這個(gè)家庭就不存在了(相當(dāng)所有線程死了,進(jìn)程也就死了),但只要有一個(gè)還在,家庭就還在(進(jìn)程中只要有一個(gè)線程還存活,進(jìn)程就還存活)。 - 主線程的產(chǎn)生
啟動(dòng)一個(gè)JVM進(jìn)程時(shí),JVM會(huì)自動(dòng)為我們創(chuàng)建一個(gè)線程,把它命名成”main”, 并把這個(gè)類中的main方法(程序)放到這個(gè)線程中的run方法中去執(zhí)行。 - java中產(chǎn)生線程的方式
在java編程時(shí),除了main線程是由JVM為我們產(chǎn)生的以外,其它所有線程都由我們自己的程序生成。
Java中定義線程的方式有兩種,繼承Thread和實(shí)現(xiàn)Runnable接口。我們來看如下程序:
package com.zhengmenbb.thread;
public class ChildThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
for(long i = 0; i < Long.MAX_VALUE; i++) {
}
}
}
上面程序使用Runnable定義了一個(gè)線程類,在主程序(或者其它程序)中我們這樣調(diào)用:
package com.zhengmenbb.thread;
public class TestMain {
public static void main(String[] args) {
//System.out.println(Thread.currentThread().getName());
Thread thread = new Thread(new ChildThread());
thread.start();
}
}
運(yùn)行main方法,你會(huì)看到生成的線程名字為:Thread-0, 當(dāng)然你可以通過thread這個(gè)線程對(duì)象引用來重設(shè)他的名字,優(yōu)先級(jí)等。使用jconsole我們打開這個(gè)進(jìn)程的線程tab頁:
會(huì)發(fā)現(xiàn)main線程已死,但是Thread-0還活著,因?yàn)槲沂褂昧艘粋€(gè)時(shí)間很長(zhǎng)的循環(huán).這也證明了只要一個(gè)線程還活著,進(jìn)程是不會(huì)死的, 但如果你等把Thread-0中run方法執(zhí)行完成了,進(jìn)程就死了。記住,只要run方法中的代碼執(zhí)行完成了,線程就死了.
我們?cè)賮砜淳€程的另一種定義方法:繼承 Thread
package com.zhengmenbb.thread;
public class ChildThread1 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
for(long i = 0; i < Long.MAX_VALUE; i++) {
}
}
}
在主程序(或者其它程序)中我們這樣調(diào)用:
package com.zhengmenbb.thread;
public class TestMain {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
Thread thread = new ChildThread1();
thread.start();
}
}
兩種方式只是定義線程類方式不一樣,運(yùn)行時(shí)產(chǎn)生的線程是一樣的,強(qiáng)烈建議使用Runnable接口方式。
- ”用戶線程”和“守護(hù)線程”
請(qǐng)看如下代碼:
package com.zhengmenbb.thread;
public class TestMain {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("JVM 退出了");
}
});
Thread thread = new Thread(new ChildThread());
thread.setDaemon(true);
thread.start();
}
}
在上面這段代碼中,我把線程的daemon(“守護(hù)”),設(shè)置了true, 你再運(yùn)行這段代碼,發(fā)現(xiàn)JVM進(jìn)程很快退出了。我們知道m(xù)ain線程run方法運(yùn)行很快,所以很快就死了,相當(dāng)妻子死了,Thread-0這個(gè)線程我們?cè)O(shè)置了daemon(“守護(hù)”),也就是說妻子死了,丈夫要守護(hù)她,也自殺隨她去了,這樣家庭(JVM)就死了。
那下面我們定義一個(gè)“用戶線程”和“守護(hù)線程”:
“用戶線程”: 只要有一個(gè)這樣的線程還活著,JVM就不會(huì)退出,這樣的線程我們定義為用戶線程. 其實(shí)是主線程和我們把daemon(“守護(hù)”),設(shè)置為false的線程。
“守護(hù)線程”:只要沒有用戶線程存活了,我就會(huì)自殺,這樣JVM主會(huì)退出。只要有用戶線程活著,我也活著。這一類線程我們稱為“守護(hù)線程”, 其實(shí)就是把daemon(“守護(hù)”),設(shè)置為true的線程。
值得一提的是,當(dāng)你在一個(gè)守護(hù)線程中產(chǎn)生了其他線程,那么這些新產(chǎn)生的線程不用設(shè)置Daemon屬性,都將是守護(hù)線程,用戶線程同樣。