java知識擴展

jvm進程

JVM進程可以由bin\jps查看。在命令行下輸入jps

  1. 由一個jdk文件系統(tǒng),可以產(chǎn)生很多jvm進程(沒bin\java.exe執(zhí)行后產(chǎn)生一個)
  2. JVM進程有個pid,默認情況下河他執(zhí)行的main所在的類相同
  3. bin\jconsole 可以監(jiān)視和管理java程序
  4. 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)原理

  1. 遠端服務(wù)器使用ServerSocket技術(shù)打開一個端口,等待請求的到來。
  2. 瀏覽器得到用戶輸入的地址(url)或者得到點擊聯(lián)接地址(url)。
  3. 瀏覽器看輸入的是IP還是域名,如果是域名,通過查找DNS服務(wù)找到此域名IP,并緩存到瀏覽器中,以加快下次查找速度。
  4. 瀏覽器組裝滿足HTTP協(xié)議的數(shù)據(jù)包(請求報文)。
  5. 瀏覽器請求在本機隨機開啟一個端口與服務(wù)端IP和服務(wù)端端口建立聯(lián)接 (TCP)。(本機IP + 本機端口 + 服務(wù)端IP + 服務(wù)端端口,用來唯一標(biāo)示一條TCP 聯(lián)接)
  6. 通過此聯(lián)接發(fā)送HTTP數(shù)據(jù)包。
  7. 服務(wù)端通過端口接收到數(shù)據(jù)之后,解析HTTP數(shù)據(jù)包,組裝成良好的格式,并調(diào)用程序處理。
  8. 服務(wù)程序處理完成之后,服務(wù)端組裝滿足HTTP協(xié)議的數(shù)據(jù)包(響應(yīng)報文)通過TCP聯(lián)接返回到請求端口上。
  9. 瀏覽器從請求端口得到數(shù)據(jù)解析響應(yīng)報文得到相應(yīng)數(shù)據(jù)后給瀏覽器軟件進行解析渲染。
  10. 請求關(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)的對象(變量可以改變)注意線程安全問題。

線程注意點

  1. 線程、進程、程序之間的關(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)所有線程死了,進程也就死了),但只要有一個還在,家庭就還在(進程中只要有一個線程還存活,進程就還存活)。
  2. 主線程的產(chǎn)生
    啟動一個JVM進程時,JVM會自動為我們創(chuàng)建一個線程,把它命名成”main”, 并把這個類中的main方法(程序)放到這個線程中的run方法中去執(zhí)行。
  3. 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頁:

Paste_Image.png

會發(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接口方式。

  1. ”用戶線程”和“守護線程”

請看如下代碼:

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屬性,都將是守護線程,用戶線程同樣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,887評論 11 349
  • 一、認識多任務(wù)、多進程、單線程、多線程 要認識多線程就要從操作系統(tǒng)的原理說起。 以前古老的DOS操作系統(tǒng)(V 6....
    GT921閱讀 1,103評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,699評論 19 139
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,602評論 1 15
  • 為喪失提供的一個哀傷過程 --------心理咨詢手記 人的成長過程中伴...
    恰如初閱讀 386評論 0 0

友情鏈接更多精彩內(nèi)容