Java語言相關基礎知識

目錄

JVM(Java虛擬機)

Java內存分配回收

分佈式

Java-HashMap原理

Java日期時間格式

Java設計模式

數據庫

Java基礎


JVM(Java虛擬機)

Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機是實現這一特點的關鍵。Java編譯器只要面向JVM,生成JVM能理解的代碼或字節碼文件。Java源文件經編譯成字節碼程序,通過JVM將每一條指令翻譯成不同平臺機器碼,通過特定平臺運行。

JVM執行程序的過程:加載 .class 文件,管理並分配內存,執行垃圾收集。

JRE(Java運行時環境)就是由JVM構造的Java程序的運行環境。

JVM主要包括四個部分:

1.類加載器(ClassLoader):在JVM啓動時或者在類運行時將需要的class加載到JVM中;

2.執行引擎:負責執行.class文件中包含的字節碼指令;

3.內存區(也叫運行時數據區):是在JVM運行的時候操作所分配的內存區。運行時內存區主要可以劃分爲5個區域:①方法區(Method Area):用於存儲類結構信息的地方,包括常量池、靜態變量、構造函數等。雖然JVM規範把方法區描述爲堆的一個邏輯部分, 但它卻有個別名non-heap(非堆)。方法區還包含一個運行時常量池。②Java堆(Heap):存儲Java實例或者對象的地方。這塊是GC(垃圾回收)的主要區域。從存儲的內容我們可以很容易知道,方法區和堆是被所有Java線程共享的。③Java棧(Stack):Java棧總是和線程關聯在一起,每當創建一個線程時,JVM就會爲這個線程創建一個對應的Java棧。在這個Java棧中又會包含多個棧幀,每運行一個方法就創建一個棧幀,用於存儲局部變量表、操作棧、方法返回值等。每一個方法從調用直至執行完成的過程,就對應一個棧幀在Java棧中入棧到出棧的過程。所以Java棧是現成私有的。④程序計數器(PC Register):用於保存當前線程執行的內存地址。由於JVM程序是多線程執行的(線程輪流切換),所以爲了保證線程切換回來後,還能恢復到原先狀態,就需要一個獨立的計數器,記錄之前中斷的地方,可見程序計數器也是線程私有的。⑤本地方法棧(Native Method Stack):和java棧的作用差不多,只不過是爲JVM使用到的native方法服務的。

4.本地方法接口:主要是調用C或C++實現的本地方法及返回結果。

Java內存分配回收

Java的內存分配原理與C/C++不同,C/C++每次申請內存時都要malloc進行系統調用,而系統調用發生在內核空間,每次都要中斷進行切換,這需要一定的開銷,而Java虛擬機是先一次性分配一塊較大的空間,然後每次new時都在該空間上進行分配和釋放,減少了系統調用的次數,節省了一定的開銷,這有點類似於內存池的概念;二是有了這塊空間過後,如何進行分配和回收就跟GC機制有關了。Java一般內存申請有兩種:靜態內存和動態內存。很容易理解,編譯時就能夠確定的內存就是靜態內存,即內存是固定的,系統一次性分配,比如int類型變量;動態內存分配就是在程序執行時才知道要分配的存儲空間大小,比如Java對象的內存空間。根據上面我們知道,Java棧、程序計數器、本地方法棧都是線程私有的,線程生就生,線程滅就滅,棧中的棧幀隨着方法的結束也會撤銷,內存自然就跟着回收了。所以這幾個區域的內存分配與回收是確定的,我們不需要管的。但是Java堆和方法區則不一樣,我們只有在程序運行期間才知道會創建哪些對象,所以這部分內存的分配和回收都是動態的。一般我們所說的垃圾回收也是針對的這一部分。

Stack的內存管理是順序分配的,而且定長,不存在內存回收問題;而Heap 則是爲Java對象的實例隨機分配內存,不定長度,所以存在內存分配和回收的問題。垃圾收集器一般必須完成兩件事:檢測出垃圾;回收垃圾。

檢測垃圾的方法有:

1.引用計數法:給一個對象添加引用計數器,每當有個地方引用它,計數器就加1;引用失效就減1。好了,問題來了,如果我有兩個對象A和B,互相引用,除此之外,沒有其他任何對象引用它們,實際上這兩個對象已經無法訪問,即是我們說的垃圾對象。但是互相引用,計數不爲0,導致無法回收,所以還有另一種方法。

2.可達性分析算法:以根集對象爲起始點進行搜索,如果有對象不可達的話,即是垃圾對象。這裏的根集一般包括Java棧中引用的對象、方法區常量池中引用的對象、本地方法中引用的對象等。

JVM在做垃圾回收的時候,會檢查堆中的所有對象是否會被這些根集對象引用,不能夠被引用的對象就會被垃圾收集器回收。一般回收算法也有如下幾種:

1.標記-清除(Mark-sweep):算法和名字一樣,分爲兩個階段:標記和清除。標記所有需要回收的對象,然後統一回收。這是最基礎的算法,後續的收集算法都是基於這個算法擴展的。不足:效率低;標記清除之後會產生大量碎片

2.複製(Copying):此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。此算法每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內存空間。

3.標記-整理(Mark-Compact):此算法結合了“標記-清除”和“複製”兩個算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“複製”算法的空間問題。

4.分代收集算法:這是當前商業虛擬機常用的垃圾收集算法。分代的垃圾回收策略,是基於這樣一個事實:不同的對象的生命週期是不一樣的。因此,不同生命週期的對象可以採取不同的收集方式,以便提高回收效率。在Java程序運行的過程中,會產生大量的對象,因每個對象所能承擔的職責不同所具有的功能不同所以也有着不一樣的生命週期,有的對象生命週期較長,比如Http請求中的Session對象,線程,Socket連接等;有的對象生命週期較短,比如String對象,由於其不變類的特性,有的在使用一次後即可回收。試想,在不進行對象存活時間區分的情況下,每次垃圾回收都是對整個堆空間進行回收,那麼消耗的時間相對會很長,而且對於存活時間較長的對象進行的掃描工作等都是徒勞。因此就需要引入分治的思想,所謂分治的思想就是因地制宜,將對象進行代的劃分,把不同生命週期的對象放在不同的代上使用不同的垃圾回收方式。①年輕代:是所有新對象產生的地方。年輕代被分爲3個部分——Enden區和兩個Survivor區(From和to)當Eden區被對象填滿時,就會執行Minor GC。並把所有存活下來的對象轉移到其中一個survivor區(假設爲from區)。Minor GC同樣會檢查存活下來的對象,並把它們轉移到另一個survivor區(假設爲to區)。這樣在一段時間內,總會有一個空的survivor區。經過多次GC週期後,仍然存活下來的對象會被轉移到年老代內存空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的。需要注意,Survivor的兩個區是對稱的,沒先後關係,from和to是相對的。②年老代:在年輕代中經歷了N次回收後仍然沒有被清除的對象,就會被放到年老代中,可以說他們都是久經沙場而不亡的一代,都是生命週期較長的對象。對於年老代和永久代,就不能再採用像年輕代中那樣搬移騰挪的回收算法,因爲那些對於這些回收戰場上的老兵來說是小兒科。通常會在老年代內存被佔滿時將會觸發Full GC,回收整個堆內存。③持久代:用於存放靜態文件,比如Java類、方法等。持久代對垃圾回收沒有顯著的影響。

分佈式

分佈式就是把一個系統/業務拆分成多個子系統/子業務去協同處理,這個過程就叫分佈式。

Java-HashMap原理

哈希表有多種不同的實現方法,最常用的一種方法是拉鍊法,可以理解爲“鏈表的數組”。

哈希表是由數組+鏈表組成的,一個長度爲16的數組中,每個元素存儲的是一個鏈表的頭結點。那麼這些元素是按照什麼樣的規則存儲到數組中呢。一般情況是通過hash(key)%len獲得,也就是元素的key的哈希值對數組長度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存儲在數組下標爲12的位置。

HashMap其實也是一個線性的數組實現的,所以可以理解爲其存儲數據的容器就是一個線性數組。

首先HashMap裏面實現一個靜態內部類Entry,其重要的屬性有 key , value, next,從屬性key,value我們就能很明顯的看出來Entry就是HashMap鍵值對實現的一個基礎bean,我們上面說到HashMap的基礎就是一個線性數組,這個數組就是Entry[],Map裏面的內容都保存在Entry[]裏面。

1.ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。

2.對於隨機訪問get和set,ArrayList覺得優於LinkedList,因爲LinkedList要移動指針。

3.對於新增和刪除操作add和remove,LinedList比較佔優勢,因爲ArrayList要移動數據。

Java日期時間格式

CST :China Standard Time, UTC+8:00 中國標準時間(北京時間),在東八區。

GMT :Greenwich Mean Time,格林威治標準時間,指位於英國倫敦郊區的皇家格林尼治天文臺的標準時間,因爲本初子午線被定義在通過那裏的經線。

UTC :Universal Time Coordinated,世界協調時間,又稱世界標準時間、世界統一時間。UTC 提供了一種與時區無關(或非特定於時區)的時間。

世界上的所有時區都可以表示爲 UTC 加上或減去一個偏移量。因此,UTC是0時區的時間,如北京爲早上八點(東八區),UTC時間就爲零點,時間比北京時晚八小時。

數據庫的時間字段starts存的是datetime類型,它是一個和時區相關的string(顯然:string都是和時區相關的),而且數據庫是按照CST時區存的時間。

Java.util.Date代表一個時間點,其值爲距公元1970年1月1日 00:00:00的毫秒數,所以它是沒有時區和Locale概念的。public long getTime() 返回自 1970 年 1 月 1 日 00:00:00 GMT 以來此 Date 對象表示的毫秒數。

Java中通過如下形式取得當前時間點:Date now =new Date(); //這個時間點與本地系統的時區無關,而正因爲其與時區的無關性,才使得我們的存儲數據(時間)是一致的(時區一致性)。一般的我們將now存儲於數據庫中,當我們需要展現數據時,將now格式化成想要的格式,如:2011-12-04 21:22:24。而這個功能一般交由Java.text.DateFormat來實現。例如:默認情況下,SimpleDateFormat 取得本地系統的時區(我的時區爲GMT+8北京),然後按照 pattern("yyyy-MM-dd HH:mm:ss")格式化now,此時輸出的就是 GMT+8 區的時間了。如果想支持國際化時間,則先指定時區,然後再格式化date數據。

我們在開發和設計系統的時候注意三點:1.自己寫公用類的時候,要對多線程調用情況下的後果在註釋裏進行明確說明。2.對線程環境下,對每一個共享的可變變量都要注意其線程安全性。3.我們的類和方法在做設計的時候,要儘量設計成無狀態的。

關於SimpleDateFormat安全的時間格式化線程安全問題的解決辦法:1.需要的時候創建新實例:在需要用到SimpleDateFormat 的地方新建一個實例,不管什麼時候,將有線程安全問題的對象由共享變爲局部私有都能避免多線程問題,不過也加重了創建對象的負擔。在一般情況下,這樣其實對性能影響比不是很明顯的。2.使用同步:同步SimpleDateFormat對象。當線程較多時,當一個線程調用該方法時,其他想要調用此方法的線程就要block,多線程併發量大的時候會對性能有一定的影響。3.使用ThreadLocal:使用ThreadLocal, 也是將共享變量變爲獨享,線程獨享肯定能比方法獨享在併發環境中能減少不少創建對象的開銷。如果對性能要求比較高的情況下,一般推薦使用這種方法。4.拋棄JDK,使用其他類庫中的時間格式化類:①使用Apache commons 裏的FastDateFormat,宣稱是既快又線程安全的SimpleDateFormat, 可惜它只能對日期進行format, 不能對日期串進行解析。②使用Joda-Time類庫來處理時間相關問題。

Java設計模式

設計模式來源衆多專家的經驗和智慧,它們是從許多優秀的軟件系統中總結出的成功的、能夠實現可維護性複用的設計方案,使用這些方案將可以讓我們避免做一些重複性的工作;設計模式提供了一套通用的設計詞彙和一種通用的形式來方便開發人員之間溝通和交流,使得設計方案更加通俗易懂;大部分設計模式都兼顧了系統的可重用性和可擴展性,這使得我們可以更好地重用一些已有的設計方案、功能模塊甚至一個完整的軟件系統,避免我們經常做一些重複的設計、編寫一些重複的代碼,許多設計模式將有助於提高系統的靈活性和可擴展性,讓我們在不修改或者少修改現有系統的基礎上增加、刪除或者替換功能模塊;合理使用設計模式並對設計模式的使用情況進行文檔化,將有助於別人更快地理解系統;學習設計模式將有助於初學者更加深入地理解面向對象思想,讓你知道:如何將代碼分散在幾個不同的類中?爲什麼要有“接口”?何謂針對抽象編程?何時不應該使用繼承?如果不修改源代碼增加新功能?同時還讓你能夠更好地閱讀和理解現有類庫(如JDK)與其他系統中的源代碼。

在學習每一個設計模式時至少應該掌握如下幾點:這個設計模式的意圖是什麼,它要解決一個什麼問題,什麼時候可以使用它;它是如何解決的,掌握它的結構圖,記住它的關鍵代碼;能夠想到至少兩個它的應用實例,一個生活中的,一個軟件中的;這個模式的優缺點是什麼,在使用時要注意什麼。當你能夠回答上述所有問題時,恭喜你,你瞭解一個設計模式了,至於掌握它,那就在開發中去使用吧,用多了你自然就掌握了。

模式從不保證任何東西,它不能保證你一定能夠做出可複用的軟件,提高你的生產率,更不能保證世界和平。模式並不能替代人來完成軟件系統的創造,它們只不過會給那些缺乏經驗但卻具備才能和創造力的人帶來希望。

觀察者模式,有時被稱作發佈/訂閱模式,觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。將一個系統分割成一個一些類相互協作的類有一個不好的副作用,那就是需要維護相關對象間的一致性。我們不希望爲了維持一致性而使各類緊密耦合,這樣會給維護、擴展和重用都帶來不便。觀察者就是解決這類的耦合關係的。

菜鳥教程有詳細的設計模式學習教程。

數據庫

一條sql執行過長的時間,要如何優化:

1、查看sql是否涉及多表的聯表或者子查詢,如果有,看是否能進行業務拆分,相關字段冗餘或者合併成臨時表(業務和算法的優化)。

2、涉及鏈表的查詢,是否能進行分表查詢,單表查詢之後的結果進行字段整合。

3、如果以上兩種都不能操作,非要鏈表查詢,那麼考慮對相對應的查詢條件做索引,加快查詢速度。

4、針對數量大的表進行歷史表分離(如交易流水錶)。

5、數據庫主從分離,讀寫分離,降低讀寫針對同一表同時的壓力,至於主從同步,mysql有自帶的binlog實現主從同步。

6、explain分析sql語句,查看執行計劃,分析索引是否用上,分析掃描行數等等。

7、查看mysql執行日誌,看看是否有其他方面的問題。

8、從根本上來說,查詢慢是佔用mysql內存比較多,那麼可以從這方面去考慮。

Java基礎

==比較是否是同一個對象(純指針操作);equals比較對象的內容。

重寫equals爲何要重寫hashCode?回答:判斷兩個對象是否相等,比較的就是其hashCode, 如果你重載了equals,比如說是基於對象的內容實現的,而保留hashCode的實現不變,那麼很可能某兩個對象明明是“相等”,而hashCode卻不一樣。  hashcode不一樣,就無法認定兩個對象相等了。

表示不同文件類型的魔術數字,指定是文件的最開頭的幾個用於唯一區別其它文件類型的字節,有了這些魔術數字,我們就可以很方便的區別不同的文件,這也使得編程變得更加容易,因爲減少了用於區別一個文件的文件類型所要花費的時間。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章