整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          JAVA編程思想.姊妹篇.JAVA編程講義.第1章 Java開發(fā)入門

          ava是一門功能強(qiáng)大的多用途編程語言,也是全球最流行的開發(fā)語言之一。它是面向?qū)ο缶幊陶Z言的代表,集跨平臺、健壯性、高性能等諸多優(yōu)點,廣泛應(yīng)用于Web后端開發(fā)、移動端開發(fā)、大數(shù)據(jù)分析、人工智能等熱門領(lǐng)域,在互聯(lián)網(wǎng)行業(yè)占據(jù)十分重要的地位。目前,全球有超過500萬的專業(yè)開發(fā)者在使用Java語言,Java程序運(yùn)行在全球數(shù)十億臺設(shè)備上。作為全書開篇,本章將對Java概述、開發(fā)環(huán)境搭建、程序開發(fā)步驟、虛擬機(jī)與垃圾回收、開發(fā)工具等內(nèi)容進(jìn)行講解,帶領(lǐng)帶領(lǐng)大家進(jìn)入Java世界。

          1.1 Java概述

          1.1.1 Java的由來與發(fā)展

          Java是一種高級計算機(jī)語言,它是由Sun公司(2009年4月20日被Oracle公司收購,2010年完成合并)于1995年5月推出的一種用來編寫跨平臺應(yīng)用軟件、完全面向?qū)ο蟮某绦蛟O(shè)計語言。Java語言簡單易用、安全可靠,自從問世以后,受到了市場的大力追捧。在PC、移動設(shè)備、家用電器等領(lǐng)域,Java技術(shù)無處不在。

          Sun公司在1995年推出Java語言以后,吸引了編程世界的廣泛關(guān)注。那么,Java到底有什么魔力呢?1990年末,Sun公司預(yù)測嵌入式系統(tǒng)將會在家電領(lǐng)域大顯身手,于是在1991年6月啟動了“Green計劃”,由詹姆斯·高斯林(James Gosling)、邁克·謝里丹(Mike Sheridan)等人帶領(lǐng)的開發(fā)團(tuán)隊負(fù)責(zé),準(zhǔn)備開發(fā)一種能夠在各種消費(fèi)性電子產(chǎn)品(如機(jī)頂盒、冰箱、收音機(jī)等)上運(yùn)行的程序架構(gòu),以便于人們與家用電器進(jìn)行信息交流與控制。因為家用電器的計算處理能力和內(nèi)存都非常有限,所以要求語言必須非常小且能夠生成非常緊湊的代碼,這樣才能在這樣的環(huán)境中執(zhí)行。另外,不同的家用電器使用的CPU不同,因此要求該語言必須是跨平臺的。開發(fā)團(tuán)隊最初考慮使用C++語言,但是C++太過復(fù)雜,以致很多開發(fā)者經(jīng)常錯誤使用,而且項目面向的是嵌入式平臺,可用的系統(tǒng)資源十分有限,所以Sun公司創(chuàng)始人之一的比爾·喬伊(Bill Joy)決定開發(fā)一種新語言,他提議在C++的基礎(chǔ)上開發(fā)一種面向?qū)ο蟮沫h(huán)境。Java便由此而問世,詹姆斯?高斯林最初將其命名為Oak(橡樹)。遺憾的是,當(dāng)時由于這門語言只能為家用電器提供一個通用環(huán)境,且受到諸多因素的限制,Oak語言沒有得到迅速推廣。1994年夏天,隨著Innernet的迅猛發(fā)展,瀏覽器的出現(xiàn),枯燥乏味的信息文檔已經(jīng)不滿足人們的需求,這給Oak語言帶來了新的生機(jī)。詹姆斯·高斯林立刻意識到這是一個機(jī)會,于是對Oak進(jìn)行了小規(guī)模的改造。之后,開發(fā)團(tuán)隊的其他成員完成了第一個基于Oak語言的網(wǎng)頁瀏覽器WebRunner,從而讓瀏覽器具有了在網(wǎng)頁中執(zhí)行內(nèi)嵌代碼的能力,可以創(chuàng)造含有動態(tài)內(nèi)容的網(wǎng)頁。1995年,Sun公司將Oak更名為Java,并將其直接發(fā)布在互聯(lián)網(wǎng)上,免費(fèi)開源給大家使用,獲得了廣大開發(fā)人員的青睞。之后,Java開始走紅,成為一門廣為人知的編程語言,被用于開發(fā)Web應(yīng)用程序。

          Java一開始具有吸引力,是因為Java程序可以在Web瀏覽器中運(yùn)行,隨著Internet普及和迅猛發(fā)展,以及Web技術(shù)的不斷更新,Java語言與時俱進(jìn)、推陳出新,使Java語言在現(xiàn)在社會經(jīng)濟(jì)發(fā)展和科學(xué)研究中,占據(jù)了越來越重要的地位。在最流行的語言流行指數(shù)Tiobe,RedMonk和PyPL中均長期排名前三,且多年是Tiobe排行榜中排名第一的語言。從手機(jī)軟件到企業(yè)級應(yīng)用、從無人駕駛汽車到線上支付、從Minecraft(我的世界)游戲娛樂到火星探測器太空探索,Java語言的使用場景非常廣泛。現(xiàn)在,Java廣泛應(yīng)用于開發(fā)服務(wù)器端的應(yīng)用程序,截止到2021年,Java開發(fā)占據(jù)了服務(wù)器端后臺開發(fā)80%以上的市場份額。

          Java語言目前主要應(yīng)用于如下領(lǐng)域:

          • 移動端安卓系統(tǒng)。目前手機(jī)的Android系統(tǒng)和IOS系統(tǒng)占據(jù)了市場的主導(dǎo)地位,在Android系統(tǒng)中大多數(shù)應(yīng)用都是用Java編寫的,所以想做好Android系統(tǒng)的程序員,擁有好的Java功底是很重要的。
          • 服務(wù)器端應(yīng)用程序。Java語言具有優(yōu)秀的可移植性和安全性,銀行、交通、石油、電力等大型領(lǐng)域信息化系統(tǒng)都選擇用Java進(jìn)行開發(fā)。
          • 云計算和大數(shù)據(jù)領(lǐng)域。隨著云計算技術(shù)的發(fā)展,越來越多的企業(yè)考慮將其應(yīng)用部署在Java平臺上。大數(shù)據(jù)技術(shù)是近些年最熱門的新興技術(shù),其主流框架都離不開Java平臺。總之,無論是公共云、私有云、大數(shù)據(jù)開發(fā),Java都是目前最適合的選擇。

          從Java編程語言本身角度來講,其嚴(yán)謹(jǐn)?shù)慕Y(jié)構(gòu),易懂的語法加上簡易的編寫為其之后的發(fā)展及革新提供了良好的保障。

          注意:Java是印度尼西亞爪哇島的英文名稱,因盛產(chǎn)咖啡而聞名。

          1.1.2 Java語言的特點

          Java語言是一門跨平臺的適用于移動端、服務(wù)器領(lǐng)域、分布式環(huán)境的面向?qū)ο蟪绦蛟O(shè)計語言,它之所以能從眾多編程語言中脫穎而出,成為最流行的服務(wù)端開發(fā)語言之一,是因為具備如下顯著特點:

          • 簡單易學(xué)。Java語言雖然衍生于C++,但是刪除了許多使用頻率低、不易理解和容易混淆的功能,比如指針、運(yùn)算符重載、多繼承等。這樣做可以使Java程序直接訪問內(nèi)存地址,保證程序更高的安全性,并提供了自動的垃圾回收機(jī)制GC,程序員不必再擔(dān)憂內(nèi)存管理問題。
          • 面向?qū)ο蟆ava是一種以對象為中心,以消息為驅(qū)動的面向?qū)ο蟮恼Z言,它提供了類、接口和繼承等,不支持類之間的多繼承,但是支持接口之間的多繼承,并支持類和接口之間的實現(xiàn)機(jī)制。
          • 平臺無關(guān)性。Java語言是平臺無關(guān)的語言,源文件(后綴為.java的文件)通過Java編譯器生成一種體系結(jié)構(gòu)中立性的目標(biāo)文件格式(后綴為.class的文件),依賴Java虛擬機(jī)(JVM)在目標(biāo)計算機(jī)系統(tǒng)中實現(xiàn)了平臺無關(guān)性,JVM是Java平臺無關(guān)的基礎(chǔ)。平臺無關(guān)性是確保程序可移植的最重要部分,Java還嚴(yán)格規(guī)定了各個基本數(shù)據(jù)類型的長度,Java編譯器是用Java語言實現(xiàn)的,Java的運(yùn)行環(huán)境是用ANSIC實現(xiàn)的,使Java系統(tǒng)本身具有很強(qiáng)的可移植性。
          • 支持多線程。線程是比進(jìn)程更小的執(zhí)行單位,很多操作系統(tǒng)都把線程視為基本的執(zhí)行單位。Java支持多線程編程,可以實現(xiàn)并發(fā)處理多個任務(wù),互不干涉,不會由于某一任務(wù)處于等待狀態(tài)而影響了其他任務(wù)的執(zhí)行,可以提高程序執(zhí)行效率。
          • 支持網(wǎng)絡(luò)編程。Java就是為網(wǎng)絡(luò)而設(shè)計的語言,完全支持互聯(lián)網(wǎng)的所有功能。Java通過系統(tǒng)類庫支持TCP/IP協(xié)議、UDP協(xié)議、HTTP協(xié)議等,用戶可以通過URL地址在網(wǎng)絡(luò)上很方便的訪問Web對象,實現(xiàn)與Web對象的信息交互。
          • 健壯性。Java語言是一門強(qiáng)類型語言,它在編譯和運(yùn)行時進(jìn)行大量的類型檢查,防止不匹配的數(shù)據(jù)類型的發(fā)生,并且具備了異常處理、強(qiáng)類型機(jī)制、GC自動回收等特性,保證了程序的穩(wěn)定、健壯。
          • 安全性。Java語言設(shè)計的目的是用于網(wǎng)絡(luò)/分布式運(yùn)算環(huán)境,因此Java語言非常注重安全性,以防遭到惡意程序的攻擊。Java語言除了丟棄指針來保證內(nèi)存使用安全以外,它通過自己的安全機(jī)制防止了惡意程序?qū)Ρ镜叵到y(tǒng)的破壞,主要通過字節(jié)碼校驗器檢查、限制從網(wǎng)絡(luò)加載的類只能訪問特定文件系統(tǒng)等,保證了Java成為了安全的編程語言。
          • 分布式計算。Java語言可以開發(fā)分布式計算的程序,具有強(qiáng)大的、使用簡單的聯(lián)網(wǎng)能力。它提供了很多可以用于網(wǎng)絡(luò)應(yīng)用編程的類庫,包括URL、URLConnection、Socket、ServerSocket等,使應(yīng)用程序可以象訪問本地文件系統(tǒng)那樣用URL訪問遠(yuǎn)程對象。

          想一想:你了解哪些語言?Java語言在眾多編程中脫穎而出的原因有哪些?

          1.2 Java開發(fā)環(huán)境搭建

          1.2.1 JDK簡介

          Java開發(fā)工具包(JavaSE Development Kits,簡稱JDK)是一套獨(dú)立程序構(gòu)成的集合,用于開發(fā)和測試Java程序,是Java程序開發(fā)的首要工具。

          JDK由Java API、Java工具和Java基礎(chǔ)的類庫等組成,其核心是Java API,API(Application Programming Interface,應(yīng)用程序編程接口)是Java提供的供編程人員使用的標(biāo)準(zhǔn)類庫,開發(fā)人員可以用這些類庫中的類來實現(xiàn)Java程序的各種功能,從而免去自行設(shè)計很多常用類的繁重工作,極大地提高開發(fā)效率。另外,Java API還包括一些重要的語言結(jié)構(gòu)以及基本圖形、網(wǎng)絡(luò)和文件I/O等。

          本書中使用的是JDK15版本,與之前的版本相比,JDK 15 為用戶提供了14項主要的增強(qiáng)(JEP),同時新增了1個孵化器模塊、3個預(yù)覽功能、2個不推薦使用的功能,并刪除了2個淘汰的功能。

          知識點撥:增強(qiáng)(JEP)、孵化器模塊(Incubator)和預(yù)覽特性(Preview)的具體含義:

          增強(qiáng):英文全稱為JDK Enhancement Proposals ,簡稱 JEP,是JDK 增強(qiáng)建議,主要包括新增特性和改進(jìn)提案。

          孵化器:實際上就是實驗版,主要從Java社區(qū)收集意見、反饋,穩(wěn)定性差,后期可能有比較大的變動,稱之為尚未定稿的API/工具。

          預(yù)覽特性:規(guī)格已經(jīng)成型,實現(xiàn)基本確定,但是最終未定稿,這些特性,還可能被移除,但可能性比較小,一般都會定下來。

          1.2.2 下載和安裝JDK

          編寫Java程序,首先要下載JDK安裝程序,讀者可以直接從Oracle公司的官方網(wǎng)站下載。

          通過瀏覽器打開Oracle官網(wǎng)(http://www.oracle.com/technetwork/java/javase/ downloads/index.html),根據(jù)提示進(jìn)入下載頁面,找到與自己的計算機(jī)操作系統(tǒng)對應(yīng)的JDK安裝文件下載鏈接,點擊下載即可。網(wǎng)頁內(nèi)容可能因版本或Oracle公司規(guī)劃而有所不同,用戶可以根據(jù)需要選擇所需要的JDK版本。

          JDK安裝文件下載成功后,就可以安裝了。本書使用的是64位的Windows10環(huán)境,接下來詳細(xì)演示W(wǎng)indows 64位平臺下JDK15的安裝過程,具體步驟如下:

          (1)雙擊從Oracle官網(wǎng)下載的JDK安裝文件,進(jìn)入JDK安裝界面,如圖1.1所示。

          (2)單擊圖1.1中的“下一步”按鈕,進(jìn)入到JDK自定義安裝界面,如圖1.2所示。

          圖1.1 JDK安裝界面 圖1.2 JDK默認(rèn)安裝路徑

          (3)建議選擇直接安裝到默認(rèn)目錄,單擊“下一步”按鈕即可進(jìn)行安裝,如圖 1.3所示。也可以點擊“更改”按鈕,自行選擇安裝目錄。

          (4)安裝完畢之后,彈出如圖1.4所示的界面,點擊“關(guān)閉”按鈕即可。

          圖1.3 等待安裝界面 圖1.4 安裝完畢界面

          1.2.3 環(huán)境變量配置

          在使用Java來編譯和運(yùn)行程序之前,必須先設(shè)置好環(huán)境變量。所謂環(huán)境變量,就是在操作系統(tǒng)中定義的變量,可供操作系統(tǒng)上的所有應(yīng)用程序使用。Path環(huán)境變量的作用是設(shè)置一個路徑,由操作系統(tǒng)去尋找該路徑下的文件(如.bat、.ext、.com等),對Java來說就是Java的安裝路徑。

          下面以Windows 10操作系統(tǒng)為例說明。具體步驟如下:

          (1)選擇“控制面板”→“系統(tǒng)和安全”→“系統(tǒng)”(也可以在桌面上右鍵單擊“此電腦”或“我的電腦”,選擇“屬性”選項),進(jìn)入到系統(tǒng)窗口,如圖1.5所示。

          單擊“高級系統(tǒng)設(shè)置”

          圖1.5 Windows 10系統(tǒng)窗口界面

          (2)單擊“高級系統(tǒng)設(shè)置”選項,彈出“系統(tǒng)屬性”窗口,如圖1.6所示。

          (3)單擊“環(huán)境變量(N)…”按鈕,彈出 “環(huán)境變量”窗口,如圖1.7所示。

          圖1.6 “系統(tǒng)屬性”窗口界面 圖1.7 “環(huán)境變量”窗口界面

          (4)在“環(huán)境變量”窗口的“系統(tǒng)變量(S)”區(qū)域中,單擊“新建(W)…”按鈕,打開“新建系統(tǒng)變量”窗口。并在“變量名(N)”文本框中輸入“JAVA_HOME”,在“變量值(V)”文本框中輸入JDK安裝目錄。筆者此時的安裝目錄為“C:\Program Files\Java\jdk-15”,如圖1.8所示。單擊“確定”按鈕,完成JAVA_HOME環(huán)境變量的配置。

          圖1.8 “新建系統(tǒng)變量”窗口界面

          (5)在“環(huán)境變量”窗口的“系統(tǒng)變量(S)”區(qū)域中,選中系統(tǒng)變量“Path”,如圖1.9所示。

          圖1.9 “環(huán)境變量”窗口需按照Path變量界面

          (6)在圖1.9所示的界面單擊“編輯(I)…”按鈕,打開“編輯環(huán)境變量”窗口,點擊“新建(N)”按鈕,在編輯頁面的文本框中添加“%JAVA_HOME%\bin”,如圖1.10所示。然后,單擊窗口的“確定”按鈕,保存環(huán)境變量,完成配置。

          圖1.10 “編輯系統(tǒng)環(huán)境變量”窗口界面

          注意:在配置Path環(huán)境變量時,JAVA_HOME環(huán)境變量并不是一定需要配置的,我們也可以直接將JDK的安裝路徑(C:\Program Files\Java\JDK-15\bin;)添加到Path環(huán)境變量中。這里配置JAVA_HOME的好處是,當(dāng)JDK的版本或安裝路徑發(fā)生變化時,只需要修改JAVA_HOME的值,而不用修改Path環(huán)境變量的值。

          個別圖書中會提到Classpath環(huán)境變量,Classpath環(huán)境變量的作用與Path環(huán)境變量的作用類似,它是JVM執(zhí)行Java程序時搜索類的路徑的順序,以最先找到為準(zhǔn),JDK1.5之后,如果沒有設(shè)置Classpath環(huán)境變量,則Java解釋器會在當(dāng)前路徑下搜索Java類,故本書不再贅述。

          1.2.4 測試開發(fā)環(huán)境搭建成功與否

          JDK配置完成后,需要測試JDK是否能夠在計算機(jī)上運(yùn)行,具體步驟如下:

          (1)同時按下鍵盤 Window鍵和R鍵,調(diào)出Dos命令行運(yùn)行窗口,在搜索框輸入cmd,如圖 1.11所示。

          (2)單擊“確定”按鈕,進(jìn)入命令行窗口,如圖1.12所示。

          圖1.11 運(yùn)行窗口 圖1.12 命令行窗口

          (3)在命令行窗口中輸入“javac”命令,并按Enter鍵,系統(tǒng)會輸出javac的幫助信息,如圖1.13所示,說明JDK已經(jīng)成功配置,否則需要仔細(xì)檢查JDK環(huán)境變量的配置是否正確。

          圖1.13 命令行信息

          1.2.5 JDK目錄詳細(xì)剖析

          JDK安裝成功后,系統(tǒng)會自動在我們的安裝目錄下生成一個目錄,稱為JDK目錄,如圖 1.14所示,我們必須熟悉JDK目錄各個文件夾的作用才能更好的學(xué)習(xí)與編寫代碼。

          圖1.14 JDK目錄

          接下來,簡單介紹一下JDK目錄及其子目錄的含義和作用:

          • bin:該目錄存放一些編譯器和工具,常用的有javac.exe(Java編譯器)、java.exe(Java運(yùn)行工具)、jar.exe(打包工具)、jdb – debugger(查錯工具)和javadoc.exe(文檔生成工具)等。
          • conf:用來存放一些JDK相關(guān)的配置文件。
          • include:該目錄是存放一些啟動JDK時需要引入的C語言的頭文件。
          • jmods:自JDK 11以后,JDK就采用了模塊化設(shè)計,以便縮小最終軟件的體積,方便定制,簡化管理。這個目錄下保存了核心模塊,也就是官方提供的各種類庫程序,具體內(nèi)容可以參考官方文檔。在JDK8中,這些資源以jar包的形式存放,如lib目錄中的rt.jar等。
          • legal目錄:所使用的協(xié)議等法律文件。
          • lib:lib是library的簡寫,存放Java類庫或庫文件,包含Java運(yùn)行環(huán)境的私有實現(xiàn)信息,不供外部使用,不能修改。src.zip文件也在該目錄中。

          注意:自JDK9 以后,就取消了目錄中的jre目錄,將之前jre目錄里面的內(nèi)容分散到其他各個目錄了。

          1.3 Java程序的開發(fā)步驟

          1.3.1 編寫源代碼

          在我們?yōu)殡娔X配好Java開發(fā)環(huán)境以后,也就代表著我們可以開始實現(xiàn)我們的Java開發(fā)之旅了,現(xiàn)在我們自己來動手編寫一個Java程序,親自感受一下Java語言的編寫規(guī)范。

          下面將編寫第一個Java程序,其功能是控制臺輸出“有夢想,一起實現(xiàn)!”,如例1-1所示。

          例1-1 HelloDream.java

          在開始編寫代碼之前,先在電腦D盤(本書使用D盤)中創(chuàng)建一個新的目錄及子目錄:“d:\javaCode\demo01”,然后在demo01下創(chuàng)建一個文本文件,并重命名為“HelloDream.java”,使用記事本打開,編寫如下程序代碼:

          1. class HelloDream{
          2. public static void main(String[] args){
          3. System.out.println("有夢想,一起實現(xiàn)!");
          4. }
          5. }

          例1-1是程序的源代碼,下面針對逐條語句進(jìn)行詳細(xì)的講解,如圖1.15所示。

          class是Java關(guān)鍵字,用來聲明該文件是一個類。類作為程序的基本單元存在,所有Java代碼必須寫在類里面

          我們自己定義的類名,方便識別,類名必須與文件名一致

          輸出語句,把我們要展示的內(nèi)容在控制臺輸出來

          這句聲明了一個main方法,作為Java程序的執(zhí)行入口,需要執(zhí)行的代碼都要寫在main方法的大括號內(nèi)

          圖1.15 記事本編寫的java代碼

          注意:在編寫Java代碼時,所有的符號必須用英文半角格式,不允許出現(xiàn)中文字符。

          1.3.2 編譯程序

          編寫好的Java代碼文件需要編譯成Java字節(jié)碼文件(class文件)才能運(yùn)行,Java程序的編譯步驟如下:

          接下來打開Dos命令行窗口,并按下面步驟來編譯和運(yùn)行HelloDream.java。

          (1)打開Dos命令行窗口,先將路徑切換到需要編譯代碼的位置,即在窗口依次輸入“d:\javaCode\demo01”和“d:”命令,如圖1.16所示。

          (2)切換好磁盤路徑之后,在命令行窗口輸入“javac HelloDream.java”命令,對源文件進(jìn)行編譯,如圖1.17所示。

          圖1.16 切換磁盤目錄 圖1.17 編譯Java源文件

          (3)在編譯成功后會發(fā)現(xiàn)同級目錄下多了一個名為“HelloDream.class”的文件,這個文件就是字節(jié)碼文件,如圖 1.18所示。

          編譯生成的字節(jié)碼文件

          圖1.18 命令行編譯后的文件目錄

          編程技巧:在進(jìn)行編譯的時候,要寫文件全名加后綴名。javac編譯utf-8編碼的java文件時,容易出現(xiàn)“錯誤: 編碼GBK的不可映射字符”。解決方法是添加encoding 參數(shù):javac -encoding utf-8 WordCount.java,如果還不能解決,將文件保存成ANSI編碼格式。

          1.3.3 運(yùn)行程序

          編譯完成之后,就可以運(yùn)行程序。在Dos命令行窗口接著輸入“java HelloDream”命令,運(yùn)行剛才已經(jīng)編譯好的java文件,運(yùn)行結(jié)果如圖 1.19所示。

          注意:在運(yùn)行的時候,輸入的是文件的全名,不加后綴名。

          1.3.4 簡化的編譯運(yùn)行程序

          通過前面的學(xué)習(xí),我們知道了一個Java程序需要經(jīng)過編寫、編譯、運(yùn)行3階段,而且細(xì)心的同學(xué)會發(fā)現(xiàn),在編譯的時候我們用的是javac命令,而在運(yùn)行的時候我們用的是java命令,這在一定程度上給大家?guī)砹瞬簧俾闊:迷冢琂DK9之后,Java程序的編譯運(yùn)行進(jìn)行了改動,變得更加簡便,不需要再使用javac命令對java文件進(jìn)行編譯后運(yùn)行,而是直接使用java命令對java文件進(jìn)行編譯運(yùn)行。

          接下來,我們將“d:\javaCode\demo01”目錄下編譯后的“HelloDrea.class”字節(jié)碼文件刪除掉,按照簡化后方法重新編譯運(yùn)行HelloDream.java程序,如圖1.20所示。

          圖1.19 運(yùn)行Java程序 圖1.20 Java命令編譯運(yùn)行

          通過圖1.20可以看到,我們只需要使用java命令就可以直接打印出java文件的輸出結(jié)果。

          1.3.5 反編譯

          java文件是高級語言代碼,class文件是低級語言代碼。編譯過程實際上是通過Java編譯器將高級語言的源代碼編譯為低級語言代碼。那么反過來,是否可以通過低級語言代碼進(jìn)行反向工程,獲取其源代碼呢?答案是肯定得,這個過程就叫做反編譯。雖然,機(jī)器語言很難反編譯為源代碼,但是中間代碼是可以進(jìn)行反編譯的,比如用戶可以把javac編譯得到的class文件進(jìn)行反編譯,將其轉(zhuǎn)換為java文件。通過反編譯,我們可以了解別人的代碼內(nèi)容,學(xué)習(xí)別人的代碼的實現(xiàn)思路,還可以通過源代碼查找bug、制作外掛等。

          Java中有很多反編譯工具,最常用的有如下幾種:

          • javap:javap是JDK自帶的一個工具,可以對代碼反編譯,也可以查看Java編譯器生成的字節(jié)碼。javap生成的文件并不是java文件,而是程序員可以看得懂的class字節(jié)碼文件。
          • jad:jad是一個比較不錯的反編譯工具,它可以把class文件反編譯成java文件的。但是,jad已經(jīng)很久不更新了,在對Java7生成的字節(jié)碼進(jìn)行反編譯時,偶爾會出現(xiàn)不支持的問題,在對Java 8的lambda表達(dá)式反編譯時就徹底失敗。
          • CFR:jad很好用,但是很久沒更新了,所以只能用一款新的工具替代它,CFR是一個不錯的選擇,相比jad來說,它的語法可能會稍微復(fù)雜一些,但是好在它可以工作。
          • JD-GUI:JD-GUI是一款功能十分強(qiáng)大的Java反編譯工具,它支持對整個jar文件進(jìn)行反編譯,其中文版可直接點擊進(jìn)行相關(guān)代碼的跳轉(zhuǎn),用戶可以使用它瀏覽和重建源代碼的即時訪問方法和字段,操作十分簡單。

          1.4 Java虛擬機(jī)與垃圾回收

          前面我們學(xué)習(xí)了Java程序的編寫、編譯與運(yùn)行過程,那么Java程序在計算機(jī)中運(yùn)行的底層原理是什么呢?它是如何實現(xiàn)跨平臺的呢?它在運(yùn)行過程中又是如何使用計算機(jī)內(nèi)存的呢?接下來,我們來學(xué)習(xí)Java虛擬機(jī)與垃圾回收機(jī)制。

          1.4.1 Java虛擬機(jī)JVM

          Java虛擬機(jī)(Java Virtual Machine ,JVM)是運(yùn)行Java程序必不可少的機(jī)制。Oracle的Java虛擬機(jī)規(guī)范給出了JVM的定義:JVM是在一臺真實的機(jī)器上用軟件方式實現(xiàn)的一臺假象機(jī)。虛擬機(jī)的代碼存儲在.class文件中,并且每個.class文件最多包含一個public class類的代碼。

          Java程序經(jīng)過編譯器(javac.exe)編譯之后,會產(chǎn)生與平臺無關(guān)的字節(jié)碼文件(即擴(kuò)展名.class的文件)。字節(jié)碼文件本質(zhì)上是一種標(biāo)準(zhǔn)化的可移植的二進(jìn)制格式,它最大的好處是可跨平臺運(yùn)行,也就是常說的“一次編譯,到處運(yùn)行”。字節(jié)碼文件必須交由解釋器來執(zhí)行,與計算機(jī)硬件、操作系統(tǒng)沒有關(guān)系,這個解釋程序就是JVM。換句話說,無論使用哪種操作平臺,只要其含有JVM,就可以運(yùn)行字節(jié)碼文件。事實上,正是有了Java虛擬機(jī)規(guī)范,才使得Java應(yīng)用程序達(dá)到與平臺無關(guān),從而實現(xiàn)可移植性,這也是Java語言風(fēng)靡全球、迅速普及的原因之一。

          回顧之前之前學(xué)習(xí)的代碼編譯、運(yùn)行過程,我們可以很容易地理解,JVM實現(xiàn)跨平臺代碼執(zhí)行的過程如圖1.21所示。

          圖1.21 JVM執(zhí)行流程圖

          最后需要強(qiáng)調(diào)的是,JVM的實現(xiàn)包括字節(jié)碼驗證、解釋器、內(nèi)存垃圾回收等,JVM虛擬機(jī)規(guī)范對運(yùn)行時數(shù)據(jù)區(qū)域的劃分及字節(jié)碼的優(yōu)化并不做嚴(yán)格的限制,它們的實現(xiàn)依不同的平臺而有所不同。

          1.4.2 垃圾回收機(jī)制

          在傳統(tǒng)的程序開發(fā)語言(C、C++及其他語言)中允許動態(tài)分配內(nèi)存,同時需要程序開發(fā)人員負(fù)責(zé)內(nèi)存資源的釋放,如果不釋放內(nèi)存,則隨著程序的不斷運(yùn)行,不斷有新的資源需要分配內(nèi)存,當(dāng)系統(tǒng)中沒有內(nèi)存可用時程序就會崩潰;或者,已動態(tài)分配的堆內(nèi)存由于某種原因未被程序釋放或無法釋放,也會造成系統(tǒng)內(nèi)存的浪費(fèi)。上述這些現(xiàn)象都被稱為“內(nèi)存漏洞”。

          垃圾回收(Garbage Collection,GC)就是指釋放垃圾對象所占用的空間,防止內(nèi)存溢出。內(nèi)存處理是讓所有編程人員都很頭疼的地方,如果忘記或者錯誤地回收內(nèi)存會導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動監(jiān)測對象并判斷是否超過作用域,從而確定是不是要回收對象。

          在Java語言中,引入了垃圾回收機(jī)制,程序開發(fā)者在編寫程序的時候無需考慮內(nèi)存管理問題。Java提供了后臺系統(tǒng)級線程,自動記錄每次內(nèi)存分配的情況,并統(tǒng)計每個內(nèi)存地址的引用次數(shù),不定時地對內(nèi)存中沒有被引用或者長時間沒有使用的對象進(jìn)行回收,這樣回收的內(nèi)存資源可以再次分配其他的內(nèi)存申請。

          垃圾回收能自動釋放內(nèi)存空間,使開發(fā)者可以將更多精力投入到軟件核心功能設(shè)計之上,不需要主動去考慮內(nèi)存漏洞的問題,極大地減輕了程序開發(fā)者編程的負(fù)擔(dān)。同時,垃圾回收是Java語言安全性策略的一個重要部份,它能夠有效保護(hù)程序的完整性。當(dāng)然,Java的垃圾回收也有一個潛在的缺點,就是它的開銷影響程序性能,Java虛擬機(jī)必須追蹤運(yùn)行程序中有用的對象,最終釋放沒用的對象,這個過程需要花費(fèi)CPU的時間。

          1.5 InteliJ IDEA開發(fā)工具

          在第1.3節(jié)編寫第一個Java程序時,我們使用的是記事本,這樣編寫程序比較辛苦且效率不高。那么,如何來提高編程效率呢?這就需要選擇一款優(yōu)秀的Java程序開發(fā)工具。

          1.5.1 IDEA概述

          在Java的學(xué)習(xí)和開發(fā)過程中,離不開一款功能強(qiáng)大、使用簡單、高效率的開發(fā)工具。程序開發(fā)工具又叫集成開發(fā)環(huán)境(IDE),是用于提供程序開發(fā)環(huán)境的應(yīng)用程序,通常包括代碼編輯器、編譯器、調(diào)試器和圖形用戶界面等,這類軟件一般集代碼編寫、分析、編譯、調(diào)試為一體,可以極大程度地提高編程效率。目前,最流行的Java集成開發(fā)環(huán)境有Eclipse、InteliJ IDEA、NetBeans、jGRASP、BlueJ等。曾經(jīng),Eclipse是Java IDE中的王者,近些年其風(fēng)頭逐步被InteliJ IDEA所取代。

          IntelliJ IDEA簡稱IDEA,是業(yè)界公認(rèn)最好的Java開發(fā)工具之一,特別在智能代碼助手、代碼自動提示、重構(gòu)、JavaEE支持、各類版本工具(git、svn等)、JUnit、CVS整合、代碼分析、 創(chuàng)新的GUI設(shè)計等方面,其功能可以說是超常的。

          1.5.2 IDEA的安裝與啟動

          接下來,我們就來介紹IDEA的下載、安裝與啟動方法(筆者寫稿時使用了IDEA的2021版,讀者可以直接在官網(wǎng)進(jìn)行下載),具體步驟如下:

          (1)通過網(wǎng)址( https://www.jetbrains.com/idea/)進(jìn)入官網(wǎng),如圖1.22所示。

          圖1.22 IDEA官網(wǎng)下載界面

          (2)點擊“Download”進(jìn)行下載,彈出下載界面,如圖 1.23所示。IntelliJ IDEA 提供了兩個版本,即 Ultimate(旗艦版)和 Community(社區(qū)版)。社區(qū)版是免費(fèi)的,但它的功能較少。旗艦版是商業(yè)版,提供了一組出色的工具和特性。

          圖1.23 idea2021版下載界面

          (3)點擊“Download”后會彈出如圖 1.24所示的注冊界面,讓我們進(jìn)行注冊,不用注冊,這時候已經(jīng)開始下載了。下載好安裝包后將其放在合適的位置,等待安裝即可。

          已開始下載

          圖1.24 等待下載與注冊界面

          (4)雙擊下載好的安裝包,彈出安裝界面,如圖 1.25所示。

          (5)點擊“Next>”按鈕選擇安裝目錄,一般選擇默認(rèn)即可,如果C盤空間不足可以選用其他盤符,如圖1.26 所示。

          圖1.25 安裝界面 圖1.26 程序安裝目錄界面

          (6)點擊“Next >”按鈕后,進(jìn)入安裝配置界面,勾選創(chuàng)建桌面快捷方式,本書使用的是64位操作系統(tǒng),所以勾選 “64bit Launder”(用戶請根據(jù)自己操作系統(tǒng)位數(shù),自行選擇),如圖 1.27所示。

          (7)點擊“Next >”按鈕,跳轉(zhuǎn)至開始安裝界面,如圖1.28所示。點擊“Install”按鈕即跳轉(zhuǎn)至等待安裝界面,如圖1.29所示。程序安裝完畢界面如圖1.30所示,點擊“Finish”按鈕即可。

          圖1.27 安裝配置界面 圖1.28 開始安裝界面

          圖1.29 安裝等待界面 圖1.30 安裝完畢界面

          1.5.3 使用IDEA進(jìn)行程序開發(fā)

          安裝好IDEA之后,接下來就帶領(lǐng)大家體驗使用IDEA進(jìn)行程序開發(fā)的過程,步驟如下:

          (1)在桌面上找到IDEA的快捷方式,雙擊圖標(biāo)打開IDEA,進(jìn)入“Welcome to IntelliJ IDEA”界面(IntelliJ IDEA旗艦版是商業(yè)收費(fèi)軟件,非付費(fèi)用戶首次登陸進(jìn)入不到收費(fèi)界面,但是該軟件為學(xué)生提供了人性化的福利,學(xué)生憑個人學(xué)號可以獲得免費(fèi)使用權(quán),具體根據(jù)官方提示操作即可),如圖1.31所示。IDEA界面的默認(rèn)顏色為黑色,默認(rèn)狀態(tài)下進(jìn)入下一步完全可以,如果不喜歡該風(fēng)格,可自行設(shè)置。在圖1.31所示界面選擇左側(cè)“Customize”,切換到如圖1.32所示的界面,再在右側(cè)“Color theme” 下拉選項卡選擇“IntelliJ Light”,背景色即可變?yōu)榱辽鐖D 1.33所示。在圖1.33所示界面選擇左側(cè)“Projects”,即可再次回到歡迎界面,如圖1.34所示。

          圖1.31 IDEA歡迎界面 圖1.32 IDEA界面顏色設(shè)置

          圖1.33 界面顏色變?yōu)榱辽?圖1.34 調(diào)整顏色后的歡迎界面

          (2)在歡迎界面點擊“New Project”按鈕后進(jìn)入“New Project”界面,如圖1.35所示。選擇項目類型和版本號,當(dāng)前選擇Java項目,Project SDK是“15 version 15”。接下來單擊“Next”按鈕,進(jìn)入如圖1.36所示的界面,本界面用來設(shè)置是否使用模板開發(fā),這里不用勾選。

          圖1.35 項目類型和版本 圖1.36 是否使用模板

          (3)點擊“Next”按鈕進(jìn)入下一步,按照如圖 1.37所示,輸入項目名稱、選擇項目目錄。

          點擊選擇項目存放目錄

          項目存放的目錄

          項目名稱

          圖1.37 設(shè)置項目名稱和目錄

          (4)單擊“Finish”按鈕,就可以看見我們用IDEA創(chuàng)建的第一個項目,如圖 1.38所示。

          圖1.38 創(chuàng)建好的項目

          (5)在src目錄下右鍵選擇“New”→“Java Class”,如圖1.39所示。點擊“Java Class”以后在彈窗內(nèi)填入類名,如圖1.40所示。

          圖1.39 創(chuàng)建類

          創(chuàng)建接口

          默認(rèn)創(chuàng)建類

          自定義類名,首字母大寫

          圖1.40 創(chuàng)建類過程中的彈窗

          (6)在類中編寫圖1.41所示的代碼,然后右鍵并點擊“Run 'HelloWorld.main()'”運(yùn)行代碼(或在菜單選擇“Run”→“Run 'HelloWorld'”,或者直接單擊工具欄的

          圖標(biāo))。

          圖1.41 編寫并運(yùn)行Java代碼

          程序運(yùn)行結(jié)果如下:

          HelloWorld!

          好好學(xué)習(xí)Java!

          程序運(yùn)行完成后,在IDEA打印運(yùn)行結(jié)果,通過在IDEA編輯器中編寫代碼,代碼編寫效率、執(zhí)行效率會更高。

          注意:.idea目錄用來存放項目的配置信息,包括歷史記錄,版本控制信息等內(nèi)容。

          1.6 本章小結(jié)

          • Java語言是由Sun公司推出的,是一種能夠跨平臺的面向?qū)ο蟮母呒壵Z言。
          • Java程序,需要經(jīng)過先編譯成字節(jié)碼文件,再次解釋的方式進(jìn)行執(zhí)行。
          • Java程序是平臺無關(guān)的,可以實現(xiàn)“一次編寫、到處運(yùn)行”。
          • Java語言具有簡單易學(xué)、跨平臺性、面向?qū)ο蟆踩浴⒕W(wǎng)絡(luò)編程、多線程、健壯性等特點。
          • 每個類都被編譯成一個獨(dú)立的字節(jié)碼文件,該文件名與類名一致,擴(kuò)展名為.class。
          • 字節(jié)碼文件最大的好處是可跨平臺執(zhí)行,可讓程序“編寫一次,到處運(yùn)行”。
          • Java是嚴(yán)格區(qū)分大小寫的語言,Java代碼中的每條語句都是分號(;)表示結(jié)束。
          • Java程序開發(fā)常用的開發(fā)工具主要有Eclipse、IDEA等,目前IDEA被業(yè)內(nèi)公認(rèn)最優(yōu)秀,需要去相關(guān)官網(wǎng)下載安裝,啟動運(yùn)行,用于編寫Java代碼,開發(fā)效率比較高。

          1.7 理論測試與實踐練習(xí)

          1.填空題

          1.1 Java的三大體系分別是______、______、______。

          1.2 編譯Java程序需要使用______命令。

          1.3 Java代碼編譯后的字節(jié)碼文件擴(kuò)展名為________。

          2.填空題

          2.1 Java程序未經(jīng)過編譯時的文件的后綴是( )

          A..dll B..exe

          C..class D..java

          2.2 用Java虛擬機(jī)編譯類名為Hello的應(yīng)用程序的正確命令是( )

          A.javac Hello.class B.Hello.class

          C.java Hello.java D.java Hello

          2.3 下列選項中,屬于Java語言特點的一項是( )

          A.使用繁瑣 B.編譯執(zhí)行

          C.節(jié)省內(nèi)存空間 D.面向?qū)ο?/span>

          2.4 Java屬于哪一類語言( )

          A.面向機(jī)器的語言 B.面向?qū)ο蟮恼Z言

          C.面向過程的語言 D.面向操作系統(tǒng)的語言

          3.簡答題

          3.1 簡述Java語言的特點。

          3.2 簡述 JDK的含義和作用。

          3.3 簡述什么是JVM。

          4.編程題

          4.1 編寫程序,在控制臺展示出“歡迎來AAA教育學(xué)習(xí)!”和“有夢想,一起實現(xiàn)!”兩句話。

          認(rèn)打包生成的jar是不能夠直接運(yùn)行的,因為帶有main方法的類信息不會添加到manifest中(打開jar文件中的META-INF/MANIFEST.MF文件,將無法看到Main-Class一行)。

          用maven打包java程序,當(dāng)執(zhí)行 java -jar 文件時提示 no main manifest attribute。
          為了生成可執(zhí)行的jar文件,需要借助插件。

          目錄:



          生成示例項目

          # 進(jìn)入你想創(chuàng)建項目的父文件夾
          cd /Volumes/RamDisk 
          # 查看當(dāng)前文件夾
          pwd
          # 生成項目
          docker run -itd --rm --name maven_quick_tmp \
          -v "$HOME/.m2/repository":/root/.m2/repository \
          -v "$PWD":/usr/src/mymaven \
          -w /usr/src/mymaven \
          virhuiai/maven_quick:version-aliyun  \
          mvn archetype:generate \
          -DarchetypeArtifactId=maven-archetype-quickstart  \
          -DinteractiveMode=false  \
          -DarchetypeVersion=1.4 \
          -DgroupId=com.virhuiai.www  \
          -DartifactId=hello-world \
          -DpackageName=com.virhuiai.www \
          -DarchetypeVersion=RELEASE

          查看下生成的項目結(jié)構(gòu):

          tree -C hello-world


          其中pom.xml的部分如下:


          如果要指定版本號,即將jdk版本替換為1.8:

          cd hello-world/
          sed -ri -e 's!<maven.compiler.source>1.7</maven.compiler.source>!<maven.compiler.source>1.8</maven.compiler.source>!g' pom.xml
          sed -ri -e 's!<maven.compiler.target>1.7</maven.compiler.target>!<maven.compiler.target>1.8</maven.compiler.target>!g' pom.xml

          方法一:maven-shade-plugin

          官網(wǎng)地址在:

          http://maven.apache.org/plugins/maven-shade-plugin/examples/executable-jar.html


          按說明,在pom.xml中添加以下內(nèi)容:

          <build>
          。。。
          <pluginManagement>
          。。。
          </pluginManagement>
          <plugins>
                <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-shade-plugin</artifactId>
                  <version>3.2.4</version>
                  <executions>
                    <execution>
                      <phase>package</phase>
                      <goals>
                        <goal>shade</goal>
                      </goals>
                      <configuration>
                        <transformers>
                          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <mainClass>com.virhuiai.www.App</mainClass>
                          </transformer>
                        </transformers>
                      </configuration>
                    </execution>
                  </executions>
                </plugin>
              </plugins>
            </build>
          

          注意這是直接位于build>plugins下的,不是pluginManagement里的,否則不會有效果()。
          現(xiàn)在執(zhí)行mvn clean install:

          mvn clean install


          其中有句:

          Replacing /usr/src/mymaven/hello-world/target/hello-world-1.0-SNAPSHOT.jar with /usr/src/mymaven/hello-world/target/hello-world-1.0-SNAPSHOT-shaded.jar

          說明已經(jīng)被替換成帶有Main-Class信息的可運(yùn)行jar。

          現(xiàn)在,在項目根目錄中執(zhí)行該jar文件:

          root@9275e11b3f0f:/usr/src/mymaven/hello-world# java -jar /usr/src/mymaven/hello-world/target/hello-world-1.0-SNAPSHOT.jar
          Hello World!

          方法二:maven-jar-plugin

                <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-jar-plugin</artifactId>
                  <configuration>
                    <archive>
                      <manifest>
                        <mainClass>com.virhuiai.www.App</mainClass> <!--  //主程序入口類,可以按住control,單機(jī)定位到該類-->
                      </manifest>
                    </archive>
                  </configuration>
                </plugin>
          

          注意這也是直接位于build>plugins下的,不是pluginManagement里的,pluginManagement指定版本號。

          、JVM中的類加載器類型

          從Java虛擬機(jī)的角度講,只有兩種不同的類加載器:啟動類加載器和其他類加載器。

          1.啟動類加載器(Boostrap ClassLoader):這個是由c++實現(xiàn)的,主要負(fù)責(zé)JAVA_HOME/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。

          2.其他類加載器:由java實現(xiàn),可以在方法區(qū)找到其Class對象。這里又細(xì)分為幾個加載器

          a).擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)用于加載JAVA_HOME/lib/ext目錄中的,或者被-Djava.ext.dirs系統(tǒng)變量指定所指定的路徑中所有類庫(jar),開發(fā)者可以直接使用擴(kuò)展類加載器。java.ext.dirs系統(tǒng)變量所指定的路徑的可以通過System.getProperty("java.ext.dirs")來查看。

          b).應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)java -classpath或-Djava.class.path所指的目錄下的類與jar包裝入工作。開發(fā)者可以直接使用這個類加載器。在沒有指定自定義類加載器的情況下,這就是程序的默認(rèn)加載器。

          c).自定義類加載器(User ClassLoader):在程序運(yùn)行期間, 通過java.lang.ClassLoader的子類動態(tài)加載class文件, 體現(xiàn)java動態(tài)實時類裝入特性。

          這四個類加載器的層級關(guān)系,如下圖所示。

          二、為什么要自定義類加載器

          1. 區(qū)分同名的類:假定在tomcat 應(yīng)用服務(wù)器,上面部署著許多獨(dú)立的應(yīng)用,同時他們擁有許多同名卻不同版本的類。要區(qū)分不同版本的類當(dāng)然是需要每個應(yīng)用都擁有自己獨(dú)立的類加載器了,否則無法區(qū)分使用的具體是哪一個。
          2. 類庫共享:每個web應(yīng)用在tomcat中都可以使用自己版本的jar。但存在如Servlet-api.jar,java原生的包和自定義添加的Java類庫可以相互共享。
          3. 加強(qiáng)類:類加載器可以在 loadClass 時對 class 進(jìn)行重寫和覆蓋,在此期間就可以對類進(jìn)行功能性的增強(qiáng)。比如使用javassist對class進(jìn)行功能添加和修改,或者添加面向切面編程時用到的動態(tài)代理,以及 debug 等原理。
          4. 熱替換:在應(yīng)用正在運(yùn)行的時候升級軟件,不需要重新啟動應(yīng)用。比如toccat服務(wù)器中JSP更新替換。

          三、自定義類加載器

          3.1 ClassLoader實現(xiàn)自定義類加載器相關(guān)方法說明

          要實現(xiàn)自定義類加載器需要先繼承ClassLoader,ClassLoader類是一個抽象類,負(fù)責(zé)加載classes的對象。自定義ClassLoader中至少需要了解其中的三個的方法: loadClass,findClass,defineClass。

          public Class<?> loadClass(String name) throws ClassNotFoundException {
          return loadClass(name, false);
          protected Class<?> findClass(String name) throws ClassNotFoundException {
          throw new ClassNotFoundException(name);
          }
          protected final Class<?> defineClass(String name, byte[] b, int off, int len)
          throws ClassFormatError
          {
          return defineClass(name, b, off, len, null);
          }
          

          loadClass:JVM在加載類的時候,都是通過ClassLoader的loadClass()方法來加載class的,loadClass使用雙親委派模式。如果要改變雙親委派模式,可以修改loadClass來改變class的加載方式。雙親委派模式這里就不贅述了。

          findClass:ClassLoader通過findClass()方法來加載類。自定義類加載器實現(xiàn)這個方法來加載需要的類,比如指定路徑下的文件,字節(jié)流等。

          definedClass:definedClass在findClass中使用,通過調(diào)用傳進(jìn)去一個Class文件的字節(jié)數(shù)組,就可以方法區(qū)生成一個Class對象,也就是findClass實現(xiàn)了類加載的功能了。

          貼上一段ClassLoader中l(wèi)oadClass源碼,見見真面目...

          protected Class<?> loadClass(String name, boolean resolve)
           throws ClassNotFoundException
          {
           synchronized (getClassLoadingLock(name)) {
           // First, check if the class has already been loaded
           Class<?> c = findLoadedClass(name);
           if (c == null) {
           long t0 = System.nanoTime();
           try {
           if (parent != null) {
           c = parent.loadClass(name, false);
           } else {
           c = findBootstrapClassOrNull(name);
           }
           } catch (ClassNotFoundException e) {
           // ClassNotFoundException thrown if class not found
           // from the non-null parent class loader
           }
           if (c == null) {
           // If still not found, then invoke findClass in order
           // to find the class.
           long t1 = System.nanoTime();
           c = findClass(name);
           // this is the defining class loader; record the stats
           sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
           sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
           sun.misc.PerfCounter.getFindClasses().increment();
           }
           }
           if (resolve) {
           resolveClass(c);
           }
           return c;
           }
          }
          

          源碼說明...

          /**
          * Loads the class with the specified <a href="#name">binary name</a>. The
          * default implementation of this method searches for classes in the
          * following order:
          *
          * <ol>
          *
          * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
          * has already been loaded. </p></li>
          *
          * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
          * on the parent class loader. If the parent is <tt>null</tt> the class
          * loader built-in to the virtual machine is used, instead. </p></li>
          *
          * <li><p> Invoke the {@link #findClass(String)} method to find the
          * class. </p></li>
          *
          * </ol>
          *
          * <p> If the class was found using the above steps, and the
          * <tt>resolve</tt> flag is true, this method will then invoke the {@link
          * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
          *
          * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
          * #findClass(String)}, rather than this method. </p>
          *
          * <p> Unless overridden, this method synchronizes on the result of
          * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
          * during the entire class loading process.
          *
          * @param name
          * The <a href="#name">binary name</a> of the class
          *
          * @param resolve
          * If <tt>true</tt> then resolve the class
          *
          * @return The resulting <tt>Class</tt> object
          *
          * @throws ClassNotFoundException
          * If the class could not be found
          */
          

          翻譯過來大概是:使用指定的二進(jìn)制名稱來加載類,這個方法的默認(rèn)實現(xiàn)按照以下順序查找類: 調(diào)用findLoadedClass(String)方法檢查這個類是否被加載過 使用父加載器調(diào)用loadClass(String)方法,如果父加載器為Null,類加載器裝載虛擬機(jī)內(nèi)置的加載器調(diào)用findClass(String)方法裝載類, 如果,按照以上的步驟成功的找到對應(yīng)的類,并且該方法接收的resolve參數(shù)的值為true,那么就調(diào)用resolveClass(Class)方法來處理類。 ClassLoader的子類最好覆蓋findClass(String)而不是這個方法。 除非被重寫,這個方法默認(rèn)在整個裝載過程中都是同步的(線程安全的)。

          resolveClass:Class載入必須鏈接(link),鏈接指的是把單一的Class加入到有繼承關(guān)系的類樹中。這個方法給Classloader用來鏈接一個類,如果這個類已經(jīng)被鏈接過了,那么這個方法只做一個簡單的返回。否則,這個類將被按照 Java?規(guī)范中的Execution描述進(jìn)行鏈接。

          3.2 自定義類加載器實現(xiàn)

          按照3.1的說明,繼承ClassLoader后重寫了findClass方法加載指定路徑上的class。先貼上自定義類加載器。

          package com.chenerzhu.learning.classloader;
          import java.nio.file.Files;
          import java.nio.file.Paths;
          /**
           * @author chenerzhu
           * @create 2018-10-04 10:47
           **/
          public class MyClassLoader extends ClassLoader {
           private String path;
           public MyClassLoader(String path) {
           this.path = path;
           }
           @Override
           protected Class<?> findClass(String name) throws ClassNotFoundException {
           try {
           byte[] result = getClass(name);
           if (result == null) {
           throw new ClassNotFoundException();
           } else {
           return defineClass(name, result, 0, result.length);
           }
           } catch (Exception e) {
           e.printStackTrace();
           }
           return null;
           }
           private byte[] getClass(String name) {
           try {
           return Files.readAllBytes(Paths.get(path));
           } catch (Exception e) {
           e.printStackTrace();
           }
           return null;
           }
          }
          

          以上就是自定義的類加載器了,實現(xiàn)的功能是加載指定路徑的class。再看看如何使用。

          package com.chenerzhu.learning.classloader;
          import org.junit.Test;
          /**
           * Created by chenerzhu on 2018/10/4.
           */
          public class MyClassLoaderTest {
           @Test
           public void testClassLoader() throws Exception {
           MyClassLoader myClassLoader = new MyClassLoader("src/test/resources/bean/Hello.class");
           Class clazz = myClassLoader.loadClass("com.chenerzhu.learning.classloader.bean.Hello");
           Object obj = clazz.newInstance();
           System.out.println(obj);
           System.out.println(obj.getClass().getClassLoader());
           }
          }
          

          首先通過構(gòu)造方法創(chuàng)建MyClassLoader對象myClassLoader,指定加載src/test/resources/bean/Hello.class路徑的Hello.class(當(dāng)然這里只是個例子,直接指定一個class的路徑了)。然后通過myClassLoader方法loadClass加載Hello的Class對象,最后實例化對象。以下是輸出結(jié)果,看得出來實例化成功了,并且類加載器使用的是MyClassLoader。

          在此我向大家推薦一個Java學(xué)習(xí)交流群。交流學(xué)習(xí)群號:874811168 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,一起學(xué)習(xí),一起進(jìn)步,目前受益良多。

          com.chenerzhu.learning.classloader.bean.Hello@2b2948e2
          com.chenerzhu.learning.classloader.MyClassLoader@335eadca
          

          四、類Class卸載

          JVM中class和Meta信息存放在PermGen space區(qū)域(JDK1.8之后存放在MateSpace中)。如果加載的class文件很多,那么可能導(dǎo)致元數(shù)據(jù)空間溢出。引起java.lang.OutOfMemory異常。對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。所以需要在JVM中卸載(unload)類Class。

          JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):

          1. 該類所有的實例都已經(jīng)被GC。
          2. 該類的java.lang.Class對象沒有在任何地方被引用。
          3. 加載該類的ClassLoader實例已經(jīng)被GC。

          很容易理解,就是要被卸載的類的ClassLoader實例已經(jīng)被GC并且本身不存在任何相關(guān)的引用就可以被卸載了,也就是JVM清除了類在方法區(qū)內(nèi)的二進(jìn)制數(shù)據(jù)。

          JVM自帶的類加載器所加載的類,在虛擬機(jī)的生命周期中,會始終引用這些類加載器,而這些類加載器則會始終引用它們所加載的類的Class對象。因此這些Class對象始終是可觸及的,不會被卸載。而用戶自定義的類加載器加載的類是可以被卸載的。雖然滿足以上三個條件Class可以被卸載,但是GC的時機(jī)我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。

          五、JVM自定義類加載器加載指定classPath下的所有class及jar

          經(jīng)過以上幾個點的說明,現(xiàn)在可以實現(xiàn)JVM自定義類加載器加載指定classPath下的所有class及jar了。這里沒有限制class和jar的位置,只要是classPath路徑下的都會被加載進(jìn)JVM,而一些web應(yīng)用服務(wù)器加載是有限定的,比如tomcat加載的是每個應(yīng)用classPath+“/classes”加載class,classPath+“/lib”加載jar。以下就是代碼啦...

          package com.chenerzhu.learning.classloader;
          import java.io.ByteArrayOutputStream;
          import java.io.File;
          import java.io.IOException;
          import java.io.InputStream;
          import java.nio.file.Files;
          import java.nio.file.Paths;
          import java.util.Enumeration;
          import java.util.Map;
          import java.util.concurrent.ConcurrentHashMap;
          import java.util.jar.JarEntry;
          import java.util.jar.JarFile;
          /**
           * @author chenerzhu
           * @create 2018-10-04 12:24
           **/
          public class ClassPathClassLoader extends ClassLoader{
           private static Map<String, byte[]> classMap = new ConcurrentHashMap<>();
           private String classPath;
           public ClassPathClassLoader() {
           }
           public ClassPathClassLoader(String classPath) {
           if (classPath.endsWith(File.separator)) {
           this.classPath = classPath;
           } else {
           this.classPath = classPath + File.separator;
           }
           preReadClassFile();
           preReadJarFile();
           }
           public static boolean addClass(String className, byte[] byteCode) {
           if (!classMap.containsKey(className)) {
           classMap.put(className, byteCode);
           return true;
           }
           return false;
           }
           /**
           * 這里僅僅卸載了myclassLoader的classMap中的class,虛擬機(jī)中的
           * Class的卸載是不可控的
           * 自定義類的卸載需要MyClassLoader不存在引用等條件
           * @param className
           * @return
           */
           public static boolean unloadClass(String className) {
           if (classMap.containsKey(className)) {
           classMap.remove(className);
           return true;
           }
           return false;
           }
           /**
           * 遵守雙親委托規(guī)則
           */
           @Override
           protected Class<?> findClass(String name) {
           try {
           byte[] result = getClass(name);
           if (result == null) {
           throw new ClassNotFoundException();
           } else {
           return defineClass(name, result, 0, result.length);
           }
           } catch (Exception e) {
           e.printStackTrace();
           }
           return null;
           }
           private byte[] getClass(String className) {
           if (classMap.containsKey(className)) {
           return classMap.get(className);
           } else {
           return null;
           }
           }
           private void preReadClassFile() {
           File[] files = new File(classPath).listFiles();
           if (files != null) {
           for (File file : files) {
           scanClassFile(file);
           }
           }
           }
           private void scanClassFile(File file) {
           if (file.exists()) {
           if (file.isFile() && file.getName().endsWith(".class")) {
           try {
           byte[] byteCode = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
           String className = file.getAbsolutePath().replace(classPath, "")
           .replace(File.separator, ".")
           .replace(".class", "");
           addClass(className, byteCode);
           } catch (IOException e) {
           e.printStackTrace();
           }
           } else if (file.isDirectory()) {
           for (File f : file.listFiles()) {
           scanClassFile(f);
           }
           }
           }
           }
           private void preReadJarFile() {
           File[] files = new File(classPath).listFiles();
           if (files != null) {
           for (File file : files) {
           scanJarFile(file);
           }
           }
           }
           private void readJAR(JarFile jar) throws IOException {
           Enumeration<JarEntry> en = jar.entries();
           while (en.hasMoreElements()) {
           JarEntry je = en.nextElement();
           je.getName();
           String name = je.getName();
           if (name.endsWith(".class")) {
           //String className = name.replace(File.separator, ".").replace(".class", "");
           String className = name.replace("\\", ".")
           .replace("/", ".")
           .replace(".class", "");
           InputStream input = null;
           ByteArrayOutputStream baos = null;
           try {
           input = jar.getInputStream(je);
           baos = new ByteArrayOutputStream();
           int bufferSize = 1024;
           byte[] buffer = new byte[bufferSize];
           int bytesNumRead = 0;
           while ((bytesNumRead = input.read(buffer)) != -1) {
           baos.write(buffer, 0, bytesNumRead);
           }
           addClass(className, baos.toByteArray());
           } catch (Exception e) {
           e.printStackTrace();
           } finally {
           if (baos != null) {
           baos.close();
           }
           if (input != null) {
           input.close();
           }
           }
           }
           }
           }
           private void scanJarFile(File file) {
           if (file.exists()) {
           if (file.isFile() && file.getName().endsWith(".jar")) {
           try {
           readJAR(new JarFile(file));
           } catch (IOException e) {
           e.printStackTrace();
           在此我向大家推薦一個Java學(xué)習(xí)交流群。交流學(xué)習(xí)群號:874811168 
           可免費(fèi)領(lǐng)取java資料。
           }
           } else if (file.isDirectory()) {
           for (File f : file.listFiles()) {
           scanJarFile(f);
           }
           }
           }
           }
           public void addJar(String jarPath) throws IOException {
           File file = new File(jarPath);
           if (file.exists()) {
           JarFile jar = new JarFile(file);
           readJAR(jar);
           }
           }
          }
          

          如何使用的代碼就不貼了,和3.2節(jié)自定義類加載器的使用方式一樣。只是構(gòu)造方法的參數(shù)變成classPath了,篇末有代碼。當(dāng)創(chuàng)建MyClassLoader對象時,會自動添加指定classPath下面的所有class和jar里面的class到classMap中,classMap維護(hù)className和classCode字節(jié)碼的關(guān)系,只是個緩沖作用,避免每次都從文件中讀取。自定義類加載器每次loadClass都會首先在JVM中找是否已經(jīng)加載className的類,如果不存在就會到classMap中取,如果取不到就是加載錯誤了。

          六、最后

          至此,JVM自定義類加載器加載指定classPath下的所有class及jar已經(jīng)完成了。這篇博文花了兩天才寫完,在寫的過程中有意識地去了解了許多代碼的細(xì)節(jié),收獲也很多。本來最近僅僅是想實現(xiàn)Quartz控制臺頁面任務(wù)添加支持動態(tài)class,結(jié)果不知不覺跑到類加載器的坑了,在此也趁這個機(jī)會總結(jié)一遍。當(dāng)然以上內(nèi)容并不能保證正確,所以希望大家看到錯誤能夠指出,幫助我更正已有的認(rèn)知,共同進(jìn)步。

          出處:https://www.cnblogs.com/chenerzhu/p/9741883.html


          主站蜘蛛池模板: 狠狠综合久久av一区二区| 亚洲sm另类一区二区三区| 精品性影院一区二区三区内射 | 国产三级一区二区三区| 国产成人一区二区三区电影网站| 精品人妻一区二区三区四区在线 | 一区二区三区亚洲视频| 丰满人妻一区二区三区免费视频| 国产精品伦一区二区三级视频| 精品无码成人片一区二区98 | 日本午夜精品一区二区三区电影 | 亚洲高清日韩精品第一区| 精品亚洲av无码一区二区柚蜜| 日本精品夜色视频一区二区| 综合无码一区二区三区| 在线视频一区二区| 国产主播一区二区三区| 成人毛片一区二区| 日韩人妻精品一区二区三区视频| 亚洲爆乳精品无码一区二区| 视频一区视频二区在线观看| 综合久久一区二区三区| 一区二区三区在线观看| 亚洲福利视频一区二区| 农村人乱弄一区二区| 久久人妻无码一区二区| 亚洲一区二区三区在线播放| 91精品国产一区二区三区左线| 免费无码AV一区二区| 亚洲第一区香蕉_国产a| 国产大秀视频一区二区三区| 亚洲成AV人片一区二区密柚| 日本无卡码免费一区二区三区| av在线亚洲欧洲日产一区二区| 精品无码一区在线观看| 亚洲片国产一区一级在线观看| 亚洲日本久久一区二区va| 夜夜精品无码一区二区三区| 3D动漫精品一区二区三区| 久久se精品一区精品二区国产| 中文字幕日韩精品一区二区三区|