java的基礎理解
1.java的記憶體管理
程式計數器:
由於Java虛擬機器的多線
程是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內
核)只會執行一條執行緒中的指令。因此,為了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條
執行緒之間的計數器互不影響,獨立儲存,我們稱這類記憶體區域為“執行緒私有”的記憶體。 如果執行緒正在執行的是一個Java方法,這個計數器
記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行的是Natvie方法,這個計數器值則為空(Undefined)。此記憶體區域是唯一一個
在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。
Java虛擬機器棧
虛擬機器棧描述的是Java
方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於儲存區域性變量表、操作棧、動態連結、方法出口
等資訊。每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。
區域性變量表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference型別),它不等同於物件本身,根據不同的虛擬機器實現,它可能是一個指向物件起始地址的引用指標,也可能指向一個代表物件的控制代碼或者其他與此物件相關的位置)和returnAddress型別(指向了一條位元組碼指令的地址)。
其中64位長度的long和double型別的資料會佔用2個區域性變數空間(Slot),其餘的資料型別只佔用1個。區域性變量表所需的記憶體
空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改
變區域性變量表的大小。
會丟擲OutOfMemoryError異常;
Java堆
對於大多數應用來說,Java堆(Java Heap)是Java虛擬機器所管理的記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此內存區域的唯一目的就是存放物件例項;
所有的物件例項以及陣列都要在堆上分配,但是隨著JIT編譯器的發展與逃逸分析技術的逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化發生,所有的物件都分配在堆上也漸漸變得不是那麼絕對;
Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”(Garbage Collected Heap,幸好國內沒翻譯成“垃圾堆”);
如果從記憶體分配的角度看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩衝區(Thread Local Allocation Buffer,TLAB);
會丟擲OutOfMemoryError異常;
方法區
方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。
執行時常量池
執行時常量池(Runtime Constant Pool)是方法區的一部分。Class檔案中除了有類的版本、欄位、方法、介面等描述等資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池中。
物件訪問
介紹完Java虛擬機器的執行時資料區之後,我們就可以來探討一個問題:在Java語言中,物件訪問是如何進行的?物件訪問在Java語言中無處不在,是最普通的程
序行為,但即使是最簡單的訪問,也會卻涉及Java棧、Java堆、方法區這三個最重要記憶體區域之間的關聯關係,如下面的這句程式碼:
Object obj = new Object();
假設這句程式碼出現在方法體中,那“Object obj”這部分的語義將會反映到Java棧的本地變量表中,作為一個reference型別資料出現。而“new Object()”這部分的語
物件訪問
介紹完Java虛擬機器的執行時資料區之後,我們就可以來探討一個問題:在Java語言中,物件訪問是如何進行的?物件訪問在Java語言中無處不在,是最普通的程
序行為,但即使是最簡單的訪問,也會卻涉及Java棧、Java堆、方法區這三個最重要記憶體區域之間的關聯關係,如下面的這句程式碼:
Object obj = new Object();
假設這句程式碼出現在方法體中,那“Object obj”這部分的語義將會反映到Java棧的本地變量表中,作為一個reference型別資料出現。而“new Object()”這部分的語
義將會反映到Java堆中,形成一塊儲存了Object型別所有例項資料值(Instance Data,物件中各個例項欄位的資料)的結構化記憶體,根據具體型別以及虛擬機器實現
的物件記憶體佈局(Object Memory Layout)的不同,這塊記憶體的長度是不固定的。另外,在Java堆中還必須包含能查詢到此物件型別資料(如物件型別、父類、實
現的介面、方法等)的地址資訊,這些型別資料則儲存在方法區中。
義將會反映到Java堆中,形成一塊儲存了Object型別所有例項資料值(Instance Data,物件中各個例項欄位的資料)的結構化記憶體,根據具體型別以及虛擬機器實現的物件記憶體佈局(Object Memory Layout)的不同,這塊記憶體的長度是不固定的。另外,在Java堆中還必須包含能查詢到此物件型別資料(如物件型別、父類、實現的介面、方法等)的地址資訊,這些型別資料則儲存在方法區中。
這兩種物件的訪問方式各有優勢,使用控制代碼訪問方式的最大好處就是reference中儲存的是穩定的控制代碼地址,在物件被移動(垃圾收集時移動物件是非
常普遍的行為)時只會改變控制代碼中的例項資料指標,而reference本身不需要被修改。使用直接指標訪問方式的最大好處就是速度更快,它節省了一次指標
定位的時間開銷,由於物件的訪問在Java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本
自己的理解,可以想象,方法的變數是放在棧區,用完就釋放.
http://www.cnblogs.com/andy-zhou/p/5316084.html
http://www.cnblogs.com/w-wfy/p/6393992.html
day01
jre 是java 執行時環境包含程式所需的核心類庫,如果寫好的程式,不要開發只要執行,那麼有jre就夠了(包含虛擬機器)
jdk 開發工具包(包含了jre)還有編譯工具javac.exe,打包工具jar.exe,執行工具java.exe
2中方式配置環境變數:1.直接把bin(包含javac和java)目錄配置到path下;2.配置java_home環境變數,在吧java_home配置到path下
day04
有關過載:有引數個數不同,引數型別不同(順序不同也是)
day05
陣列名是區域性變數,建立在虛擬機器棧中,存的是在堆去中開闢記憶體空間的物件的首地址
陣列分動態和靜態初始化
越界和空指標異常(空指標就是一個引用變數的地址是空地址,就會出現;只有引用資料型別才出現,基本資料型別沒有)
java程式執行的時候,每一個方法出現都會進入虛擬機器棧,以棧幀的形式索引,區域性變數就在棧幀的地址範圍內有效;但是方法走完就彈棧區域性變數消失(main方法一樣會彈棧,之後沒有棧中的引用指向堆區的物件,該物件就成為了垃圾等待回收);
day06
方法區(程式碼倉庫)裡存著類的資訊,.java檔案編譯後生成.class檔案,類資訊從硬碟進入記憶體的方法區,要用的時候引用進棧.(呼叫main方法是jvm做的),所謂類載入就是載入進入方法區;
所以順序是jvm呼叫一個類的方法時:類載入到方法區--->類的方法進棧--->遇到變數引用--->到堆中去new出來把地址賦給棧中的成員變數
物件的建立:
都有預設的初試化值:引用型別的預設值是null;int是0;
成員變數和區域性變數的區別
* A:在類中的位置不同
* 成員變數:在類中方法外
* 區域性變數:在方法定義中或者方法宣告上
* B:在記憶體中的位置不同
* 成員變數:在堆記憶體(成員變數屬於物件,物件進堆記憶體)
* 區域性變數:在棧記憶體(區域性變數屬於方法,方法進棧記憶體)
* C:生命週期不同
* 成員變數:隨著物件的建立而存在,隨著物件的消失而消失
* 區域性變數:隨著方法的呼叫而存在,隨著方法的呼叫完畢而消失
* D:初始化值不同
* 成員變數:有預設初始化值---------------------------------------------->>>>>>>>>>>重要 null or 0
* 區域性變數:沒有預設初始化值,必須定義,賦值,然後才能使用。
當有成員和區域性同名,就近原則
引用資料型別包括:陣列,物件,介面,列舉;
注意------->>>>>跟主函式在一起的都加靜態,不跟他在一起的不用加
封裝的例子:插排
this代表當前物件的引用(不用的時候同名會因為就近原則導致賦值不成功);---->>>用來區分成員變數和區域性變數重名;
day07
強引用:
軟引用:用root演算法,無論是否物件可達,只要記憶體不夠就會回收
弱引用:物件可達也沒有,GC下次一定回收
虛引用:無法通過虛引用獲得一個物件的例項,設定虛引用的目的就是能在這個物件被收集器回收時收到一個系統通知。
靜態之後的變數直接在類載入(以.class物件的形式進入)的同時進入方法區(在類空間的內容部屬於類載入位置的靜態區)
A:static關鍵字的特點
* a:隨著類的載入而載入,不能用this(代表物件地址)
* b:優先於物件存在
* c:被類的所有物件共享
* 舉例:咱們班級的學生應該共用同一個班級編號。
* 其實這個特點也是在告訴我們什麼時候使用靜態?
* 如果某個成員變數是被所有物件共享的,那麼它就應該定義為靜態的。
* 舉例:
* 飲水機(用靜態修飾)
* 水杯(不能用靜態修飾)
* 共性用靜態,特性用非靜態
* d:可以通過類名呼叫
* 其實它本身也可以通過物件名呼叫。
* 推薦使用類名呼叫。
* 靜態修飾的內容一般我們稱其為:與類相關的,類成員
------>>>>在類載入的同時,載入了其他的方法(但是非靜態的方法是資訊理解為壓縮檔案不能直接用,而靜態的方法是可以直接用的)
* 靜態變數儲存於方法區的靜態區
* 成員變數儲存於堆記憶體
------>>>>注意區別成員變數和區域性變數的關係.
java的入口函式的引數args是接收鍵盤錄入的;
day08---->>>>
程式碼塊:
區域性程式碼塊:類中方法內
構造程式碼塊(初始化塊):類中方法外(沒建立一次物件就會執行一次)--------------->>>>>>>>>他的作用是沒必要把共同的方法放在每一個構造方法中;優先構造方法執行;
靜態程式碼塊(類初始化用):只執行一次(就是構造程式碼塊加了static),隨類載入,而載入,注意和靜態方法不一樣,靜態方法需要呼叫的走,靜態程式碼塊只要類載入就執行;
繼承:不要為了部分功能去繼承(應該在向上抽一層);構造方法不能繼承;
this可以呼叫子類的也可以呼叫父類的(其實是子類的,不過是繼承下來了);super只能呼叫父類;
子類的所有構造方法都會先訪問父類的空參構造,就是預設加了super(),這麼做就是為了繼承,否則父類的東西過不來;
多型的前提的是繼承
day08
區別重寫和過載
final修飾方法,就是不希望被子類重寫
final修飾類,比如string類,不能被繼承
final修飾變數,變數就變成了常量,但是是物件裡面的常量,加個static就變成了全域性的常量,還可以類名訪問方便呼叫;(常量命名要大寫)
----->>>>特別注意final修飾的變數一定要在物件構造完畢前初始化;可以顯示初始化,也可以構造方法裡初始化,只能一次.不會使用預設值;
day09
polymorphic多型:
基於多型的編譯看左邊,執行看右邊;可以以抽象類作為左邊邊,右邊直接實現即可;
前提:1.繼承;2.方法重寫;3.父類引用指向子類物件;-------->>>>>這樣就產生了一個引用出現多種形態.
------------------->>>>>>>>>>>>
特別注意:在堆裡建立的物件分為父類區和子類區,如果是子父類變數同名並不會被覆蓋,父類引用調父類的變數,子類引用調子類的變數;但是如果是同名的方法,方法就被覆蓋了,調的就是子類的;因為方法編譯看的是父類(按道理),但是執行的時候動態繫結;所以在當方法是靜態的時候,就不會動態繫結,還是用父類的方法,其實靜態就相當於類名呼叫;
---------------->>>>>>>>>>>形象的理解多型:類的屬性就是特徵,方法就是能力.父類指向子類就是強能力(子類)喬裝為統一的弱能力,特徵卻不會變(比如名字)
---------------->>>>>>>>>>>編譯就是看父類,執行看子類,但是如果父類沒有這個方法,那麼呵呵,就會報錯,需要轉型;(就是不能使用子類特有的行為)
---------------->>>>>>>>>>>當做引數的時候多型就提高了程式的擴充套件性;(在醫療專案中Fragment就可以這樣轉統一處理,不用特有方法就直接傳,用特有方法用instanceof判斷後呼叫)
---------------->>>>>>>>>>>不能訪問子類特有的屬性和行為.
abstract類和方法:
抽象類特點:不能例項化(要繼承重寫,可以採用多型);子類一定要重寫所有抽象方法(除非子類也是抽象的);也有空參構造;不可以修飾變數;方法既可以抽象,也可以非抽象;
---------------->>>>>>>>>>>前面說了多型不能使用子類特有的方法,這個時候可以在父類把特有的方法定義為抽象(不知道子類怎麼執行),就由多型引出抽象;
介面:
--------------->>>>>>>>>>>特點:必須全是抽象方法;變數只能是常量(隱藏了關鍵字public.fianl和static);介面沒有構造方法;方法(隱藏了public,abstract,光寫返回值就可以);所以介面都是public(介面本來就是暴露原則)
--------------->>>>>>>>>>>介面也能使用多型指向實現者;介面和介面之間是繼承繼承關係可以多繼承;
--------------->>>>>>>>>>>注意"抽象類和普通類一樣,只是能定義抽象方法;
day10
--------------->>>>>>>>>>>
\ 本類 同一個包下(子類和無關類) 不同包下(子類) 不同包下(無關類)
private Y
預設 Y Y
protected Y Y Y
public Y Y Y Y
--------------->>>>>>>>>>>注意類只能用預設修飾符修飾:
--------------->>>>>>>>>>>當一個類中的所有方法都是靜態的時候,就要把構造方法私有,不讓別的類建立物件;
--------------->>>>>>>>>>>內部類,本身就是內部類的一個成員(有成員方法,成員變數) new 外部類物件.內部類物件
--------------->>>>>>>>>>>內部類調外部類的變數outer.this.變數
--------------->>>>>>>>>>>有關區域性內部類(瞭解用):在類方法中的類
--------------->>>>>>>>>>>區域性內部類訪問他所在方法的變數要加final,原因就是方法彈棧變數消失,但是堆裡的物件還是存在;
--------------->>>>>>>>>>>注意:匿名內部類是區域性內部類的一種,必須寫在方法裡;
他的寫法是new 介面(){實現介面的方法};整個代表實現了這個介面的物件;通常在new thread()裡面要傳一個實現runnable的物件,就可以傳匿名內部類
當然當用介面指向是現在,當他有特有方法時沒有辦法呼叫(無法轉型)
day11
object類的方法
hashcode();得到物件的雜湊值
tostring():類名@雜湊值(16進位制)
equals方法:地址值對比但是string類重寫了這個方法
--------------->>>>>>>>>>>基本資料型別都有用==比較(比較引用型別只是比較地址值),引用型別可以用equals方法(自己重寫)
day12
string類被final修飾不可以被繼承
""sdfsdf""這是一個物件.而這個物件是不能改變的;就比如說;
String str=new String("123123"); 這裡不能改變的是"123123"這個物件,而不是str這只是個引用他可以在指向別的物件
day13
基本資料型別的包裝類,用物件的方法對基本資料型別進行操作,會比較方便;(其實內部封裝了方法,有物件裡面就封裝了方法)
day16
泛型:出現的原因是為了解決層次多了之後的類轉換異常(以前是統一用object統一轉型處理,容易出錯);這就把執行時候的錯誤放到編譯期發現;
--------------->>>>>>>>>>>泛型可以在類上,也可以在方法上(最好就是用類的泛型);但是注意:靜態方法一定要定義自己的泛型,因為類的泛型要建立物件才確定,而靜態方法類載入好他就確定了;
介面也可以新增泛型,實現的時候指定具體的型別就可以;
萬用字元:
List<?> list=new ArrayList<>();就是當右邊返回的泛型不能確定的時候,左邊可以用萬用字元表示;
? extends E 向E下面限定,固定上邊界; ------>>>>>>相當於父類指向子類
day18
? super E
就是父類的比較器子類也可以用,固定了下邊界;------>>>>>>同樣相當於父類指向子類
--------------->>>>>>>>>>>需要注意,誰呼叫誰是泛型的E(放進去要求我有的你要有,拿出來要求你有的我全有)
day19
直接在程式碼中寫檔名比如:"xxx.txt"他的路徑就表示相對於當前包下的路徑;
為什麼要封裝父級路徑為file和子路徑,而不是直接用路徑的字串呢,因為用file比字串的功能強大;
函式file.createfile建立檔案,file.mkdir建立資料夾,file.mkdirs建立多級資料夾;
--------------->>>>>>>>>>>
file的獲取功能:獲取絕對路徑:getAbslutePath,獲取構造方法裡傳入的路徑:getPath
list:拿到多有名稱的陣列;listFile[]拿到該資料夾下的檔案陣列
day20
有關流為什麼用int接收的原因是避免出現11111111 這個位元組
FileOutPutStream 沒有就建立,有就清空,後面會有一個boolean值表示是否追加
fis.read()表示讀到的一個位元組用int表示,fis.read(arr)表示讀到了幾個位元組到arr中,沒有的話就是-1
buffer不過是中間加了一個小陣列來讀取,包裝方法,只要一個位元組一個位元組的讀,之所以可以迴圈,因為把硬碟的讀取,轉義到了記憶體
close()可以重新整理緩衝區之後再關閉流(最後重新整理),flush()是光重新整理(可以實時重新整理)
--------------->>>>>>>>>>>
day21
reader和writer就是可以直接讀寫字元,內部都有緩衝區,記得要關流
newline是跨平臺的,writer("\r\n")只適用Windows平臺
設計模式1:裝飾
拿到被裝飾的類的引用-->在構造方法中傳入-->在對於的方法中擴充被裝飾的類;
破除耦合選擇任意的物件包裝,隨意增加功能;被裝飾類,跟裝飾的類無關;
interface Coder {
public void code();
}
class Student implements Coder {
@Override
public void code() {
System.out.println("javase");
System.out.println("javaweb");
}
}
class HeiMaStudent implements Coder {
private Student s; //獲取到被包裝的類的引用
public ItcastStudent(Student s) { //通過建構函式建立物件的時候,傳入被包裝的物件
this.s = s;
}
@Override
public void code() { //對其原有功能進行升級
s.code();
System.out.println("資料庫");
System.out.println("ssh");
System.out.println("安卓");
System.out.println(".....");
}
}
--------------->>>>>>>>>>
day22
序列流 本質就是把流放在序列流中排隊,在流中遍歷;
vector 有一個列舉的函式,在序列流中可以直接傳入列舉的物件;
bytearrayoutputstream不用關
所謂序列化就是把物件的內容順序儲存到記憶體當中;就是存的過程,對應的讀取就叫反序列化;
ObjecOutputStream 需要碼錶;
serializable 需要id的原因:存的時候有一個,取得時候按照這個來取;
拿到當前的執行緒物件和物件的引用名字是不同的;在那個執行緒呼叫currentThread方法,就是那那個執行緒的名字;
設定守護執行緒為了在主任務(其他非守護執行緒)結束的時候退出任務;
加入執行緒就是插隊用的,要丟擲一箇中斷異常;可以設定插隊時間;
禮讓執行緒,就是用yield讓出cpu
--------------->>>>>>>>>>
注意:(非靜態的)方法中的所物件是this,所以要跟同步程式碼塊同步的話,就要用this
而在(靜態方法)中不能用this(優先於物件存在)這裡可以用該類的位元組碼物件(.class);
--------------->>>>>>>>>>
有關執行緒安全的問題:類中的變數加上static就是公用一個變數,但是四個執行緒的判斷條件會衝突;
注意:有關執行緒加鎖一定要注意鎖一定要加成一樣的,如果是this的話表示的是執行緒本身,所以採用.class物件才能保證唯一(如果用引用型別的成員變數當做鎖物件,必須是靜態的);
針對火車票的例子,可以把公用的資源(票數)放在runnable中這樣傳入到thread中就不用考慮同步的問題了
--------------->>>>>>>>>>
設計模式2:懶漢式(處理多執行緒的問題)和餓漢式
用static final 直接定死單例也行
懶漢式,有雙重校驗(鎖前鎖後都要校驗 --第一次是效率不用每次排隊,第二次防止多次建立),判空後再加鎖,不要在判空前加鎖(就是不要在getInstance的方法上加鎖);
day24
runtime採用的是單例的模式,可以執行dos命令;
timer計時器,timertask
等待和喚醒synchronized(this)把this物件作為監視器,這個物件上所有的監視
即在同步程式碼塊中,用哪個物件鎖,就用哪個物件呼叫喚醒的方法;
執行緒組就是同意管理執行緒,比如可以同意設定守護執行緒
--------------->>>>>>>>>>
設計模式3:簡單工廠:就是把物件的建立放到工廠類裡去執行.----改的時候要改工廠類很不利於維護
工廠方法:就是採用多型的思想,創造一個動物工廠的介面,然後其他各個對應的工廠實現這個介面並返回對應的類,(事先要建立對應的類)----增加的時候方便;
--------------->>>>>>>>>>
day26
網路程式設計:tcp有客戶端和服務端(有對應的ip地址和埠號),而udp只要有發有接收就行了.
--------------->>>>>>>>>>
string型別的值的確是不能修改的。
你先String s = "123";
那麼在String常量池中先生成一個String物件"123",s是該物件的引用。
然後s = "456";
同樣在String常量池中建立一個String物件"456",s重新指向了"456"。
"123"仍然是"123",並沒有被修改。
只是s的指向發生了變化。
有關string是final為什麼可以改變值;
--------------->>>>>>>>>>
有關多型的問題:“成員變數,靜態方法看左邊;非靜態方法:編譯看左邊,執行看右邊。”
有關throwable 包含error和exception 其中error是不能修改的(比如oom),而其中runtimeException是不用主動try/catch
--------------->>>>>>>>>>
string的加號連線對記憶體消耗過大,故使用stringbuffer(會在常量池中不斷的建立副本)