jvm進程
JVM進程可以由bin\jps查看。在命令行下輸入jps
- 由一個jdk文件系統(tǒng),可以產(chǎn)生很多jvm進程(沒bin\java.exe執(zhí)行后產(chǎn)生一個)
- JVM進程有個pid,默認情況下河他執(zhí)行的main所在的類相同
- bin\jconsole 可以監(jiān)視和管理java程序
- jvm的三大財產(chǎn)
- 內(nèi)存
內(nèi)存是JVM擁有的主要財產(chǎn)之一,內(nèi)存你看到了分堆和非堆,這兩個值是 在執(zhí)行命令java.exe是可以修改的,如
java.exe 類名 -Xmx3550m -Xms3550m -XX:MaxPermSize=16m
-Xmx堆內(nèi)存最大
-Xms堆內(nèi)存最小
-XX:MaxPermSize 非堆 (放class,static var)
一般在實際的過程中將Xms與 Xmx設(shè)置為一樣。應(yīng)為避免程序執(zhí)行后期內(nèi)存不夠或分配不及時。這兩個值的大小將直接影響程序的性能<br />
在eclipse中,可以在點Run configurations...后界面設(shè)置.

線程
程啟動運行時會有一個線程去啟動main方法
除了main線程,其它都是JVM內(nèi)置的,我們自己沒有開啟.在實際運行中,這里的線程太多和太少都不行,要維持在一個合理的范圍,而且也要時刻關(guān)注他們的狀態(tài)。如果程序中一個線程都沒有,那么進程也死了。類
其實就是程序,包括JRE中的類,使用的第三方的jar
包和我們應(yīng)用中自己寫的程序,這些類加載進入內(nèi)存,都放在非堆中。
所以我們JVM進程是一個有身份證(pid),有姓名(名稱),擁有內(nèi)存,程類(程序)的一個靜態(tài)實體(CPU無法調(diào)度執(zhí)行)。
java類加載器基本知識
java web server 基本實現(xiàn)原理
- 遠端服務(wù)器使用ServerSocket技術(shù)打開一個端口,等待請求的到來。
- 瀏覽器得到用戶輸入的地址(url)或者得到點擊聯(lián)接地址(url)。
- 瀏覽器看輸入的是IP還是域名,如果是域名,通過查找DNS服務(wù)找到此域名IP,并緩存到瀏覽器中,以加快下次查找速度。
- 瀏覽器組裝滿足HTTP協(xié)議的數(shù)據(jù)包(請求報文)。
- 瀏覽器請求在本機隨機開啟一個端口與服務(wù)端IP和服務(wù)端端口建立聯(lián)接 (TCP)。(本機IP + 本機端口 + 服務(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)報文)通過TCP聯(lián)接返回到請求端口上。
- 瀏覽器從請求端口得到數(shù)據(jù)解析響應(yīng)報文得到相應(yīng)數(shù)據(jù)后給瀏覽器軟件進行解析渲染。
- 請求關(guān)閉聯(lián)接
先用socket技術(shù)實現(xiàn)一個main方法
public class WebServer {
//服務(wù)端Socket只要一個,所以定義成static, 同一時間只能一個線程訪問(主線程)
private static ServerSocket ss;
public static void main(String[] args) throws IOException {
//綁定端口,只會執(zhí)行一次
ss = new ServerSocket(8999);
//主線程永遠不死,所以用了個死循環(huán)
while (true) {
//這是一個IO阻塞式語句,也就是如果沒有請求(IO操作)進來,當(dāng)前線程會在此等待,不再向下執(zhí)行
Socket socket = ss.accept();
//得到請求報文(內(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)報文
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不會打印出來,未出頭部
pw.write("html");
pw.flush();
} catch (IOException exception) {
//TODO 處理異常
} finally {
socket.close();
pw.close();
//socket = null;
}
}
}
}
啟動程序后,在瀏覽器輸入http://127.0.0.1:8999/abc查看報文。

但是上程序有一個問題,一個線程同一時間只能運行一段代碼,在上面的例子中,邏輯處理是在主線程中運行的(當(dāng)產(chǎn)生一個JVM進程時,同時會產(chǎn)生一個主線程,main方法中的代碼就是在主線程中執(zhí)行),當(dāng)一個請求還在處理邏輯和輸出時,此時又來了一個請求,那么此請求將會被阻塞。所以我們可以把程序調(diào)整成如下樣子。
public class WebServer {
//服務(wù)端Socket只要一個,所以定義成static, 同一時間只能一個線程訪問
private static ServerSocket ss;
public static void main(String[] args) throws IOException {
ss = new ServerSocket(8999);
//有線程永遠不死,所以用了個死循環(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)于一個公司的前臺,將客戶(請求) 接入到公司,安排其他人來處理。
這樣每個請求一個線程,運行完成后,線程就死了,這樣主線程負擔(dān)就輕了,不會發(fā)生阻塞了,但是新問題又來了,每個請求一個線程,那線程太多了,所以我們真正應(yīng)該使用線程池。
servlet注意點
servlet可以在web配置中設(shè)置容器一啟動就被創(chuàng)建、初始化而不是第一個訪問后再被創(chuàng)建、初始化
<load-on-startup>number(1or2or3)</load-on-startup>數(shù)字越小越早被創(chuàng)建、初始化init(servletconfig) 可以從web.xml中獲取初始化數(shù)值。
servlet 中變量要定義在方法內(nèi),不允許放在servlet 下,防止線程串行,線程就不安全(如果這樣,會產(chǎn)生用戶a的線程進行到一半,用戶b的線程進來將用戶a的信息替換,用戶a有可能登陸到用戶b的賬號)
無狀態(tài)的對象(只有方法或變量為只讀)可以放在servlet第一層下。
有狀態(tài)的對象(變量可以改變)注意線程安全問題。
線程注意點
- 線程、進程、程序之間的關(guān)系
CPU是所有進程共同擁有的,所有進程(包括所有JVM進程和非JVM進程)。大家都知道一個CPU在一個時間點,只能運行一行指令,也就是我們的程序,在Java中,所有程序都必須運行在線程里,所以CPU是調(diào)度線程再運行線程中的指令(程序),這些指令在運行時會向它的進程申請使用公共財產(chǎn)(堆內(nèi)存),同時線程也有自己的私有財產(chǎn)(棧內(nèi)存),這樣就構(gòu)成了”內(nèi)存(堆)”,”線程(棧內(nèi)存)”,”類(程序)”三者之間的關(guān)系,打個比方來說:
一個家庭有夫妻兩個,他們都有自己的小金庫,同時也有家庭共同的財產(chǎn),丈夫在做一件事情時,他要審請家庭財產(chǎn),同時要使用自己的小金庫才可以完成。那么在這個例子中,家庭就是一個進程,夫妻是兩個線程,共同的財產(chǎn)是堆內(nèi)存,小金庫是棧內(nèi)存,事情就是類(程序)。夫妻在家庭這個空間中生存,如果夫妻兩人不幸都死了,那這個家庭就不存在了(相當(dāng)所有線程死了,進程也就死了),但只要有一個還在,家庭就還在(進程中只要有一個線程還存活,進程就還存活)。 - 主線程的產(chǎn)生
啟動一個JVM進程時,JVM會自動為我們創(chuàng)建一個線程,把它命名成”main”, 并把這個類中的main方法(程序)放到這個線程中的run方法中去執(zhí)行。 - java中產(chǎn)生線程的方式
在java編程時,除了main線程是由JVM為我們產(chǎn)生的以外,其它所有線程都由我們自己的程序生成。
Java中定義線程的方式有兩種,繼承Thread和實現(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定義了一個線程類,在主程序(或者其它程序)中我們這樣調(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();
}
}
運行main方法,你會看到生成的線程名字為:Thread-0, 當(dāng)然你可以通過thread這個線程對象引用來重設(shè)他的名字,優(yōu)先級等。使用jconsole我們打開這個進程的線程tab頁:

會發(fā)現(xiàn)main線程已死,但是Thread-0還活著,因為我使用了一個時間很長的循環(huán).這也證明了只要一個線程還活著,進程是不會死的, 但如果你等把Thread-0中run方法執(zhí)行完成了,進程就死了。記住,只要run方法中的代碼執(zhí)行完成了,線程就死了.
我們再來看線程的另一種定義方法:繼承 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();
}
}
兩種方式只是定義線程類方式不一樣,運行時產(chǎn)生的線程是一樣的,強烈建議使用Runnable接口方式。
- ”用戶線程”和“守護線程”
請看如下代碼:
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(“守護”),設(shè)置了true, 你再運行這段代碼,發(fā)現(xiàn)JVM進程很快退出了。我們知道m(xù)ain線程run方法運行很快,所以很快就死了,相當(dāng)妻子死了,Thread-0這個線程我們設(shè)置了daemon(“守護”),也就是說妻子死了,丈夫要守護她,也自殺隨她去了,這樣家庭(JVM)就死了。
那下面我們定義一個“用戶線程”和“守護線程”:
“用戶線程”: 只要有一個這樣的線程還活著,JVM就不會退出,這樣的線程我們定義為用戶線程. 其實是主線程和我們把daemon(“守護”),設(shè)置為false的線程。
“守護線程”:只要沒有用戶線程存活了,我就會自殺,這樣JVM主會退出。只要有用戶線程活著,我也活著。這一類線程我們稱為“守護線程”, 其實就是把daemon(“守護”),設(shè)置為true的線程。
值得一提的是,當(dāng)你在一個守護線程中產(chǎn)生了其他線程,那么這些新產(chǎn)生的線程不用設(shè)置Daemon屬性,都將是守護線程,用戶線程同樣。