Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
ava是一門功能強大的多用途編程語言,也是全球最流行的開發語言之一。它是面向對象編程語言的代表,集跨平臺、健壯性、高性能等諸多優點,廣泛應用于Web后端開發、移動端開發、大數據分析、人工智能等熱門領域,在互聯網行業占據十分重要的地位。目前,全球有超過500萬的專業開發者在使用Java語言,Java程序運行在全球數十億臺設備上。作為全書開篇,本章將對Java概述、開發環境搭建、程序開發步驟、虛擬機與垃圾回收、開發工具等內容進行講解,帶領帶領大家進入Java世界。
Java是一種高級計算機語言,它是由Sun公司(2009年4月20日被Oracle公司收購,2010年完成合并)于1995年5月推出的一種用來編寫跨平臺應用軟件、完全面向對象的程序設計語言。Java語言簡單易用、安全可靠,自從問世以后,受到了市場的大力追捧。在PC、移動設備、家用電器等領域,Java技術無處不在。
Sun公司在1995年推出Java語言以后,吸引了編程世界的廣泛關注。那么,Java到底有什么魔力呢?1990年末,Sun公司預測嵌入式系統將會在家電領域大顯身手,于是在1991年6月啟動了“Green計劃”,由詹姆斯·高斯林(James Gosling)、邁克·謝里丹(Mike Sheridan)等人帶領的開發團隊負責,準備開發一種能夠在各種消費性電子產品(如機頂盒、冰箱、收音機等)上運行的程序架構,以便于人們與家用電器進行信息交流與控制。因為家用電器的計算處理能力和內存都非常有限,所以要求語言必須非常小且能夠生成非常緊湊的代碼,這樣才能在這樣的環境中執行。另外,不同的家用電器使用的CPU不同,因此要求該語言必須是跨平臺的。開發團隊最初考慮使用C++語言,但是C++太過復雜,以致很多開發者經常錯誤使用,而且項目面向的是嵌入式平臺,可用的系統資源十分有限,所以Sun公司創始人之一的比爾·喬伊(Bill Joy)決定開發一種新語言,他提議在C++的基礎上開發一種面向對象的環境。Java便由此而問世,詹姆斯?高斯林最初將其命名為Oak(橡樹)。遺憾的是,當時由于這門語言只能為家用電器提供一個通用環境,且受到諸多因素的限制,Oak語言沒有得到迅速推廣。1994年夏天,隨著Innernet的迅猛發展,瀏覽器的出現,枯燥乏味的信息文檔已經不滿足人們的需求,這給Oak語言帶來了新的生機。詹姆斯·高斯林立刻意識到這是一個機會,于是對Oak進行了小規模的改造。之后,開發團隊的其他成員完成了第一個基于Oak語言的網頁瀏覽器WebRunner,從而讓瀏覽器具有了在網頁中執行內嵌代碼的能力,可以創造含有動態內容的網頁。1995年,Sun公司將Oak更名為Java,并將其直接發布在互聯網上,免費開源給大家使用,獲得了廣大開發人員的青睞。之后,Java開始走紅,成為一門廣為人知的編程語言,被用于開發Web應用程序。
Java一開始具有吸引力,是因為Java程序可以在Web瀏覽器中運行,隨著Internet普及和迅猛發展,以及Web技術的不斷更新,Java語言與時俱進、推陳出新,使Java語言在現在社會經濟發展和科學研究中,占據了越來越重要的地位。在最流行的語言流行指數Tiobe,RedMonk和PyPL中均長期排名前三,且多年是Tiobe排行榜中排名第一的語言。從手機軟件到企業級應用、從無人駕駛汽車到線上支付、從Minecraft(我的世界)游戲娛樂到火星探測器太空探索,Java語言的使用場景非常廣泛?,F在,Java廣泛應用于開發服務器端的應用程序,截止到2021年,Java開發占據了服務器端后臺開發80%以上的市場份額。
Java語言目前主要應用于如下領域:
從Java編程語言本身角度來講,其嚴謹的結構,易懂的語法加上簡易的編寫為其之后的發展及革新提供了良好的保障。
注意:Java是印度尼西亞爪哇島的英文名稱,因盛產咖啡而聞名。
Java語言是一門跨平臺的適用于移動端、服務器領域、分布式環境的面向對象程序設計語言,它之所以能從眾多編程語言中脫穎而出,成為最流行的服務端開發語言之一,是因為具備如下顯著特點:
想一想:你了解哪些語言?Java語言在眾多編程中脫穎而出的原因有哪些?
Java開發工具包(JavaSE Development Kits,簡稱JDK)是一套獨立程序構成的集合,用于開發和測試Java程序,是Java程序開發的首要工具。
JDK由Java API、Java工具和Java基礎的類庫等組成,其核心是Java API,API(Application Programming Interface,應用程序編程接口)是Java提供的供編程人員使用的標準類庫,開發人員可以用這些類庫中的類來實現Java程序的各種功能,從而免去自行設計很多常用類的繁重工作,極大地提高開發效率。另外,Java API還包括一些重要的語言結構以及基本圖形、網絡和文件I/O等。
本書中使用的是JDK15版本,與之前的版本相比,JDK 15 為用戶提供了14項主要的增強(JEP),同時新增了1個孵化器模塊、3個預覽功能、2個不推薦使用的功能,并刪除了2個淘汰的功能。
知識點撥:增強(JEP)、孵化器模塊(Incubator)和預覽特性(Preview)的具體含義:
增強:英文全稱為JDK Enhancement Proposals ,簡稱 JEP,是JDK 增強建議,主要包括新增特性和改進提案。
孵化器:實際上就是實驗版,主要從Java社區收集意見、反饋,穩定性差,后期可能有比較大的變動,稱之為尚未定稿的API/工具。
預覽特性:規格已經成型,實現基本確定,但是最終未定稿,這些特性,還可能被移除,但可能性比較小,一般都會定下來。
編寫Java程序,首先要下載JDK安裝程序,讀者可以直接從Oracle公司的官方網站下載。
通過瀏覽器打開Oracle官網(http://www.oracle.com/technetwork/java/javase/ downloads/index.html),根據提示進入下載頁面,找到與自己的計算機操作系統對應的JDK安裝文件下載鏈接,點擊下載即可。網頁內容可能因版本或Oracle公司規劃而有所不同,用戶可以根據需要選擇所需要的JDK版本。
JDK安裝文件下載成功后,就可以安裝了。本書使用的是64位的Windows10環境,接下來詳細演示Windows 64位平臺下JDK15的安裝過程,具體步驟如下:
(1)雙擊從Oracle官網下載的JDK安裝文件,進入JDK安裝界面,如圖1.1所示。
(2)單擊圖1.1中的“下一步”按鈕,進入到JDK自定義安裝界面,如圖1.2所示。
圖1.1 JDK安裝界面 圖1.2 JDK默認安裝路徑
(3)建議選擇直接安裝到默認目錄,單擊“下一步”按鈕即可進行安裝,如圖 1.3所示。也可以點擊“更改”按鈕,自行選擇安裝目錄。
(4)安裝完畢之后,彈出如圖1.4所示的界面,點擊“關閉”按鈕即可。
圖1.3 等待安裝界面 圖1.4 安裝完畢界面
在使用Java來編譯和運行程序之前,必須先設置好環境變量。所謂環境變量,就是在操作系統中定義的變量,可供操作系統上的所有應用程序使用。Path環境變量的作用是設置一個路徑,由操作系統去尋找該路徑下的文件(如.bat、.ext、.com等),對Java來說就是Java的安裝路徑。
下面以Windows 10操作系統為例說明。具體步驟如下:
(1)選擇“控制面板”→“系統和安全”→“系統”(也可以在桌面上右鍵單擊“此電腦”或“我的電腦”,選擇“屬性”選項),進入到系統窗口,如圖1.5所示。
單擊“高級系統設置”
圖1.5 Windows 10系統窗口界面
(2)單擊“高級系統設置”選項,彈出“系統屬性”窗口,如圖1.6所示。
(3)單擊“環境變量(N)…”按鈕,彈出 “環境變量”窗口,如圖1.7所示。
圖1.6 “系統屬性”窗口界面 圖1.7 “環境變量”窗口界面
(4)在“環境變量”窗口的“系統變量(S)”區域中,單擊“新建(W)…”按鈕,打開“新建系統變量”窗口。并在“變量名(N)”文本框中輸入“JAVA_HOME”,在“變量值(V)”文本框中輸入JDK安裝目錄。筆者此時的安裝目錄為“C:\Program Files\Java\jdk-15”,如圖1.8所示。單擊“確定”按鈕,完成JAVA_HOME環境變量的配置。
圖1.8 “新建系統變量”窗口界面
(5)在“環境變量”窗口的“系統變量(S)”區域中,選中系統變量“Path”,如圖1.9所示。
圖1.9 “環境變量”窗口需按照Path變量界面
(6)在圖1.9所示的界面單擊“編輯(I)…”按鈕,打開“編輯環境變量”窗口,點擊“新建(N)”按鈕,在編輯頁面的文本框中添加“%JAVA_HOME%\bin”,如圖1.10所示。然后,單擊窗口的“確定”按鈕,保存環境變量,完成配置。
圖1.10 “編輯系統環境變量”窗口界面
注意:在配置Path環境變量時,JAVA_HOME環境變量并不是一定需要配置的,我們也可以直接將JDK的安裝路徑(C:\Program Files\Java\JDK-15\bin;)添加到Path環境變量中。這里配置JAVA_HOME的好處是,當JDK的版本或安裝路徑發生變化時,只需要修改JAVA_HOME的值,而不用修改Path環境變量的值。
個別圖書中會提到Classpath環境變量,Classpath環境變量的作用與Path環境變量的作用類似,它是JVM執行Java程序時搜索類的路徑的順序,以最先找到為準,JDK1.5之后,如果沒有設置Classpath環境變量,則Java解釋器會在當前路徑下搜索Java類,故本書不再贅述。
JDK配置完成后,需要測試JDK是否能夠在計算機上運行,具體步驟如下:
(1)同時按下鍵盤 Window鍵和R鍵,調出Dos命令行運行窗口,在搜索框輸入cmd,如圖 1.11所示。
(2)單擊“確定”按鈕,進入命令行窗口,如圖1.12所示。
圖1.11 運行窗口 圖1.12 命令行窗口
(3)在命令行窗口中輸入“javac”命令,并按Enter鍵,系統會輸出javac的幫助信息,如圖1.13所示,說明JDK已經成功配置,否則需要仔細檢查JDK環境變量的配置是否正確。
圖1.13 命令行信息
JDK安裝成功后,系統會自動在我們的安裝目錄下生成一個目錄,稱為JDK目錄,如圖 1.14所示,我們必須熟悉JDK目錄各個文件夾的作用才能更好的學習與編寫代碼。
圖1.14 JDK目錄
接下來,簡單介紹一下JDK目錄及其子目錄的含義和作用:
注意:自JDK9 以后,就取消了目錄中的jre目錄,將之前jre目錄里面的內容分散到其他各個目錄了。
在我們為電腦配好Java開發環境以后,也就代表著我們可以開始實現我們的Java開發之旅了,現在我們自己來動手編寫一個Java程序,親自感受一下Java語言的編寫規范。
下面將編寫第一個Java程序,其功能是控制臺輸出“有夢想,一起實現!”,如例1-1所示。
例1-1 HelloDream.java
在開始編寫代碼之前,先在電腦D盤(本書使用D盤)中創建一個新的目錄及子目錄:“d:\javaCode\demo01”,然后在demo01下創建一個文本文件,并重命名為“HelloDream.java”,使用記事本打開,編寫如下程序代碼:
例1-1是程序的源代碼,下面針對逐條語句進行詳細的講解,如圖1.15所示。
class是Java關鍵字,用來聲明該文件是一個類。類作為程序的基本單元存在,所有Java代碼必須寫在類里面
我們自己定義的類名,方便識別,類名必須與文件名一致
輸出語句,把我們要展示的內容在控制臺輸出來
這句聲明了一個main方法,作為Java程序的執行入口,需要執行的代碼都要寫在main方法的大括號內
圖1.15 記事本編寫的java代碼
注意:在編寫Java代碼時,所有的符號必須用英文半角格式,不允許出現中文字符。
編寫好的Java代碼文件需要編譯成Java字節碼文件(class文件)才能運行,Java程序的編譯步驟如下:
接下來打開Dos命令行窗口,并按下面步驟來編譯和運行HelloDream.java。
(1)打開Dos命令行窗口,先將路徑切換到需要編譯代碼的位置,即在窗口依次輸入“d:\javaCode\demo01”和“d:”命令,如圖1.16所示。
(2)切換好磁盤路徑之后,在命令行窗口輸入“javac HelloDream.java”命令,對源文件進行編譯,如圖1.17所示。
圖1.16 切換磁盤目錄 圖1.17 編譯Java源文件
(3)在編譯成功后會發現同級目錄下多了一個名為“HelloDream.class”的文件,這個文件就是字節碼文件,如圖 1.18所示。
編譯生成的字節碼文件
圖1.18 命令行編譯后的文件目錄
編程技巧:在進行編譯的時候,要寫文件全名加后綴名。javac編譯utf-8編碼的java文件時,容易出現“錯誤: 編碼GBK的不可映射字符”。解決方法是添加encoding 參數:javac -encoding utf-8 WordCount.java,如果還不能解決,將文件保存成ANSI編碼格式。
編譯完成之后,就可以運行程序。在Dos命令行窗口接著輸入“java HelloDream”命令,運行剛才已經編譯好的java文件,運行結果如圖 1.19所示。
注意:在運行的時候,輸入的是文件的全名,不加后綴名。
通過前面的學習,我們知道了一個Java程序需要經過編寫、編譯、運行3階段,而且細心的同學會發現,在編譯的時候我們用的是javac命令,而在運行的時候我們用的是java命令,這在一定程度上給大家帶來了不少麻煩。好在,JDK9之后,Java程序的編譯運行進行了改動,變得更加簡便,不需要再使用javac命令對java文件進行編譯后運行,而是直接使用java命令對java文件進行編譯運行。
接下來,我們將“d:\javaCode\demo01”目錄下編譯后的“HelloDrea.class”字節碼文件刪除掉,按照簡化后方法重新編譯運行HelloDream.java程序,如圖1.20所示。
圖1.19 運行Java程序 圖1.20 Java命令編譯運行
通過圖1.20可以看到,我們只需要使用java命令就可以直接打印出java文件的輸出結果。
java文件是高級語言代碼,class文件是低級語言代碼。編譯過程實際上是通過Java編譯器將高級語言的源代碼編譯為低級語言代碼。那么反過來,是否可以通過低級語言代碼進行反向工程,獲取其源代碼呢?答案是肯定得,這個過程就叫做反編譯。雖然,機器語言很難反編譯為源代碼,但是中間代碼是可以進行反編譯的,比如用戶可以把javac編譯得到的class文件進行反編譯,將其轉換為java文件。通過反編譯,我們可以了解別人的代碼內容,學習別人的代碼的實現思路,還可以通過源代碼查找bug、制作外掛等。
Java中有很多反編譯工具,最常用的有如下幾種:
前面我們學習了Java程序的編寫、編譯與運行過程,那么Java程序在計算機中運行的底層原理是什么呢?它是如何實現跨平臺的呢?它在運行過程中又是如何使用計算機內存的呢?接下來,我們來學習Java虛擬機與垃圾回收機制。
Java虛擬機(Java Virtual Machine ,JVM)是運行Java程序必不可少的機制。Oracle的Java虛擬機規范給出了JVM的定義:JVM是在一臺真實的機器上用軟件方式實現的一臺假象機。虛擬機的代碼存儲在.class文件中,并且每個.class文件最多包含一個public class類的代碼。
Java程序經過編譯器(javac.exe)編譯之后,會產生與平臺無關的字節碼文件(即擴展名.class的文件)。字節碼文件本質上是一種標準化的可移植的二進制格式,它最大的好處是可跨平臺運行,也就是常說的“一次編譯,到處運行”。字節碼文件必須交由解釋器來執行,與計算機硬件、操作系統沒有關系,這個解釋程序就是JVM。換句話說,無論使用哪種操作平臺,只要其含有JVM,就可以運行字節碼文件。事實上,正是有了Java虛擬機規范,才使得Java應用程序達到與平臺無關,從而實現可移植性,這也是Java語言風靡全球、迅速普及的原因之一。
回顧之前之前學習的代碼編譯、運行過程,我們可以很容易地理解,JVM實現跨平臺代碼執行的過程如圖1.21所示。
圖1.21 JVM執行流程圖
最后需要強調的是,JVM的實現包括字節碼驗證、解釋器、內存垃圾回收等,JVM虛擬機規范對運行時數據區域的劃分及字節碼的優化并不做嚴格的限制,它們的實現依不同的平臺而有所不同。
在傳統的程序開發語言(C、C++及其他語言)中允許動態分配內存,同時需要程序開發人員負責內存資源的釋放,如果不釋放內存,則隨著程序的不斷運行,不斷有新的資源需要分配內存,當系統中沒有內存可用時程序就會崩潰;或者,已動態分配的堆內存由于某種原因未被程序釋放或無法釋放,也會造成系統內存的浪費。上述這些現象都被稱為“內存漏洞”。
垃圾回收(Garbage Collection,GC)就是指釋放垃圾對象所占用的空間,防止內存溢出。內存處理是讓所有編程人員都很頭疼的地方,如果忘記或者錯誤地回收內存會導致程序或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測對象并判斷是否超過作用域,從而確定是不是要回收對象。
在Java語言中,引入了垃圾回收機制,程序開發者在編寫程序的時候無需考慮內存管理問題。Java提供了后臺系統級線程,自動記錄每次內存分配的情況,并統計每個內存地址的引用次數,不定時地對內存中沒有被引用或者長時間沒有使用的對象進行回收,這樣回收的內存資源可以再次分配其他的內存申請。
垃圾回收能自動釋放內存空間,使開發者可以將更多精力投入到軟件核心功能設計之上,不需要主動去考慮內存漏洞的問題,極大地減輕了程序開發者編程的負擔。同時,垃圾回收是Java語言安全性策略的一個重要部份,它能夠有效保護程序的完整性。當然,Java的垃圾回收也有一個潛在的缺點,就是它的開銷影響程序性能,Java虛擬機必須追蹤運行程序中有用的對象,最終釋放沒用的對象,這個過程需要花費CPU的時間。
在第1.3節編寫第一個Java程序時,我們使用的是記事本,這樣編寫程序比較辛苦且效率不高。那么,如何來提高編程效率呢?這就需要選擇一款優秀的Java程序開發工具。
在Java的學習和開發過程中,離不開一款功能強大、使用簡單、高效率的開發工具。程序開發工具又叫集成開發環境(IDE),是用于提供程序開發環境的應用程序,通常包括代碼編輯器、編譯器、調試器和圖形用戶界面等,這類軟件一般集代碼編寫、分析、編譯、調試為一體,可以極大程度地提高編程效率。目前,最流行的Java集成開發環境有Eclipse、InteliJ IDEA、NetBeans、jGRASP、BlueJ等。曾經,Eclipse是Java IDE中的王者,近些年其風頭逐步被InteliJ IDEA所取代。
IntelliJ IDEA簡稱IDEA,是業界公認最好的Java開發工具之一,特別在智能代碼助手、代碼自動提示、重構、JavaEE支持、各類版本工具(git、svn等)、JUnit、CVS整合、代碼分析、 創新的GUI設計等方面,其功能可以說是超常的。
接下來,我們就來介紹IDEA的下載、安裝與啟動方法(筆者寫稿時使用了IDEA的2021版,讀者可以直接在官網進行下載),具體步驟如下:
(1)通過網址( https://www.jetbrains.com/idea/)進入官網,如圖1.22所示。
圖1.22 IDEA官網下載界面
(2)點擊“Download”進行下載,彈出下載界面,如圖 1.23所示。IntelliJ IDEA 提供了兩個版本,即 Ultimate(旗艦版)和 Community(社區版)。社區版是免費的,但它的功能較少。旗艦版是商業版,提供了一組出色的工具和特性。
圖1.23 idea2021版下載界面
(3)點擊“Download”后會彈出如圖 1.24所示的注冊界面,讓我們進行注冊,不用注冊,這時候已經開始下載了。下載好安裝包后將其放在合適的位置,等待安裝即可。
已開始下載
圖1.24 等待下載與注冊界面
(4)雙擊下載好的安裝包,彈出安裝界面,如圖 1.25所示。
(5)點擊“Next>”按鈕選擇安裝目錄,一般選擇默認即可,如果C盤空間不足可以選用其他盤符,如圖1.26 所示。
圖1.25 安裝界面 圖1.26 程序安裝目錄界面
(6)點擊“Next >”按鈕后,進入安裝配置界面,勾選創建桌面快捷方式,本書使用的是64位操作系統,所以勾選 “64bit Launder”(用戶請根據自己操作系統位數,自行選擇),如圖 1.27所示。
(7)點擊“Next >”按鈕,跳轉至開始安裝界面,如圖1.28所示。點擊“Install”按鈕即跳轉至等待安裝界面,如圖1.29所示。程序安裝完畢界面如圖1.30所示,點擊“Finish”按鈕即可。
圖1.27 安裝配置界面 圖1.28 開始安裝界面
圖1.29 安裝等待界面 圖1.30 安裝完畢界面
安裝好IDEA之后,接下來就帶領大家體驗使用IDEA進行程序開發的過程,步驟如下:
(1)在桌面上找到IDEA的快捷方式,雙擊圖標打開IDEA,進入“Welcome to IntelliJ IDEA”界面(IntelliJ IDEA旗艦版是商業收費軟件,非付費用戶首次登陸進入不到收費界面,但是該軟件為學生提供了人性化的福利,學生憑個人學號可以獲得免費使用權,具體根據官方提示操作即可),如圖1.31所示。IDEA界面的默認顏色為黑色,默認狀態下進入下一步完全可以,如果不喜歡該風格,可自行設置。在圖1.31所示界面選擇左側“Customize”,切換到如圖1.32所示的界面,再在右側“Color theme” 下拉選項卡選擇“IntelliJ Light”,背景色即可變為亮色,如圖 1.33所示。在圖1.33所示界面選擇左側“Projects”,即可再次回到歡迎界面,如圖1.34所示。
圖1.31 IDEA歡迎界面 圖1.32 IDEA界面顏色設置
圖1.33 界面顏色變為亮色 圖1.34 調整顏色后的歡迎界面
(2)在歡迎界面點擊“New Project”按鈕后進入“New Project”界面,如圖1.35所示。選擇項目類型和版本號,當前選擇Java項目,Project SDK是“15 version 15”。接下來單擊“Next”按鈕,進入如圖1.36所示的界面,本界面用來設置是否使用模板開發,這里不用勾選。
圖1.35 項目類型和版本 圖1.36 是否使用模板
(3)點擊“Next”按鈕進入下一步,按照如圖 1.37所示,輸入項目名稱、選擇項目目錄。
點擊選擇項目存放目錄
項目存放的目錄
項目名稱
圖1.37 設置項目名稱和目錄
(4)單擊“Finish”按鈕,就可以看見我們用IDEA創建的第一個項目,如圖 1.38所示。
圖1.38 創建好的項目
(5)在src目錄下右鍵選擇“New”→“Java Class”,如圖1.39所示。點擊“Java Class”以后在彈窗內填入類名,如圖1.40所示。
圖1.39 創建類
創建接口
默認創建類
自定義類名,首字母大寫
圖1.40 創建類過程中的彈窗
(6)在類中編寫圖1.41所示的代碼,然后右鍵并點擊“Run 'HelloWorld.main()'”運行代碼(或在菜單選擇“Run”→“Run 'HelloWorld'”,或者直接單擊工具欄的
圖標)。
圖1.41 編寫并運行Java代碼
程序運行結果如下:
HelloWorld!
好好學習Java!
程序運行完成后,在IDEA打印運行結果,通過在IDEA編輯器中編寫代碼,代碼編寫效率、執行效率會更高。
注意:.idea目錄用來存放項目的配置信息,包括歷史記錄,版本控制信息等內容。
1.1 Java的三大體系分別是______、______、______。
1.2 編譯Java程序需要使用______命令。
1.3 Java代碼編譯后的字節碼文件擴展名為________。
2.1 Java程序未經過編譯時的文件的后綴是( )
A..dll B..exe
C..class D..java
2.2 用Java虛擬機編譯類名為Hello的應用程序的正確命令是( )
A.javac Hello.class B.Hello.class
C.java Hello.java D.java Hello
2.3 下列選項中,屬于Java語言特點的一項是( )
A.使用繁瑣 B.編譯執行
C.節省內存空間 D.面向對象
2.4 Java屬于哪一類語言( )
A.面向機器的語言 B.面向對象的語言
C.面向過程的語言 D.面向操作系統的語言
3.1 簡述Java語言的特點。
3.2 簡述 JDK的含義和作用。
3.3 簡述什么是JVM。
4.1 編寫程序,在控制臺展示出“歡迎來AAA教育學習!”和“有夢想,一起實現!”兩句話。
認打包生成的jar是不能夠直接運行的,因為帶有main方法的類信息不會添加到manifest中(打開jar文件中的META-INF/MANIFEST.MF文件,將無法看到Main-Class一行)。
用maven打包java程序,當執行 java -jar 文件時提示 no main manifest attribute。
為了生成可執行的jar文件,需要借助插件。
目錄:
# 進入你想創建項目的父文件夾
cd /Volumes/RamDisk
# 查看當前文件夾
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
查看下生成的項目結構:
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
官網地址在:
http://maven.apache.org/plugins/maven-shade-plugin/examples/executable-jar.html
按說明,在pom.xml中添加以下內容:
<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里的,否則不會有效果()。
現在執行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
說明已經被替換成帶有Main-Class信息的可運行jar。
現在,在項目根目錄中執行該jar文件:
root@9275e11b3f0f:/usr/src/mymaven/hello-world# java -jar /usr/src/mymaven/hello-world/target/hello-world-1.0-SNAPSHOT.jar
Hello World!
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.virhuiai.www.App</mainClass> <!-- //主程序入口類,可以按住control,單機定位到該類-->
</manifest>
</archive>
</configuration>
</plugin>
注意這也是直接位于build>plugins下的,不是pluginManagement里的,pluginManagement指定版本號。
、JVM中的類加載器類型
從Java虛擬機的角度講,只有兩種不同的類加載器:啟動類加載器和其他類加載器。
1.啟動類加載器(Boostrap ClassLoader):這個是由c++實現的,主要負責JAVA_HOME/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。
2.其他類加載器:由java實現,可以在方法區找到其Class對象。這里又細分為幾個加載器
a).擴展類加載器(Extension ClassLoader):負責用于加載JAVA_HOME/lib/ext目錄中的,或者被-Djava.ext.dirs系統變量指定所指定的路徑中所有類庫(jar),開發者可以直接使用擴展類加載器。java.ext.dirs系統變量所指定的路徑的可以通過System.getProperty("java.ext.dirs")來查看。
b).應用程序類加載器(Application ClassLoader):負責java -classpath或-Djava.class.path所指的目錄下的類與jar包裝入工作。開發者可以直接使用這個類加載器。在沒有指定自定義類加載器的情況下,這就是程序的默認加載器。
c).自定義類加載器(User ClassLoader):在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件, 體現java動態實時類裝入特性。
這四個類加載器的層級關系,如下圖所示。
二、為什么要自定義類加載器
三、自定義類加載器
3.1 ClassLoader實現自定義類加載器相關方法說明
要實現自定義類加載器需要先繼承ClassLoader,ClassLoader類是一個抽象類,負責加載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()方法來加載類。自定義類加載器實現這個方法來加載需要的類,比如指定路徑下的文件,字節流等。
definedClass:definedClass在findClass中使用,通過調用傳進去一個Class文件的字節數組,就可以方法區生成一個Class對象,也就是findClass實現了類加載的功能了。
貼上一段ClassLoader中loadClass源碼,見見真面目...
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 */
翻譯過來大概是:使用指定的二進制名稱來加載類,這個方法的默認實現按照以下順序查找類: 調用findLoadedClass(String)方法檢查這個類是否被加載過 使用父加載器調用loadClass(String)方法,如果父加載器為Null,類加載器裝載虛擬機內置的加載器調用findClass(String)方法裝載類, 如果,按照以上的步驟成功的找到對應的類,并且該方法接收的resolve參數的值為true,那么就調用resolveClass(Class)方法來處理類。 ClassLoader的子類最好覆蓋findClass(String)而不是這個方法。 除非被重寫,這個方法默認在整個裝載過程中都是同步的(線程安全的)。
resolveClass:Class載入必須鏈接(link),鏈接指的是把單一的Class加入到有繼承關系的類樹中。這個方法給Classloader用來鏈接一個類,如果這個類已經被鏈接過了,那么這個方法只做一個簡單的返回。否則,這個類將被按照 Java?規范中的Execution描述進行鏈接。
3.2 自定義類加載器實現
按照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; } }
以上就是自定義的類加載器了,實現的功能是加載指定路徑的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()); } }
首先通過構造方法創建MyClassLoader對象myClassLoader,指定加載src/test/resources/bean/Hello.class路徑的Hello.class(當然這里只是個例子,直接指定一個class的路徑了)。然后通過myClassLoader方法loadClass加載Hello的Class對象,最后實例化對象。以下是輸出結果,看得出來實例化成功了,并且類加載器使用的是MyClassLoader。
在此我向大家推薦一個Java學習交流群。交流學習群號:874811168 里面會分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發、高性能、分布式、微服務架構的原理,JVM性能優化、分布式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,一起學習,一起進步,目前受益良多。
com.chenerzhu.learning.classloader.bean.Hello@2b2948e2 com.chenerzhu.learning.classloader.MyClassLoader@335eadca
四、類Class卸載
JVM中class和Meta信息存放在PermGen space區域(JDK1.8之后存放在MateSpace中)。如果加載的class文件很多,那么可能導致元數據空間溢出。引起java.lang.OutOfMemory異常。對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。所以需要在JVM中卸載(unload)類Class。
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):
很容易理解,就是要被卸載的類的ClassLoader實例已經被GC并且本身不存在任何相關的引用就可以被卸載了,也就是JVM清除了類在方法區內的二進制數據。
JVM自帶的類加載器所加載的類,在虛擬機的生命周期中,會始終引用這些類加載器,而這些類加載器則會始終引用它們所加載的類的Class對象。因此這些Class對象始終是可觸及的,不會被卸載。而用戶自定義的類加載器加載的類是可以被卸載的。雖然滿足以上三個條件Class可以被卸載,但是GC的時機我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。
五、JVM自定義類加載器加載指定classPath下的所有class及jar
經過以上幾個點的說明,現在可以實現JVM自定義類加載器加載指定classPath下的所有class及jar了。這里沒有限制class和jar的位置,只要是classPath路徑下的都會被加載進JVM,而一些web應用服務器加載是有限定的,比如tomcat加載的是每個應用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,虛擬機中的 * Class的卸載是不可控的 * 自定義類的卸載需要MyClassLoader不存在引用等條件 * @param className * @return */ public static boolean unloadClass(String className) { if (classMap.containsKey(className)) { classMap.remove(className); return true; } return false; } /** * 遵守雙親委托規則 */ @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學習交流群。交流學習群號:874811168 可免費領取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節自定義類加載器的使用方式一樣。只是構造方法的參數變成classPath了,篇末有代碼。當創建MyClassLoader對象時,會自動添加指定classPath下面的所有class和jar里面的class到classMap中,classMap維護className和classCode字節碼的關系,只是個緩沖作用,避免每次都從文件中讀取。自定義類加載器每次loadClass都會首先在JVM中找是否已經加載className的類,如果不存在就會到classMap中取,如果取不到就是加載錯誤了。
六、最后
至此,JVM自定義類加載器加載指定classPath下的所有class及jar已經完成了。這篇博文花了兩天才寫完,在寫的過程中有意識地去了解了許多代碼的細節,收獲也很多。本來最近僅僅是想實現Quartz控制臺頁面任務添加支持動態class,結果不知不覺跑到類加載器的坑了,在此也趁這個機會總結一遍。當然以上內容并不能保證正確,所以希望大家看到錯誤能夠指出,幫助我更正已有的認知,共同進步。
出處:https://www.cnblogs.com/chenerzhu/p/9741883.html
*請認真填寫需求信息,我們會在24小時內與您取得聯系。