1. 程式人生 > 其它 >【Java高階工程師蛻變之路】024 Tomcat原始碼剖析與調優

【Java高階工程師蛻變之路】024 Tomcat原始碼剖析與調優

手寫mini版Tomcat

Minicat要做的事情:

作為一個伺服器軟體提供服務的,也即我們可以通過瀏覽器客戶端傳送http請求, Minicat可以接收到請求進行處理,處理之後的結果可以返回瀏覽器客戶端。
1)提供服務,接收請求(Socket通訊) 2)請求資訊封裝成Request物件(Response物件) 3)客戶端請求資源,資源分為靜態資源(html)和動態資源(Servlet) 4)資源返回給客戶端瀏覽器

1.0

V1.0需求:瀏覽器請求http://localhost:8080,返回一個固定的字串到⻚面"Hello Minicat!"

2.0

V2.0需求:封裝Request和Response物件,返回html靜態資原始檔

3.0

V3.0需求:可以請求動態資源(Servlet)

4.0

V4.0需求:在已有Minicat基礎上進一步擴充套件,模擬出webapps部署效果 磁碟上放置一個webapps目錄,webapps中可以有多個專案,例如demo1、demo2、demo3... 每個專案中含有servlet,可以根據請求url定位對應servlet進一步處理。

最終程式碼

完成4.0版本之後,最終程式碼如下:

Bootstrap啟動類

/**
 * Minicat的主類
 */
public class Bootstrap {

    /**
     * 定義socket監聽的埠號
     */
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }


    /**
     * Minicat啟動需要初始化展開的一些操作
     */
    public void start() throws Exception {

        // 載入解析相關的配置,web.xml
        loadServlet();

        // 解析server.xml,讀取webapps路徑
        // 遍歷webapps,每個應用通過單獨設定類載入器(防止預設的類載入機制導致不同應用的相同class問題)
        // 讀取web.xml的servlet配置,生成servlet儲存到servletMap
        // 儲存的key需要加上應用的字首
        loadWebapps();

        System.out.println("全部載入完成,servletMap:" + servletMap);

        // 定義一個執行緒池
        int corePoolSize = 10;
        int maximumPoolSize = 50;
        long keepAliveTime = 100L;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();


        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                threadFactory,
                handler
        );


        /*
            完成Minicat 1.0版本
            需求:瀏覽器請求http://localhost:8080,返回一個固定的字串到頁面"Hello Minicat!"
         */
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=====>>>Minicat start on port:" + port);

        /*while(true) {
            Socket socket = serverSocket.accept();
            // 有了socket,接收到請求,獲取輸出流
            OutputStream outputStream = socket.getOutputStream();
            String data = "Hello Minicat!";
            String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;
            outputStream.write(responseText.getBytes());
            socket.close();
        }*/


        /**
         * 完成Minicat 2.0版本
         * 需求:封裝Request和Response物件,返回html靜態資原始檔
         */
        /*while(true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();

            // 封裝Request物件和Response物件
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());

            response.outputHtml(request.getUrl());
            socket.close();

        }*/


        /**
         * 完成Minicat 3.0版本
         * 需求:可以請求動態資源(Servlet)
         */
        /*while(true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();

            // 封裝Request物件和Response物件
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());

            // 靜態資源處理
            if(servletMap.get(request.getUrl()) == null) {
                response.outputHtml(request.getUrl());
            }else{
                // 動態資源servlet請求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request,response);
            }

            socket.close();

        }
*/

        /*
            多執行緒改造(不使用執行緒池)
         */
        /*while(true) {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
            requestProcessor.start();
        }*/
        // System.out.println("=========>>>>>>使用執行緒池進行多執行緒改造");
        /*
            多執行緒改造(使用執行緒池)
         */
        /*
        while(true) {

            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
            //requestProcessor.start();
            threadPoolExecutor.execute(requestProcessor);
        }
        */

        /**
         * 完成Minicat 4.0版本
         * 需求:實現webapps部署,並支援多專案部署
         */
        while (true) {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
            threadPoolExecutor.execute(requestProcessor);
        }
    }

    /**
     * 載入解析server.xml,初始化webapps裡面的servlet
     */
    private void loadWebapps() {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(resourceAsStream);
            Element element = document.getRootElement();

            // 為了簡單,暫不考慮多個host的情況,以單個host為例
            List<Element> hosts = element.selectNodes("//Host");
            // 未找到主機,不處理
            if (null == hosts || hosts.size() == 0) {
                return;
            }

            Element host = hosts.get(0);
            String appBase = host.attributeValue("appBase");

            // 找到appBase
            String classPath = this.getClass().getResource(".").getFile();
            String absoluteAppBase = Paths.get(classPath).getParent().getParent().getParent().toAbsolutePath().toString() + "/" + appBase;
            File file = new File(absoluteAppBase);
            if (!file.exists()) {
                System.out.println("webapps目錄不存在,將不會載入任何應用");
                return;
            }
            System.out.println("準備從下面的目錄載入webapps:" + absoluteAppBase + "\n");

            File[] dirs = file.listFiles();

            for (File dir : dirs) {
                if (!dir.isDirectory()) {
                    continue;
                }

                // 遍歷每個應用
                String appPath = "/" + dir.getName();

                List<Element> contextList = host.elements();
                for (Element context : contextList) {
                    String docBase = context.attributeValue("docBase");
                    if (!appPath.equals(docBase)) {
                        continue;
                    }

                    String appPrefix = context.attributeValue("path");
                    String absAppPath = absoluteAppBase + docBase;

                    System.out.println("開始載入應用:" + appPrefix + ",應用路徑:" + absAppPath);
                    // 載入各個應用的servlet
                    loadWebappsServlet(appPrefix, absAppPath);
                }
            }
            System.out.println("載入webapps的servlet完成");
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }


    private Map<String, HttpServlet> servletMap = new HashMap<String, HttpServlet>();

    /**
     * 載入解析web.xml,初始化Servlet
     */
    private void loadServlet() {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                // <servlet-name>lagou</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.LagouServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();


                // 根據servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /lagou
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());

            }

            System.out.println("載入servlet完成");
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    private void loadWebappsServlet(String appPrefix, String absAppPath) {
        InputStream resourceAsStream = null;
        try {
            resourceAsStream = new FileInputStream(absAppPath + "/web.xml");

            SAXReader saxReader = new SAXReader();

            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                // <servlet-name>lagou</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.LagouServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();


                // 根據servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /lagou
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();

                // 使用自定義的類載入器載入
                MyClassLoader loader = new MyClassLoader(absAppPath, servletClass);
                Class<?> aClass = loader.loadClass(servletClass);

                // 注意:下面的會從預設的類載入器載入
                //  Class<?> aClass = Class.forName(servletClass);

                // 載入失敗不新增
                if (null == aClass) {
                    continue;
                }

                servletMap.put(appPrefix + urlPattern, (HttpServlet) aClass.newInstance());

            }

            System.out.println("載入應用" + appPrefix + "的servlet完成\n");
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }


    /**
     * Minicat 的程式啟動入口
     *
     * @param args
     */
    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            // 啟動Minicat
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定義的ClassLoader

/**
 * 自定義類載入器,主要用於應用的servlet載入
 *
 * @name: MyClassLoader
 * @author: terwer
 * @date: 2022-01-18 10:19
 **/
public class MyClassLoader extends ClassLoader {

    private String absAppPath;
    private String servletClass;

    public MyClassLoader(String absAppPath, String servletClass) {
        this.absAppPath = absAppPath;
        this.servletClass = servletClass;
    }

    /**
     * 通過指定全路徑記載class檔案
     *
     * @param name class檔案路徑(包名+class檔名)
     * @return Class<?>
     * @throws ClassNotFoundException
     */
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 這裡要注意:如果不是servlet,需要使用雙親委派
        // 因為應用裡面會使用Minicat裡面定義的類
        // 而這些類沒必要拷貝一份到應用裡面
        // 只有servlet才去跳過雙親委派
        if (!name.equals(this.servletClass)) {
            System.out.println("非servlet,不使用自定義類載入器:" + name);
            Class<?> clazz = this.getClass().getClassLoader().loadClass(name);
            return clazz;
        }

        String filePath = absAppPath + "/" + name.replaceAll("\\.", "/") + ".class";
        String classFullPath = "file://" + filePath;
        System.out.println("MyClassLoader開始載入:" + classFullPath);

        byte[] classBytes = null;
        Path path = null;

        try {
            path = Paths.get(new URI(classFullPath));
            classBytes = Files.readAllBytes(path);
        } catch (URISyntaxException | IOException e) {
            e.printStackTrace();
        }

        Class<?> clazz = defineClass(name, classBytes, 0, classBytes.length);

        return clazz;
    }
}

http協議工具類

/**
 * http協議工具類,主要是提供響應頭資訊,這裡我們只提供200和404的情況
 */
public class HttpProtocolUtil {

    /**
     * 為響應碼200提供請求頭資訊
     * @return
     */
    public static String getHttpHeader200(long contentLength) {
        return "HTTP/1.1 200 OK \n" +
                "Content-Type: text/html \n" +
                "Content-Length: " + contentLength + " \n" +
                "\r\n";
    }

    /**
     * 為響應碼404提供請求頭資訊(此處也包含了資料內容)
     * @return
     */
    public static String getHttpHeader404() {
        String str404 = "<h1>404 not found</h1>";
        return "HTTP/1.1 404 NOT Found \n" +
                "Content-Type: text/html \n" +
                "Content-Length: " + str404.getBytes().length + " \n" +
                "\r\n" + str404;
    }
}

Request封裝類

/**
 * 把請求資訊封裝為Request物件(根據InputSteam輸入流封裝)
 */
public class Request {

    private String method; // 請求方式,比如GET/POST
    private String url;  // 例如 /,/index.html

    private InputStream inputStream;  // 輸入流,其他屬性從輸入流中解析出來


    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public Request() {
    }


    // 構造器,輸入流傳入
    public Request(InputStream inputStream) throws IOException {
        this.inputStream = inputStream;

        // 從輸入流中獲取請求資訊
        int count = 0;
        while (count == 0) {
            count = inputStream.available();
        }

        byte[] bytes = new byte[count];
        inputStream.read(bytes);

        String inputStr = new String(bytes);
        // 獲取第一行請求頭資訊
        String firstLineStr = inputStr.split("\\n")[0];  // GET / HTTP/1.1

        String[] strings = firstLineStr.split(" ");

        this.method = strings[0];
        this.url = strings[1];

        System.out.println("=====>>method:" + method);
        System.out.println("=====>>url:" + url);


    }
}

Response封裝類

/**
 * 封裝Response物件,需要依賴於OutputStream
 *
 * 該物件需要提供核心方法,輸出html
 */
public class Response {

    private OutputStream outputStream;

    public Response() {
    }

    public Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }


    // 使用輸出流輸出指定字串
    public void output(String content) throws IOException {
        outputStream.write(content.getBytes());
    }


    /**
     *
     * @param path  url,隨後要根據url來獲取到靜態資源的絕對路徑,進一步根據絕對路徑讀取該靜態資原始檔,最終通過
     *                  輸出流輸出
     *              /-----> classes
     */
    public void outputHtml(String path) throws IOException {
        // 獲取靜態資原始檔的絕對路徑
        String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);

        // 輸入靜態資原始檔
        File file = new File(absoluteResourcePath);
        if(file.exists() && file.isFile()) {
            // 讀取靜態資原始檔,輸出靜態資源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
        }else{
            // 輸出404
            output(HttpProtocolUtil.getHttpHeader404());
        }

    }

}

靜態資源處理工具類

public class StaticResourceUtil {

    /**
     * 獲取靜態資原始檔的絕對路徑
     * @param path
     * @return
     */
    public static String getAbsolutePath(String path) {
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replaceAll("\\\\","/") + path;
    }


    /**
     * 讀取靜態資原始檔輸入流,通過輸出流輸出
     */
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {

        int count = 0;
        while(count == 0) {
            count = inputStream.available();
        }

        int resourceSize = count;
        // 輸出http請求頭,然後再輸出具體內容
        outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());

        // 讀取內容輸出
        long written = 0 ;// 已經讀取的內容長度
        int byteSize = 1024; // 計劃每次緩衝的長度
        byte[] bytes = new byte[byteSize];

        while(written < resourceSize) {
            if(written  + byteSize > resourceSize) {  // 說明剩餘未讀取大小不足一個1024長度,那就按真實長度處理
                byteSize = (int) (resourceSize - written);  // 剩餘的檔案內容長度
                bytes = new byte[byteSize];
            }

            inputStream.read(bytes);
            outputStream.write(bytes);

            outputStream.flush();
            written+=byteSize;
        }
    }
}

動態資源請求

Servlet

public interface Servlet {

    void init() throws Exception;

    void destory() throws Exception;

    void service(Request request,Response response) throws Exception;
}

HttpServlet

public abstract class HttpServlet implements Servlet{


    public abstract void doGet(Request request,Response response);

    public abstract void doPost(Request request,Response response);


    @Override
    public void service(Request request, Response response) throws Exception {
        if("GET".equalsIgnoreCase(request.getMethod())) {
            doGet(request,response);
        }else{
            doPost(request,response);
        }
    }
}

業務類LagouServlet

public class LagouServlet extends HttpServlet {
    @Override
    public void doGet(Request request, Response response) {


        // try {
        //     Thread.sleep(100000);
        // } catch (InterruptedException e) {
        //     e.printStackTrace();
        // }
        String content = "<h1>LagouServlet get</h1>";
        try {
            response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(Request request, Response response) {
        String content = "<h1>LagouServlet post</h1>";
        try {
            response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destory() throws Exception {

    }
}

多執行緒版RequestProcessor

public class RequestProcessor extends Thread {

    private Socket socket;
    private Map<String,HttpServlet> servletMap;

    public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
        this.socket = socket;
        this.servletMap = servletMap;
    }

    @Override
    public void run() {
        try{
            InputStream inputStream = socket.getInputStream();

            // 封裝Request物件和Response物件
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());

            // 靜態資源處理
            if(servletMap.get(request.getUrl()) == null) {
                response.outputHtml(request.getUrl());
            }else{
                // 動態資源servlet請求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request,response);
            }

            socket.close();

        }catch (Exception e) {
            e.printStackTrace();
        }

    }
}

完整程式碼地址

https://gitee.com/youweics/Minicat

Tomcat原始碼剖析

Tomcat原始碼構建

下載原始碼

https://tomcat.apache.org/download-80.cgi

https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.73/src/apache-tomcat-8.5.73-src.tar.gz

原始碼匯入準備

  1. 解壓原始碼,在 apache-tomcat-8.5.73-src 目錄建立一個 pom.xml 檔案,內容如下
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVer
<!--引入編譯外掛--> <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
<!--tomcat 依賴的基礎包--> 
<dependencies>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>ant</groupId>
            <artifactId>ant</artifactId>
<version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml</groupId>
            <artifactId>jaxrpc</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compiler</groupId>
            <artifactId>ecj</artifactId>
            <version>4.5.1</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.soap</groupId>
            <artifactId>javax.xml.soap-api</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>
</project>
  1. 在 apache-tomcat-8.5.73-src 目錄中建立 source 資料夾

  2. 將 conf、webapps 目錄移動到剛剛建立的 source 資料夾中

將原始碼工程匯入到 IDEA 中

給 tomcat 的原始碼程式啟動類 Bootstrap 配置 VM 引數,因為 tomcat 原始碼執行也需要載入配置文 件等。

-Dcatalina.base=/Users/terwer/Documents/code/tomcat8/apache-tomcat-8.5.73-src/source
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=/Users/terwer/Documents/code/tomcat8/apache-tomcat-8.5.73-src/source/conf/logging.properties

解決 JSP 編譯錯誤

需要在tomcat的原始碼ContextConfig類中 的configureStart方法中增加一行程式碼將 Jsp 引擎初始化

重啟Tomcat。

Tomcat核心流程原始碼剖析

生命週期

Tomcat中的各容器元件都會涉及建立、銷燬等,因此設計了生命週期介面Lifecycle進行統一規範,各容 器元件實現該介面。

Lifecycle生命週期介面繼承體系示意圖

核心原始碼

主要關注Tomcat啟動流程和Tomcat請求處理流程

Tomcat啟動流程

Tomcat請求處理流程

請求處理流程分析

請求處理流程示意圖

Tomcat Mapper元件的體系結構

Tomcat類載入機制剖析

Java類(.java)—> 位元組碼檔案(.class) —> 位元組碼檔案需要被載入到jvm記憶體當中(這個過程就是一個 類載入的過程)

類載入器(ClassLoader,說白了也是一個類,jvm啟動的時候先把類載入器讀取到記憶體當中去,其他的 類(比如各種jar中的位元組碼檔案,自己開發的程式碼編譯之後的.class檔案等等))

要熟悉Tomcat的類載入機制,首先的熟悉JVM的類載入機制,因為Tomcat是執行在JVM之上的

JVM類載入機制

JVM 的類載入機制中有一個非常重要的⻆色叫做類載入器(ClassLoader),類載入器有自己的體系, Jvm內建了幾種類載入器,包括:引導類載入器、擴充套件類載入器、系統類載入器,他們之間形成父子關 系,通過 Parent 屬性來定義這種關係,最終可以形成樹形結構。

使用者可以自定義類載入器,用於載入特定目錄下的class檔案

當 JVM 執行過程中,使用者自定義了類載入器去載入某些類時,會按照下面的步驟(父類委託機制)

  1. 使用者自己的類載入器,把載入請求傳給父載入器,父載入器再傳給其父載入器,一直到載入器 樹的頂層

  2. 最頂層的類載入器首先針對其特定的位置載入,如果載入不到就轉交給子類

  3. 如果一直到底層的類載入都沒有載入到,那麼就會丟擲異常 ClassNotFoundException

因此,按照這個過程可以想到,如果同樣在 classpath 指定的目錄中和自己工作目錄中存放相同的 class,會優先載入 classpath 目錄中的檔案

雙親委派機制

什麼是雙親委派機制

當某個類載入器需要載入某個.class檔案時,它首先把這個任務委託給他的上級類載入器,遞迴這個操
作,如果上級的類載入器沒有載入,自己才會去載入這個類。

雙親委派機制的作用

防止重複載入同一個.class。通過委託去向上面問一問,載入過了,就不用再載入一遍。保證資料
安全。

保證核心.class不能被篡改。通過委託方式,不會去篡改核心.class,即使篡改也不會去載入,即使 載入也不會是同一個.class物件了。不同的載入器載入同一個.class也不是同一個.class物件。這樣保證了class執行安全(如果子類載入器先載入,那麼我們可以寫一些與java.lang包中基礎類同名的類, 然後再定義一個子類載入器,這樣整個應用使用的基礎類就都變成我們自己定義的類了。 )
Object類 -----> 自定義類載入器(會出現問題的,那麼真正的Object類就可能被篡改了)

Tomcat的類載入機制

Tomcat 的類載入機制相對於 Jvm 的類載入機制做了一些改變。 沒有嚴格的遵從雙親委派機制,也可以說打破了雙親委派機制

比如:有一個tomcat,webapps下部署了兩個應用

app1/lib/a-1.0.jar com.lagou.edu.Abc

app2/lib/a-2.0.jar com.lagou.edu.Abc

不同版本中Abc類的內容是不同的,程式碼是不一樣的

  1. 引導類載入器 和 擴充套件類載入器 的作用不變
  2. 系統類載入器正常情況下載入的是 CLASSPATH 下的類,但是 Tomcat 的啟動指令碼並未使用該變 量,而是載入tomcat啟動的類,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。 位於CATALINA_HOME/bin下
  3. Common 通用類載入器載入Tomcat使用以及應用通用的一些類,位於CATALINA_HOME/lib下, 比如servlet-api.jar
  4. Catalina ClassLoader 用於載入伺服器內部可⻅類,這些類應用程式不能訪問 Shared ClassLoader 用於載入應用程式共享類,這些類伺服器不會依賴
  5. Webapp ClassLoader,每個應用程式都會有一個獨一無二的Webapp ClassLoader,他用來載入 本應用程式 /WEB-INF/classes 和 /WEB-INF/lib 下的類。

tomcat 8.5 預設改變了嚴格的雙親委派機制

首先從 Bootstrap Classloader 載入指定的類 如果未載入到,則從 /WEB-INF/classes 載入 如果未載入到,則從 /WEB-INF/lib/*.jar 載入

如果未載入到,則依次從 System、Common、Shared 載入(在這最後一步,遵從雙親委派機制)

Tomcat對https的支援

https簡介

Http超文字傳輸協議,明文傳輸 ,傳輸不安全,https在傳輸資料的時候會對資料進行加密 ssl協議
TLS(transport layer security)協議

https和http的區別

HTTPS協議使用時需要到電子商務認證授權機構(CA)申請SSL證書

HTTP預設使用8080埠,HTTPS預設使用8443埠

HTTPS則是具有SSL加密的安全性傳輸協議,對資料的傳輸進行加密,效果上相當於HTTP的升級版

HTTP的連線是無狀態的,不安全的;

HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網路協議,比HTTP協議安全

https的工作原理

Tomcat對https的支援

1、使用 JDK 中的 keytool 工具生成免費的祕鑰庫檔案(證書)

keytool -genkey -alias terwer -keyalg RSA -keystore terwer.keystore

2、配置 conf/server.xml

    maxThreads="150" schema="https" secure="true" SSLEnabled="true">
    <SSLHostConfig>
        <Certificate
certificateKeystoreFile="/Users/terwer/Documents/code/tomcat8/apache-tomcat-8.5.73/conf/terwer.keystore" certificateKeystorePassword="123456"  type="RSA"
/>
    </SSLHostConfig>
</Connector>

3、使用https協議訪問8443埠(https://localhost:8443)。

Tomcat的效能優化策略

系統性能的衡量指標,主要是響應時間和吞吐量。

  1. 響應時間:執行某個操作的耗時;

  2. 吞吐量:系統在給定時間內能夠支援的事務數量,單位為TPS(Transactions PerSecond的縮寫,也 就是事務數/秒,一個事務是指一個客戶機向伺服器傳送請求然後伺服器做出反應的過程。

Tomcat優化從兩個方面進行

1)JVM虛擬機器優化(優化記憶體模型)
2)Tomcat自身配置的優化(比如是否使用了共享執行緒池?IO模型?)

具體的效果需要根據生產環境調整到合適的引數

虛擬機器執行優化

Java 虛擬機器的執行優化主要是記憶體分配和垃圾回收策略的優化:

  • 記憶體直接影響服務的執行效率和吞吐量

  • 垃圾回收機制會不同程度地導致程式執行中斷(垃圾回收策略不同,垃圾回收次數和回收效率都是

  • 不同的)

Java虛擬機器相關引數

JVM記憶體模型

引數調整例項

 JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -
XX:MaxMetaspaceSize=512m"

利用記憶體工具檢視

ps -ef|grep tomcat      
jhsdb jmap --heap --pid 21336

垃圾回收策略(GC策略)

垃圾回收效能指標

吞吐量:工作時間(排除GC時間)佔總時間的百分比, 工作時間並不僅是程式執行的時間,還包 含記憶體分配時間。

暫停時間:由垃圾回收導致的應用程式停止響應次數/時間。

垃圾收集器

1、序列收集器(Serial Collector)

單執行緒執行所有的垃圾回收工作, 適用於單核CPU伺服器 工作程序-----|(單執行緒)垃圾回收執行緒進行垃圾收集|---工作程序繼續

2、並行收集器(Parallel Collector)

工作程序-----|(多執行緒)垃圾回收執行緒進行垃圾收集|---工作程序繼續

又稱為吞吐量收集器(關注吞吐量), 以並行的方式執行年輕代的垃圾回收, 該方式可以顯著降 低垃圾回收的開銷(指多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態)。適用於多 處理器或多執行緒硬體上執行的資料量較大的應用

3、併發收集器(Concurrent Collector)

以併發的方式執行大部分垃圾回收工作,以縮短垃圾回收的暫停時間。適用於那些響應時間優先於 吞吐量的應用, 因為該收集器雖然最小化了暫停時間(指使用者執行緒與垃圾收集執行緒同時執行,但不一 定是並行的,可能會交替進行), 但是會降低應用程式的效能

4、CMS收集器(Concurrent Mark Sweep Collector)

併發標記清除收集器, 適用於那些更願意縮短垃圾回收暫停時間並且負擔的起與垃圾回收共享處
理器資源的應用

5、G1收集器(Garbage-First Garbage Collector)

適用於大容量記憶體的多核伺服器, 可以在滿足垃圾回收暫停時間目標的同時, 以最大可能性實現 高吞吐量( JDK1.7之後)

調整垃圾收集器

JAVA_OPTS="-XX:+UseConcMarkSweepGC"

命令列開啟 jconsole ,可以看到 jdk11 預設是 G1收集器

調整之後

Tomcat配置調優