轉自: https://blog.csdn.net/Scythe666/article/details/52053100
Java基礎總結
一、JVM
1、記憶體模型
1.1.1 記憶體分幾部分
(1)程式計數器
可看作目前線程所執行的位元組碼的**行号訓示器**。位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢複等基礎功能都需要依賴這個計數器來完成。
線上程建立時建立。執行本地方法時,PC的值為null。為了線程切換後能恢複到正确的執行位置,每條線程都需要有一個獨立的程式計數器,線程私有。
(2)Java虛拟機棧
線程私有,生命周期同線程。**每個方法在執行同時,建立棧幀**。用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊。棧中的局部變量表主要存放一些基本類型的變量(int, short, long, byte, float,double, boolean, char)和對象句柄。
棧中有局部變量表,包含參數和局部變量。
此外,java中沒有寄存器,是以所有的參數傳遞依靠操作數棧。
棧上配置設定,小對象(一般幾十個bytes),在沒有逃逸的情況下,可以直接配置設定在棧上。(沒有逃逸是指,對象隻能給目前線程使用,如果多個線程都要用,則不可以,因為棧是線程私有的。)直接配置設定在棧上,可以自動回收,減輕GC壓力。因為棧本身比較小,大對象也不可以配置設定,會影響性能。
-XX:+DoEscapeAnalysis 啟用逃逸分析,若非逃逸則可棧上配置設定。
(3)本地方法棧
線程私有,與Java虛拟機棧非常相似,差別不過是虛拟機棧為虛拟機執行Java 方法(也就是位元組碼)服務,而本地方法棧則是為虛拟機使用到的 Native 方法(非java語言實作,比如C)服務。Hotspot 直接把本地方法棧和虛拟機棧合二為一。
棧&本地方法棧:**線程建立時産生,方法執行是生成棧幀。**
(4)Java堆
線程共有(可能劃分出多個線程私有的配置設定緩沖區,Thread Local Allow),Java虛拟機管理記憶體中最大的一塊,此區域唯一目的就是存放對象執行個體,幾乎所有對象執行個體在此區配置設定,線程共享記憶體。可細分為新生代和老年代,友善GC。主流虛拟機都是按可擴充實作(通過-Xmx 和 -Xms 控制)。
注意:Java堆是Java代碼可及的記憶體,是留給開發人員使用的;**非堆(Non-Heap)**就是JVM留給 自己用的,是以方法區、JVM内部處理或優化所需的記憶體(如JIT編譯後的代碼緩存)、每個類結構(如運作時常數池、字段和方法資料)以及方法和構造方法的代碼都在非堆記憶體中。
關于TLAB
Sun Hotspot JVM為了提升對象記憶體配置設定的效率,對于所建立的線程都會配置設定一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運作的情況計算而得,在TLAB上配置設定對象時不需要加鎖,是以JVM在給線程的對象配置設定記憶體時會盡量的在TLAB上配置設定,在這種情況下JVM中配置設定對象記憶體的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間配置設定
TLAB僅作用于新生代的Eden Space,是以在編寫Java程式時,通常多個小的對象比大的對象配置設定起來更加高效。詳見:http://www.cnblogs.com/sunada2005/p/3577799.html
Java堆:**在虛拟機啟動時建立**
(5)方法區
線程共有,用于存儲已被虛拟機加載的類資訊、常量池、靜态變量、即時編譯器編譯後的代碼等資料。雖然Java虛拟機規範把方法區描述為堆的一個邏輯部分,但它卻有一個别名Non-Heap(非堆),目的是與Java堆區分開。
注意,通常和永久區(Perm)關聯在一起。但也不一定,JDK6時,String等常量資訊儲存于方法區,JDK7時,移動到了堆。永久代和方法區不是一個概念,但是有的虛拟機用永久代來實作方法區,可以用永久代GC來管理方法區,省去專門寫的功夫。
(6)運作時常量池
方法區的一部分,存放編譯期生成的各種字面量和符号引用。
(7)直接記憶體
并不是虛拟機運作時資料區的一部分,也不是Java虛拟機規範中定義的記憶體區域,也可能導緻 OOM 異常(記憶體區域綜合>實體記憶體時)。NIO類,可以使用Native 函數庫直接配置設定堆外記憶體,然後通過一個存儲在Java 堆裡面的 DirectByteBuffer 對象作為這塊記憶體的引用進行操作。
類加載時 方法資訊儲存在一塊稱為方法區的記憶體中, 并不随你建立對象而随對象儲存于堆中。可參考《深入java虛拟機》前幾章。
另參考(他人文章):
如果instance method也随着instance增加而增加的話,那記憶體消耗也太大了,為了做到共用一小段記憶體,Java 是根據this關鍵字做到的,比如:instance1.instanceMethod(); instance2.instanceMethod(); 在傳遞給對象參數的時候,Java 編譯器自動先加上了一個this參數,它表示傳遞的是這個對象引用,雖然他們兩個對象共用一個方法,但是他們的方法中所産生的資料是私有的,這是因為參數被傳進來變成call stack内的entry,而各個對象都有不同call stack,是以不會混淆。其實調用每個非static方法時,Java 編譯器都會自動的先加上目前調用此方法對象的參數,有時候在一個方法調用另一個方法,這時可以不用在前面加上this的,因為要傳遞的對象參數就是目前執行這個方法的對象。
詳見:http://blog.csdn.net/scythe666/article/details/51700142
1.1.2 堆溢出、棧溢出原因及執行個體,線上如何排查
(1)棧溢出
遞歸,容易引起棧溢出stackoverflow;因為方法循環調用,方法調用會不斷建立棧幀。
造成棧溢出的幾種情況:
1)遞歸過深
2)數組、List、map資料過大
3 ) 建立過多線程
對于Java虛拟機棧和本地方法棧,Java虛拟機規範規定了**兩種異常**狀況:
① 線程請求深度>虛拟機所允許的深度,将抛出StackOverFlowError(SOF)異常;
② 如果虛拟機可動态擴充,且擴充時無法申請到足夠的記憶體,就會抛出OutOfMemoryError(OOM)異常。
(2)堆溢出
如果在堆中沒有記憶體完成執行個體配置設定,且堆無法擴充時,将抛出OOM異常。
在方法區也會抛出 OOM 異常。
執行個體
可使用以下代碼造成堆棧溢出:
- package overflow;
- import java.util.ArrayList;
- /**
- * Created by hupo.wh on 2016/7/7.
- */
- public class MyTest {
- public void testHeap(){
- for(;;){
- ArrayList list = new ArrayList ( );
- }
- }
- int num= ;
- public void testStack(){
- num++;
- this.testStack();
- }
- public static void main(String[] args){
- MyTest t = new MyTest();
- t.testHeap();
- //t.testStack();
- }
- }
如下代碼會造成OOM堆溢出:
- package OOM;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * Created by hupo.wh on 2016/7/15.
- */
- public class App1 {
- static class OOMClass {
- long[] num = new long[ ];
- }
- public static void main(String[] args) {
- List<OOMClass> list = new ArrayList<>();
- while ( true) {
- list.add( new OOMClass());
- }
- }
- }
另外,Java虛拟機的堆大小如何設定:指令行
java –Xms128m //JVM占用最小記憶體
–Xmx512m //JVM占用最大記憶體
–XX:PermSize=64m //最小堆大小
–XX:MaxPermSize=128m //最大堆大小
2、類加載機制
基本上所有的類加載器都是 java.lang.ClassLoader類的一個執行個體。下面詳細介紹這個 Java 類。
1.2.1 java.lang.ClassLoader類介紹
java.lang.ClassLoader類的基本職責就是根據一個**指定的類的名稱**,找到或者生成其對應的位元組代碼,然後從這些位元組代碼中定義出一個 Java 類,即 java.lang.Class類的一個執行個體。除此之外,ClassLoader還負責加載 Java 應用所需的資源,如圖像檔案和配置檔案等。
1.2.2 類加載器的樹狀組織結構
Java 中的類加載器大緻可以分成兩類:一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。**系統提供**的類加載器主要有下面三個:
(1)引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實作的,并不繼承自 java.lang.ClassLoader。
BootStrapClassLoader
負責jdk_home/jre/lib目錄下的核心 api或 -Xbootclasspath選項指定的jar包加載進來。
(2)擴充類加載器(extensions class loader):它用來加載 Java 的擴充庫。Java 虛拟機的實作會提供一個擴充庫目錄。該類加載器在此目錄裡面查找并加載 Java 類。
ExtClassLoader
負責jdk_home/jre/lib/ext目錄下的jar包或 -Djava.ext.dirs指定目錄下的jar包加載進來。
(3)系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來擷取它。
AppClassLoader
負責java -classpath/-Djava.class.path所指的目錄下的類與jar包加載進來,System.getClassLoader擷取到的就是這個類加載器。
除了系統提供的類加載器以外,開發人員可以通過繼承 java.lang.ClassLoader類的方式實作自己的類加載器,以滿足一些特殊的需求。
除了引導類加載器之外,所有的類加載器都有一個父類加載器。getParent()方法可以得到。對于系統提供的類加載器來說,系統類加載器的父類加載器是擴充類加載器,而擴充類加載器的父類加載器是引導類加載器;對于開發人員編寫的類加載器來說,其父類加載器是加載此類加載器 Java 類的類加載器。因為類加載器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的。一般來說,開發人員編寫的類加載器的父類加載器是系統類加載器。類加載器通過這種方式組織起來,形成樹狀結構。樹的根節點就是引導類加載器。
1.2.3 雙親委派模型
類加載器在嘗試自己去查找某個類的位元組代碼并定義它時,會先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推。
在介紹代理模式背後的動機之前,首先需要說明一下 Java 虛拟機是如何判定兩個 Java 類是相同的。**Java 虛拟機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。**隻有兩者都相同的情況,才認為兩個類是相同的。即便是同樣的位元組代碼,被不同的類加載器加載之後所得到的類,也是不同的。比如一個 Java 類 com.example.Sample,編譯之後生成了位元組代碼檔案 Sample.class。兩個不同的類加載器 ClassLoaderA和 ClassLoaderB分别讀取了這個 Sample.class檔案,并定義出兩個 java.lang.Class類的執行個體來表示這個類。這兩個執行個體是不相同的。對于 Java 虛拟機來說,它們是不同的類。試圖對這兩個類的對象進行互相指派,會抛出運作時異常 ClassCastException。
是以才有雙親委派模型,這樣的話,可保證加載的類(特别是Object和String這類基礎類)是同一個。
- package classloaderstring;
- /**
- * Created by hupo.wh on 2016/7/7.
- */
- public class String {
- public java.lang. String toString() {
- return "這是我自定義的String類的toString方法";
- }
- }
- package classloaderstring;
- import java.lang.*;
- import java.lang.reflect.Method;
- /**
- * Created by hupo.wh on 2016/7/7.
- */
- public class TestString {
- public static void main(java.lang. String args[]) throws Exception {
- java.lang. String classDataRootPath = "D:\\xiaohua\\WhTest\\out\\production\\WhTest\\classloader\\Sample";
- FileSystemClassLoader fscl 1 = new FileSystemClassLoader(classDataRootPath);
- Class<?> class 1 = fscl 1.loadClass( "classloaderstring.String");
- Object obj 1 = class 1.newInstance();
- System. out. println(java.lang. String. class.getClassLoader());
- System. out. println( class 1.getClassLoader());
- System. out. println(java.lang. String. class);
- System. out. println( class 1);
- Method setSampleMethod = class 1.getMethod( "toString");
- System. out. println(setSampleMethod.invoke(obj 1));
- }
- }
輸出:
- null
- [email protected] a57993
- class java.lang. String
- class classloaderstring. String
- 這是我自定義的 String類的toString方法
這兩個類并不是一個String類,要包名類名+loader一緻是不可能的,是以雙親委派模型從外界無法破壞。
注意:這裡有
- 若加載的類能被系統加載器加載到(Sample類在classpath下),則無異常。因為defining class loader都是AppClassLoader
- 若加載的類不能被系統加載器加載到,則抛異常。此時的 defining class loader 才是自定義的 FileSystemClassLoader
1.2.4 defining loader 和 initiating loader
前面提到過類加載器會首先代理給其它類加載器來嘗試加載某個類。這就意味着真正完成類的加載工作的類加載器和啟動這個加載過程的類加載器,有可能不是同一個。真正完成類的加載工作是通過調用 defineClass來實作的;而啟動類的加載過程是通過調用 loadClass來實作的。前者稱為一個類的定義加載器(**defining loader**),後者稱為初始加載器(**initiating loader**)。在 Java 虛拟機判斷兩個類是否相同的時候,使用的是類的定義加載器。也就是說,哪個類加載器啟動類的加載過程并不重要,重要的是最終定義這個類的加載器。兩種類加載器的關聯之處在于:**一個類的定義加載器是它引用的其它類的初始加載器。**如類 com.example.Outer引用了類 com.example.Inner,則由類 com.example.Outer的定義加載器負責啟動類 com.example.Inner的加載過程。
方法 loadClass()抛出的是 java.lang.ClassNotFoundException異常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError異常。
類加載器在成功加載某個類之後,會把得到的 java.lang.Class類的執行個體緩存起來。下次再請求加載該類的時候,類加載器會直接使用緩存的類的執行個體,而不會嘗試再次加載。也就是說,對于一個類加載器執行個體來說,相同全名的類隻加載一次,即 loadClass方法不會被重複調用。
1.2.5 Class.forName 加載
Class.forName是一個靜态方法,同樣可以用來加載類。該方法有兩種形式:
Class.forName(String name, boolean initialize, ClassLoader loader)
和
Class.forName(String className)
第一種形式的參數 name表示的是類的全名;initialize表示是否初始化類;loader表示加載時使用的類加載器。
第二種形式則相當于設定了參數 initialize的值為 true,loader的值為目前類的類加載器。Class.forName的一個很常見的用法是在加載資料庫驅動的時候。如
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()
用來加載 Apache Derby 資料庫的驅動。
詳見:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html
1.2.6 類加載過程
從類被加載到虛拟機記憶體中開始,到解除安裝出記憶體為止,類的生命周期包括加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和解除安裝(Unloading)7個階段。
http://www.open-open.com/lib/view/open1352161045813.html
其中加載(除了自定義加載)+連結的過程是完全由jvm負責的,什麼時候要對類進行初始化工作(加載+連結在此之前已經完成了),jvm有嚴格的規定(四種情況):
1.遇到new,getstatic,putstatic,invokestatic這4條位元組碼指令時,加入類還沒進行初始化,則馬上對其進行初始化工作。其實就是3種情況:用new執行個體化一個類時、讀取或者設定類的靜态字段時(不包括被final修飾的靜态字段,因為他們已經被塞進常量池了)、以及執行靜态方法的時候。
2.使用java.lang.reflect.*的方法對類進行反射調用的時候,如果類還沒有進行過初始化,馬上對其進行。
3.初始化一個類的時候,如果他的父親還沒有被初始化,則先去初始化其父親。
4.當jvm啟動時,使用者需要指定一個要執行的主類(包含static void main(String[] args)的那個類),則jvm會先去初始化這個類。
以上4種預處理稱為對一個類進行主動的引用,其餘的其他情況,稱為被動引用,都不會觸發類的初始化。
加載:
在加載階段,虛拟機主要完成三件事:
1.通過一個類的全限定名來擷取定義此類的二進制位元組流。
2.将這個位元組流所代表的靜态存儲結構轉化為方法區域的運作時資料結構。
3.在Java堆中生成一個代表這個類的java.lang.Class對象,作為方法區域資料的通路入口。
驗證:
驗證階段作用是保證Class檔案的位元組流包含的資訊符合JVM規範,不會給JVM造成危害。如果驗證失敗,就會抛出一個java.lang.VerifyError異常或其子類異常。驗證過程分為四個階段:
1.檔案格式驗證:驗證位元組流檔案是否符合Class檔案格式的規範,并且能被目前虛拟機正确的處理。
2.中繼資料驗證:是對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合Java語言的規範。
3.位元組碼驗證:主要是進行資料流和控制流的分析,保證被校驗類的方法在運作時不會危害虛拟機。
4.符号引用驗證:符号引用驗證發生在虛拟機将符号引用轉化為直接引用的時候,這個轉化動作将在解析階段中發生。
準備:
準備階段為變量配置設定記憶體并設定類變量的初始化。在這個階段配置設定的僅為類的變量(static修飾的變量),而不包括類的執行個體變量,執行個體變量将會在對象執行個體化時随着對象一起配置設定在Java堆中。對非final的變量,JVM會将其設定成“零值”,而不是其指派語句的值:
private static int size = ;
那麼在這個階段,size的值為0,而不是12。 final修飾的類變量将會指派成真實的值。
解析:
解析過程是将常量池内的符号引用替換成直接引用。主要包括四種類型引用的解析。類或接口的解析、字段解析、方法解析、接口方法解析。
初始化:
在準備階段,類變量已經經過一次初始化了,在這個階段,則是根據程式員通過程式制定的計劃去初始化類的變量和其他資源。這些資源有static{}塊,構造函數,父類的初始化等。
至于使用和解除安裝階段階段,這裡不再過多說明,使用過程就是根據程式定義的行為執行,解除安裝由GC完成
3、垃圾回收 GC
1.3.1 引用計數法
目前主流的虛拟機都沒有使用引用計數法,主要原因就是它很難解決對象之間互相循環引用的問題。
1.3.2 可達性分析算法
思想:
通過一系列稱為 GC Roots 的對象作為起始點,從這些點開始向下搜尋,搜尋走過的路徑稱為引用鍊,當一個對象到GC Roots沒有任何引用鍊連接配接(用圖論的話來說,就是從GC Roots到這個對象不可達),證明此對象不可用。
Java語言中,可作為GC Roots的對象包括:
(1)虛拟機棧(棧幀中的本地變量表)中引用的對象
(2)方法區中類靜态屬性引用的對象
(3)方法區中常量引用的對象
(4)本地方法棧中JNI ( 即一般說的Native方法)引用的對象
1.3.3 再談引用
在JDK 1.2之後 ,Java對引用的概念進行了擴充,将引用分為強引用(Strong Reference )、軟引用(Soft Reference )、弱引用(Weak Reference )、虛引用(Phantom Reference) 4種 , 引用強度依次逐漸減弱。
強引用
指在程式代碼之中普遍存在的,類似“Object obj=new Object ( ) ”這類的引用 ,隻要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
軟引用
用來描述一些還有用但并非必需的對象。對于軟引用關聯着的對象,在系統将要發生記憶體溢出異常之前,将會把這些對象列進回收範圍之中進行**二次回收**。如果這次回收還沒有足夠的記憶體,才會拋出記憶體溢出異常。在JDK 1.2之後,提供了SoftReference類來實作軟引用。
弱引用
也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象隻能生存到下一次垃圾收集發生之前。在JDK1.2之後,提供了PhantomReference類來實作虛引用。
虛引用
也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象執行個體。為一個對象設定虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。在JDK1.2之後,提供了PhantomReference類來實作虛引用。
1.3.4 對象回收過程
即使在可達性分析算法中不可達的對象,也并非是“非死不可”的 ,這時候它們暫時處于“緩刑” 階段 ,要真正宣告一個對象死亡 ,至少要經曆**兩次标記過程**
如果這個對象被判定為有必要執行finalize() 方法,那麼這個對象将會放置在一個叫做 F-Queue的隊列之中,并在稍後由一個由虛拟機自動建立的、低優先級的Finalizer線程去執行它。
1.3.5 對于方法區(Hotspot虛拟機的永久代)的回收
判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面3個條件才能算是“無用的類”:
(1)該類所有的執行個體都已經被回收,也就是Java堆中不存在該類的任何執行個體
(2)加載該類的ClassLoader已經被回收
(3)該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射通路該類的方法
詳情參考:深入了解Java虛拟機第三章 對象存活判定算法
1.3.6 垃圾收集算法
1.3.6.1 标記-清除算法
望名生意,算法分為“标記”和“清除”兩個階段:
首先标記出所有需要回收的對象,在标記完成後統一回收所有被标記的對象,它的标記過程如前
它的主要不足有兩個:
(1)效率問題,标記和清除兩個過程的效率都不高;
(2)空間問題,标記清除之後會産生**大量不連續的記憶體碎片**,空間碎片太多可能會導緻以後在程式運作過程中需要配置設定較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。
1.3.6.2 複制算法
将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。
适用于對象存活率低的場景(新生代)
這樣使得每次都是對整個半區進行記憶體回收,記憶體配置設定時也就不用考慮記憶體碎片等複雜情況,隻要移動堆頂指針 ,按順序配置設定記憶體即可,實作簡單,運作高效。隻是這種算法的代價是将記憶體縮小為了原來的一半,未免太高了一點。
将記憶體分為**一塊較大的Eden空間和兩塊較小的Survivor空間** ,每次使用Eden和其中一塊Survivor。當回收時,将Eden和Survivor中還存活着的對象一次地複制到另外一塊Survivor空間上,最 後清理掉Eden和剛才用過的Survivor空間。HotSpot虛拟機預設Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用記憶體空間為整個新生代容量的90% ( 80%+10% ) ,隻有10% 的記憶體會被 “浪費”。當然,98%的對象可回收隻是一般場景下的資料,我們沒有辦法保證每次回收都隻有不多于10%的對象存活,當Survivor空間不夠用時,需要依賴其他記憶體(這裡指老年代)進行配置設定擔保( Handle Promotion ) 。
1.3.6.3 标記-整理算法
适用于對象存活率高的場景(老年代)
複制收集算法在對象存活率較高時就要進行較多的複制操作,效率将會變低。更關鍵的是 ,如果不想浪費50%的空間,就需要有額外的空間進行配置設定擔保,以應對被使用的記憶體中所有對象都100%存活的極端情況,是以在老年代一般不能直接選用這種算法。
标記過程類似“标記-清除”算法,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體,類似于磁盤整理的過程
總的分類如下圖:
1.3.7 記憶體申請過程
記憶體由Perm和Heap組成。其中Heap = {Old + NEW = { Eden , from, to } }。perm用來存放常量等。
heap中分為年輕代(young)和年老代(old)。年輕代又分為Eden,Survivor(幸存區)。Survivor又分為from,to,也可以不隻是這兩塊,切from和to沒有先後順序。其中,old和young區比例可手動配置設定。
當OLD區空間不夠時,JVM會在OLD區進行完全的垃圾收集。完全垃圾收集後,若Survivor及OLD區仍然無法存放從Eden複制過來的部分對象,導緻JVM無法在Eden區為新對象建立記憶體區域,則出現”out of memory”Error。
好文請見:http://blog.csdn.net/scythe666/article/details/51852938
4、JVM啟動過程
JVM工作原理和特點主要是指作業系統裝入JVM是通過jdk中Java.exe來完成,通過下面4步來完成JVM環境.
1.建立JVM裝載環境和配置
2.裝載JVM.dll
3.初始化JVM.dll并挂界到JNIENV(JNI調用接口)執行個體
4.調用JNIEnv執行個體裝載并處理class類。
詳見:http://blog.csdn.net/ning109314/article/details/10411495
5、Class檔案結構
Class檔案的總體結構如下:
- Class 檔案 {
- 檔案描述
- 常量池
- 類概述
- 字段表
- 方法表
- 擴充資訊表
- }
1.5.1 檔案描述
(1)magic位、class檔案版本号。Magic位很容易記住,數值是0xCAFEBABE。
(2)常量池
存儲一組常量,供class檔案中其它元素引用。常量池中順序存儲着一組常量,常量在池中的位置稱為索引。Class檔案中其它結構通過索引來引用常量。常量最主要是被指令引用。編譯器将源碼編譯成指令和常量,圖形表示如下:
(3)類概述
存儲了目前類的總體資訊,包括目前類名、所繼承的父類、所實作的接口。
(4)字段表
存儲了一組字段結構,類中每個字段對應一個字段結構。
字段結構存儲了字段的資訊,包括字段名、字段修飾符、字段指向的類型等。
(5)方法表
存儲了一組方法結構,類中每個方法對應一個方法結構。
方法結構比較複雜,它内部最重要的結構是Code結構。每個非抽象方法的方法結構下有一個Code結構,存儲了方法的位元組碼。
(6)擴充資訊表
存儲了類級别的可選資訊,例如類級别的annotation。(方法、字段級别的annotation分别存儲在方法結構、字段結構中)
1.5.2 棧結構
我們對于站結構的内部構造,大部分則了解甚少。位元組碼的執行依賴棧結構,了解棧結構是了解位元組碼的基礎。
棧由幀組成,一個幀對應一個方法調用。一個方法被調用時,一個幀被建立,方法傳回時,對應的幀被銷毀。
幀存儲了方法執行期間的資料,包括變量資料和計算的中間結果。幀由兩部分組成,變量表和操作棧。這兩個結構是位元組碼執行期間直接依賴的兩個結構
操作棧
顧名思義,操作棧是一個棧結構,即LIFO結構。操作棧位于幀内部,用于存儲方法執行期間的中間結果。操作棧在JVM中的角色,類似于寄存器在實體機中的角色。
位元組碼中絕大多數指令,都是圍繞着操作棧執行的。它們或是從其他地方讀資料,壓入操作棧;或是從操作棧彈資料進行處理;還有的先彈資料,再處理,最會将結果壓入操作。
在JVM中,要對資料進行處理,首先要把資料讀進操作棧。
int變量求和
要對兩個int變量求和,我們先通過iload指令量兩個變量壓入操作棧,然後執行iadd指令。iadd從操作棧彈出兩個int值,求和,然後将結果壓入操作棧。
調用方法對象
調用對象方法時,我們需要将被調用對象,調用參數依次壓入操作棧,然後執行invokevirtual指令。該指令從操作棧彈出調用參數,被調用對象,執行方法調用。
變量表
變量表用于存儲變量資料。
變量表由一組槽組成。一個槽能夠存儲一個除long、double外其他類型的資料。兩個槽能夠存儲一個long型或double型資料。變量所在的槽在變量表中位置稱為變量索引,對于long和double類型,變量索引是第一個槽的位置。
變量在表量表中的順序是:
this、方法參數(從左向右)、其它變量
如果是static方法,則this沒有。
示例:
有如下方法:
- void test ( int a, int b){
- int c= ;
- long d= ;
- }
其對應的變量表為:
二、Java基礎
1、什麼是接口?什麼是抽象類?差別是什麼?
2.1.1 接口
在軟體工程中,接口泛指供别人調用的方法或者函數。從這裡,我們可以體會到Java語言設計者的初衷,它是**對行為的抽象**。
接口中可以含有 變量和方法。但是要注意,接口中的變量會**被隐式地指定為public static final變量**(并且**隻能是public static final變量**,用private修飾會報編譯錯誤),而**方法會被隐式地指定為public abstract方法且隻能是public abstract方法**(用其他關鍵字,比如private、protected、static、 final等修飾會報編譯錯誤),并且接口中所有的方法不能有具體的實作,也就是說,接口中的方法必須都是抽象方法。從這裡可以隐約看出接口和抽象類的差別,接口是一種極度抽象的類型,它比抽象類更加“抽象”,并且一般情況下不在接口中定義變量。
可以看出,允許一個類遵循多個特定的接口。如果一個非抽象類遵循了某個接口,就必須實作該接口中的所有方法。對于遵循某個接口的抽象類,可以不實作該接口中的抽象方法。
2.1.2 抽象類
抽象方法是一種特殊的方法:它隻有聲明,而沒有具體的實作。抽象方法的聲明格式為:
abstract void fun();
抽象方法必須用abstract關鍵字進行修飾。如果一個類含有抽象方法,則稱這個類為抽象類,抽象類必須在類前用abstract關鍵字修飾。因為抽象類中含有無具體實作的方法,是以**不能用抽象類建立對象。**
下面要注意一個問題:在《JAVA程式設計思想》一書中,将抽象類定義為“包含抽象方法的類”,但是後面發現如果一個類不包含抽象方法,隻是用abstract修飾的話也是抽象類。也就是說抽象類不一定必須含有抽象方法。個人覺得這個屬于鑽牛角尖的問題吧,因為如果一個抽象類不包含任何抽象方法,為何還要設計為抽象類?是以暫且記住這個概念吧,不必去深究為什麼。
- /**
- * Created by hupo.wh on 2016/7/7.
- */
- public abstract class AbstractClass {
- public void ab() {
- System. out.println( "Hello");
- }
- }
從這裡可以看出,抽象類就是為了繼承而存在的,如果你定義了一個抽象類,卻不去繼承它,那麼等于白白建立了這個抽象類,因為你不能用它來做任何事情。對于一個父類,如果它的某個方法在父類中實作出來沒有任何意義,必須根據子類的實際需求來進行不同的實作,那麼就可以将這個方法聲明為abstract方法,此時這個類也就成為abstract類了。
包含抽象方法的類稱為抽象類,但并不意味着抽象類中隻能有抽象方法,它和普通類一樣,同樣可以擁有成員變量和普通的成員方法。注意,抽象類和普通類的主要有三點差別:
1)抽象方法必須為public或者protected(因為如果為private,則不能被子類繼承,子類便無法實作該方法),預設情況下預設為public。
2)抽象類不能用來建立對象;
3)如果一個類繼承于一個抽象類,則子類必須實作父類的抽象方法。如果子類沒有實作父類的抽象方法,則必須将子類也定義為為abstract類。
在其他方面,抽象類和普通的類并沒有差別。
2.1.3 差別
2.1.3.1 文法層面上的差別
1)抽象類可以提供成員方法的實作細節,而接口中隻能存在public abstract 方法;
2)抽象類中的成員變量可以是各種類型的,而接口中的成員變量隻能是public static final類型的;
3)接口中不能含有靜态代碼塊以及靜态方法,而抽象類可以有靜态代碼塊和靜态方法;
4)一個類隻能繼承一個抽象類,而一個類卻可以實作多個接口。
2.1.3.2 設計層面上的差別
1)**抽象類是對一種事物的抽象,即對類抽象,而接口是對行為的抽象。**
抽象類是對整個類整體進行抽象,包括屬性、行為,但是接口卻是對類局部(行為)進行抽象。舉個簡單的例子,飛機和鳥是不同類的事物,但是它們都有一個共性,就是都會飛。那麼在設計的時候,可以将飛機設計為一個類Airplane,将鳥設計為一個類Bird,但是不能将 飛行 這個特性也設計為類,是以它隻是一個行為特性,并不是對一類事物的抽象描述。此時可以将 飛行 設計為一個接口Fly,包含方法fly( ),然後Airplane和Bird分别根據自己的需要實作Fly這個接口。然後至于有不同種類的飛機,比如戰鬥機、民用飛機等直接繼承Airplane即可,對于鳥也是類似的,不同種類的鳥直接繼承Bird類即可。從這裡可以看出,繼承是一個 "是不是"的關系,而 接口 實作則是 "有沒有"的關系。如果一個類繼承了某個抽象類,則子類必定是抽象類的種類,而接口實作則是有沒有、具備不具備的關系,比如鳥是否能飛(或者是否具備飛行這個特點),能飛行則可以實作這個接口,不能飛行就不實作這個接口。
2)**設計層面不同,抽象類作為很多子類的父類,它是一種模闆式設計。而接口是一種行為規範,它是一種輻射式設計。**
什麼是模闆式設計?最簡單例子,大家都用過ppt裡面的模闆,如果用模闆A設計了ppt B和ppt C,ppt B和ppt C公共的部分就是模闆A了,如果它們的公共部分需要改動,則隻需要改動模闆A就可以了,不需要重新對ppt B和ppt C進行改動。而輻射式設計,比如某個電梯都裝了某種報警器,一旦要更新報警器,就必須全部更新。也就是說對于抽象類,如果需要添加新的方法,可以直接在抽象類中添加具體的實作,子類可以不進行變更;而對于接口則不行,如果接口進行了變更,則所有實作這個接口的類都必須進行相應的改動。
詳見好文:http://www.cnblogs.com/dolphin0520/p/3811437.html
2、什麼是序列化?
2.2.1 概念
序列化,序列化是可以把對象轉換成位元組流在網絡上傳輸。将一個java對象變成位元組流的形式傳出去或者從一個位元組流中恢複成一個java對象。
個人認為,序列化就是一種思想,能夠完成轉換,能夠轉換回來,效率越高越好
序列化(Serialization)是将對象的狀态資訊轉換為可以存儲或傳輸的形式的過程。在序列化期間,對象将其目前狀态寫入到臨時或持久性存儲區。之後可以通過從存儲區中讀取或反序列化對象的狀态,重新建立該對象。
java中的序列化(serialization)機制能夠将一個執行個體對象的狀态資訊寫入到一個位元組流中,使其可以通過socket進行傳輸、或者持久化存儲到資料庫或檔案系統中;然後在需要的時候,可以根據位元組流中的資訊來重構一個相同的對象。序列化機制在java中有着廣泛的應用,EJB、RMI等技術都是以此為基礎的。
一般而言,要使得一個類可以序列化,隻需簡單實作java.io.Serializable接口即可(還要實作無參數的構造方法)。該接口是一個标記式接口,它本身不包含任何内容,實作了該接口則表示這個類準備支援序列化的功能。
2.2.2 序列化與反序列化例程
序列化一般有三種形式:預設形式、xml、json格式
預設格式如下:
- package serializable;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- /**
- * Created by hupo.wh on 2016/7/3.
- */
- public class SerializeToFlatFile {
- public static void main(String[] args) {
- SerializeToFlatFile ser = new SerializeToFlatFile();
- ser.savePerson();
- ser.restorePerson();
- }
- public void savePerson(){
- Person myPerson = new Person( "Jay", );
- try{
- FileOutputStream fos = new FileOutputStream( "d:\\person.txt");
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- System. out.println( "Person--Jay,24---Written");
- oos.writeObject(myPerson);
- oos.flush();
- oos.close();
- } catch(Exception e){
- e.printStackTrace();
- }
- }
- //@SuppressWarnings("resource")
- public void restorePerson(){
- try{
- FileInputStream fls = new FileInputStream( "d:\\person.txt");
- ObjectInputStream ois = new ObjectInputStream(fls);
- Person myPerson = (Person)ois.readObject();
- System. out.println( "\n---------------------\n");
- System. out.println( "Person --read:");
- System. out.println( "Name is:"+myPerson.getName());
- System. out.println( "Age is :"+myPerson.getAge());
- } catch(Exception e){
- e.printStackTrace();
- }
- }
- }
另兩種大同小異
2.2.3 應用場景
序列化的實作:将需要被序列化的類實作Serializable接口,該接口沒有需要實作的方法,implements Serializable隻是為了标注該對象是可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,接着,使用ObjectOutputStream對象的writeObject(Object obj)方法就可以将參數為obj的對象寫出(即儲存其狀态),要恢複的話則用輸入流
詳見:http://blog.csdn.net/scythe666/article/details/51718784
三種情況下需要進行序列化
1、把對象持久化到檔案或資料中
2、在網絡上傳輸
3、進行RMI傳輸對象時
RPC和RMI都是遠端調用,屬于中間件技術。RMI是針對于java語言的,它使用的是JRMP協定通信,而RPC是更大衆化的,使用http協定傳輸。
其版本号id,Java的序列化機制是通過在運作時判斷類的serialVersionUID來驗證版本一緻性的。在進行反序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認為是一緻的,可以進行反序列化,否則就會出現序列化版本不一緻的異常。
常用序列化技術有3種:java seriaizable,hessian,hessian2,以及protobuf
工具有很多,網上有個對比:
詳見:http://kb.cnblogs.com/page/515982/
3、網絡通信過程及實踐
2.3.1 TCP三向交握和四次揮手
明顯三次握手是建立連接配接,四次揮手是斷開連接配接,總圖如下:
2.3.1.1 握手
(1)首先,Client端發送連接配接請求封包(SYN=1,seq=client_isn)
(2)Server段接受連接配接後回複ACK封包,并為這次連接配接配置設定資源。(SYN=1,seq=client_isn,ack = client_isn+1)
(3)Client端接收到ACK封包後也向Server段發生ACK封包,并配置設定資源,這樣TCP連接配接就建立了。(SYN=0,seq=client_isn+1,ack = server_isn+1)
三次握手過程如下圖所示:
2.3.1.2 揮手
注意:
中斷連接配接端可以是Client端,也可以是Server端。
(1)假設Client端發起中斷連接配接請求,也就是發送FIN封包。
(2) Server端接到FIN封包後,意思是說"我Client端沒有資料要發給你了",但是如果你還有資料沒有發送完成,則不必急着關閉Socket,可以繼續發送資料。是以 Server 端會先發送ACK,"告訴Client端,你的請求我收到了,但是我還沒準備好,請繼續你等我的消息"。
這個時候Client端就進入 FIN_WAIT 狀态,繼續等待Server端的FIN封包。
(3)當Server端确定資料已發送完成,則向Client端發送FIN封包,"告訴Client端,好了,我這邊資料發完了,準備好關閉連接配接了"。
(4)Client端收到FIN封包後,"就知道可以關閉連接配接了,但是他還是不相信網絡,怕Server端不知道要關閉,是以發送 ACK 後進入 TIME_WAIT 狀态,如果 Server 端沒有收到 ACK 則可以重傳“,Server端收到ACK後,"就知道可以斷開連接配接了"。
Client端等待了2MSL後依然沒有收到回複,則證明Server端已正常關閉,那好,我Client端也可以關閉連接配接了。Ok,TCP連接配接就這樣關閉了!
注意:
(1)2個wait狀态,FIN_WAIT和TIME_WAIT
(2)如果是Server端發起,過程反過來,因為在揮手的時候c和s在對等位置。
2.3.1.3 握手揮手狀态圖
Client端所經曆的狀态如下:
Server端所經曆的過程如下:
2.3.1.4 注意問題
1、在TIME_WAIT狀态中,如果TCP client端最後一次發送的ACK丢失了,它将重新發送。TIME_WAIT狀态中所需要的時間是依賴于實作方法的。典型的值為30秒、1分鐘和2分鐘。等待之後連接配接正式關閉,并且所有的資源(包括端口号)都被釋放。
2、為什麼連接配接的時候是三次握手,關閉的時候卻是四次握手?
答:因為當Server端收到Client端的SYN連接配接請求封包後,可以直接發送SYN+ACK封包。其中ACK封包是用來應答的,SYN封包是用來同步的。但是關閉連接配接時,當Server端收到FIN封包時,很可能并不會立即關閉SOCKET,是以隻能先回複一個ACK封包,告訴Client端,"你發的FIN封包我收到了"。隻有等到我Server端所有的封包都發送完了,我才能發送FIN封包,是以不能一起發送。故需要四步握手。
3、為什麼TIME_WAIT狀态需要經過2MSL(最大封包段生存時間)才能傳回到CLOSE狀态?
答:雖然按道理,四個封包都發送完畢,我們可以直接進入CLOSE狀态了,但是我們必須假象網絡是不可靠的,有可以最後一個ACK丢失。是以TIME_WAIT狀态就是用來重發可能丢失的ACK封包。
2.3.1.5 附:封包詳解
TCP封包中的SYN,FIN,ACK,PSH,RST,URG
TCP的三次握手是怎麼進行的:發送端發送一個SYN=1,ACK=0标志的資料包給接收端,請求進行連接配接,這是第一次握手;接收端收到請求并且允許連接配接的話,就會發送一個SYN=1,ACK=1标志的資料包給發送端,告訴它,可以通訊了,并且讓發送端發送一個确認資料包,這是第二次握手;最後,發送端發送一個SYN=0,ACK=1的資料包給接收端,告訴它連接配接已被确認,這就是第三次握手。之後,一個TCP連接配接建立,開始通訊。
*SYN:同步标志
同步序列編号(Synchronize Sequence Numbers)欄有效。該标志僅在三次握手建立TCP連接配接時有效。它提示TCP連接配接的服務端檢查序列編号,該序列編号為TCP連接配接初始端(一般是用戶端)的初始序列編号。在這裡,可以把 TCP序列編号看作是一個範圍從0到4,294,967,295的32位計數器。通過TCP連接配接交換的資料中每一個位元組都經過序列編号。在TCP報頭中的序列編号欄包括了TCP分段中第一個位元組的序列編号。
*ACK:确認标志
确認編号(Acknowledgement Number)欄有效。大多數情況下該标志位是置位的。TCP報頭内的确認編号欄内包含的确認編号(w+1,Figure-1)為下一個預期的序列編号,同時提示遠端系統已經成功接收所有資料。
*RST:複位标志
複位标志有效。用于複位相應的TCP連接配接。
*URG:緊急标志
緊急(The urgent pointer) 标志有效。緊急标志置位
*PSH:推标志
該标志置位時,接收端不将該資料進行隊列處理,而是盡可能快将資料轉由應用處理。在處理 telnet 或 rlogin 等互動模式的連接配接時,該标志總是置位的。
*FIN:結束标志
帶有該标志置位的資料包用來結束一個TCP回話,但對應端口仍處于開放狀态,準備接收後續資料。
TCP的幾個狀态對于我們分析所起的作用
在TCP層,有個FLAGS字段,這個字段有以下幾個辨別:SYN, FIN, ACK, PSH, RST, URG.其中,對于我們日常的分析有用的就是前面的五個字段。它們的含義是:SYN表示建立連接配接,FIN表示關閉連接配接,ACK表示響應,PSH表示有 DATA資料傳輸,RST表示連接配接重置。其中,ACK是可能與SYN,FIN等同時使用的,比如SYN和ACK可能同時為1,它表示的就是建立連接配接之後的響應,如果隻是單個的一個SYN,它表示的隻是建立連接配接。
TCP的幾次握手就是通過這樣的ACK表現出來的。但SYN與FIN是不會同時為1的,因為前者表示的是建立連接配接,而後者表示的是斷開連接配接。RST一般是在FIN之後才會出現為1的情況,表示的是連接配接重置。一般地,當出現FIN包或RST包時,我們便認為用戶端與伺服器端斷開了連接配接;而當出現SYN和SYN+ACK包時,我們認為用戶端與伺服器建立了一個連接配接。PSH為1的情況,一般隻出現在 DATA内容不為0的包中,也就是說PSH為1表示的是有真正的TCP資料包内容被傳遞。TCP的連接配接建立和連接配接關閉,都是通過請求-響應的模式完成的。
詳見:http://blog.csdn.net/scythe666/article/details/50967632
tcp的狀态
http://www.cnblogs.com/qlee/archive/2011/07/12/2104089.html
http://www.2cto.com/net/201209/157585.html
2.3.2 Socket通信
套接字(socket)是通信的基石,是支援TCP/IP協定的網絡通信的基本操作單元。它是網絡通信過程中端點的抽象表示,包含進行網絡通信必須的五種資訊:連接配接使用的協定,本地主機的IP位址,本地程序的協定端口,遠地主機的IP位址,遠地程序的協定端口。
套接字對是一個四元組,(local ip, local port, remote ip, remote port),通過這一四元組,唯一确定了網絡通信的兩端(兩個程序或線程),ip位址确定主機,端口确定程序。
經典的在同一台主機上兩個程序或線程之間的通信通過以下三種方法
管道通信(Pipes)
消息隊列(Message queues)
共享記憶體通信(Shared memory)
這裡有許多其他的方法,但是上面三中是非常經典的程序間通信。
詳見:http://blog.csdn.net/violet_echo_0908/article/details/49539593
socket程式設計執行個體:
- /////TalkClient .java
- package socket;
- import java.io.*;
- import java.net.*;
- /**
- * Created by hupo.wh on 2016/7/8.
- */
- public class TalkClient {
- public static void main( String args[]) {
- try {
- Socket socket = new Socket( "10.63.37.140", );
- //向本機的4700端口發出客戶請求
- BufferedReader sin = new BufferedReader( new InputStreamReader(System. in));
- //由系統标準輸入裝置構造BufferedReader對象
- PrintWriter os = new PrintWriter(socket.getOutputStream());
- //由Socket對象得到輸出流,并構造PrintWriter對象
- BufferedReader is = new BufferedReader( new InputStreamReader(socket.getInputStream()));
- //由Socket對象得到輸入流,并構造相應的BufferedReader對象
- String readline;
- readline = sin.readLine(); //從系統标準輸入讀入一字元串
- while (!readline. equals( "bye")) {
- //若從标準輸入讀入的字元串為 "bye"則停止循環
- os. println(readline);
- //将從系統标準輸入讀入的字元串輸出到Server
- os.flush();
- //重新整理輸出流,使Server馬上收到該字元串
- System. out. println( "Client:" + readline);
- //在系統标準輸出上列印讀入的字元串
- System. out. println( "Server:" + is.readLine());
- //從Server讀入一字元串,并列印到标準輸出上
- readline = sin.readLine(); //從系統标準輸入讀入一字元串
- } //繼續循環
- os.close(); //關閉Socket輸出流
- is.close(); //關閉Socket輸入流
- socket.close(); //關閉Socket
- } catch (Exception e) {
- System. out. println( "Error" + e); //出錯,則列印出錯資訊
- }
- }
- }
- /////TalkServer.java
- package socket;
- /**
- * Created by hupo.wh on 2016/7/8.
- */
- import java.io.*;
- import java.net.*;
- public class TalkServer{
- public static void main( String args[]) {
- try{
- ServerSocket server= null;
- try{
- server= new ServerSocket( );
- //建立一個ServerSocket在端口4700監聽客戶請求
- } catch(Exception e) {
- System. out. println( "can not listen to:"+e);
- //出錯,列印出錯資訊
- }
- Socket socket= null;
- try{
- socket=server.accept();
- //使用accept()阻塞等待客戶請求,有客戶
- //請求到來則産生一個Socket對象,并繼續執行
- System. out. println( "用戶端成功連接配接...");
- } catch(Exception e) {
- System. out. println( "Error."+e);
- //出錯,列印出錯資訊
- }
- String line;
- BufferedReader is= new BufferedReader( new InputStreamReader(socket.getInputStream()));
- //由Socket對象得到輸入流,并構造相應的BufferedReader對象
- PrintWriter os = new PrintWriter(socket.getOutputStream());
- //由Socket對象得到輸出流,并構造PrintWriter對象
- BufferedReader sin= new BufferedReader( new InputStreamReader(System. in));
- //由系統标準輸入裝置構造BufferedReader對象
- System. out. println( "Client:"+ is.readLine());
- //在标準輸出上列印從用戶端讀入的字元串
- line= sin.readLine();
- //從标準輸入讀入一字元串
- while(! line. equals( "bye")){
- //如果該字元串為 "bye",則停止循環
- os. println( line);
- //向用戶端輸出該字元串
- os.flush();
- //重新整理輸出流,使Client馬上收到該字元串
- System. out. println( "Server:"+ line);
- //在系統标準輸出上列印讀入的字元串
- System. out. println( "Client:"+ is.readLine());
- //從Client讀入一字元串,并列印到标準輸出上
- line= sin.readLine();
- //從系統标準輸入讀入一字元串
- } //繼續循環
- os.close(); //關閉Socket輸出流
- is.close(); //關閉Socket輸入流
- socket.close(); //關閉Socket
- server.close(); //關閉ServerSocket
- } catch(Exception e){
- System. out. println( "Error:"+e);
- //出錯,列印出錯資訊
- }
- }
- }
詳見:http://www.cnblogs.com/linzheng/archive/2011/01/23/1942328.html
2.3.3 Http
HTTP協定是無狀态的,同一個用戶端的這次請求和上次請求是沒有對應關系,對http伺服器來說,它并不知道這兩個請求來自同一個用戶端。 為了解決這個問題, Web程式引入了Cookie機制來維護狀态.
Http響應
在接收和解釋請求消息後,伺服器傳回一個HTTP響應消息。
HTTP響應也是由三個部分組成,分别是:狀态行、消息報頭、響應正文
1、狀态行格式如下:
- HTTP-Version Status-Code Reason-Phrase CRLF
- 其中,HTTP-Version表示伺服器HTTP協定的版本;Status-Code表示伺服器發回的響應狀态代碼;Reason-Phrase表示狀态代碼的文本描述。
- 狀态代碼有三位數字組成,第一個數字定義了響應的類别,且有五種可能取值:
- xx:訓示資訊 --表示請求已接收,繼續處理
- xx:成功 --表示請求已被成功接收、了解、接受
- xx:重定向 --要完成請求必須進行更進一步的操作
- xx:用戶端錯誤 --請求有文法錯誤或請求無法實作
- xx:伺服器端錯誤 --伺服器未能實作合法的請求
- 常見狀态代碼、狀态描述、說明:
- OK //用戶端請求成功
- Bad Request //用戶端請求有文法錯誤,不能被伺服器所了解
- Unauthorized //請求未經授權,這個狀态代碼必須和WWW-Authenticate報頭域一起使用
- Forbidden //伺服器收到請求,但是拒絕提供服務
- Not Found //請求資源不存在,eg:輸入了錯誤的URL
- Internal Server Error //伺服器發生不可預期的錯誤
- Server Unavailable //伺服器目前不能處理用戶端的請求,一段時間後可能恢複正常
- eg:HTTP/ OK ( CRLF)
2、響應報頭
3、響應正文就是伺服器傳回的資源的内容
詳見:(1)http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html
(2)http://kb.cnblogs.com/page/130970/#statelesshttp
(3)http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386832653051fd44e44e4f9e4ed08f3e5a5ab550358d000
4、什麼是線程?java線程池運作過程及實踐(Executors)
一個程序包括多個線程,但是這些線程是共同享有程序占有的資源和位址空間的。
程序是作業系統進行資源配置設定的基本機關,而線程是作業系統進行排程的基本機關。
程序可能包括多個線程。
好文:http://www.oschina.net/question/565065_86540
2.4.1 Volatile
線程的工作記憶體中儲存了被該線程使用到的變量的主記憶體的副本拷貝,線程對變量的所有操作(讀取、指派等)都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變量(包括volatile的底層實作)。
這裡的主記憶體、工作記憶體和Java堆棧、方法區不是一個層次記憶體劃分,基本上沒有關系。
如果要勉強對應:主記憶體對應Java堆中對象執行個體資料部分,工作記憶體對應于虛拟機棧中部分區域。
從更低層次來說,主記憶體就直接對應實體硬體記憶體,而為了優化,工作記憶體優先儲存于寄存器和高速緩存中。
volatile可以說是Java虛拟機提供的最輕量級的同步機制。
當一個變量定義為volatile以後,它将具備兩種屬性:
(1)保證此變量對所有線程的可見性
volatile的錯誤用法:
- package MultiThread;
- /**
- * Created by hupo.wh on 2016/7/8.
- */
- public class VolatileTest {
- private static final int THREAD_NUM = ;
- public static volatile int race= ;
- public static void increase(){
- race++ ;
- }
- public static void main(String[] args) {
- Thread[] threads = new Thread[THREAD_NUM];
- for ( int i = ; i < THREAD_NUM; i++) {
- threads[i] = new Thread( new Runnable() {
- @ Override
- public void run () {
- for( int i= ;i< ;i++){
- //System.out.println("race == "+race);
- increase();
- }
- }
- });
- threads[i].start();
- }
- while(Thread.activeCount()> ){
- Thread. yield();
- }
- System. out.println(race);
- }
- }
輸出的正确答案應該是200000,但是每次輸出都小于200000,并發失敗的問題在于increase()方法。用javap發編譯看一下發現就increase()方法在Class中檔案有四條位元組碼組成。
volatile變量隻能保證可見性,當不符合一下規則是還是使用synchronized或java.util.concurrent中的原子類。
1.運算結果并不依賴變量的目前值,或者能夠確定單一的線程修改變量的
2.變量不需要與其他的狀态變量共同參與不變限制。
正确用法:
- package MultiThread;
- /**
- * Created by hupo.wh on 2016/7/8.
- */
- public class VolatileShutdown {
- volatile boolean shutdownRequested;
- public void shutdown() {
- shutdownRequested = true;
- }
- public void doWork() {
- while (!shutdownRequested) {
- //do stuff
- }
- }
- }
(2)使用volatile變量的第二個語義是禁止指令重排序優化
2.4.2 原子性、可見性與有序性
(1)**原子性**
保證read、load、assign、use、store和write操作是原子的
(2)**可見性**
當一個線程修改了共享變量的值,其他線程可以立即得知這個修改
(3)**有序性**
本線程觀察,所有的操作都是有序的,如果在一個線程中觀察另一個線程,所有操作都是無序的(指令重排序和工作記憶體與主記憶體同步延遲)。
2.4.3 Lock vs Synchronized
Synchronized關鍵字經過編譯以後,會在同步塊前後分别形成monitorenter和monitorexit這兩個位元組碼指令。Synchronized 使用詳見:http://blog.csdn.net/luoweifu/article/details/46613015
主要相同點:lock能完成synchronized所實作的所有功能
主要不同點:lock有比synchronized更精确的線程語義和更好的性能.synchronized會自動釋放鎖,而Lock一定要求程式員手工釋放,并且必須在finally從句中釋放.
1、ReentrantLock 擁有Synchronized相同的并發性和記憶體語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候
線程A和B都要擷取對象O的鎖定,假設A擷取了對象O鎖,B将等待A釋放對O的鎖定,
如果使用 synchronized ,如果A不釋放,B将一直等下去,不能被中斷
如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹别的事情
- ReentrantLock擷取鎖定與三種方式:
- a) lock(), 如果擷取了鎖立即傳回,如果别的線程持有鎖,目前線程則一直處于休眠狀态,直到擷取鎖
- b) tryLock(), 如果擷取了鎖立即傳回 true,如果别的線程正持有鎖,立即傳回 false;
- c)tryLock( long timeout,TimeUnit unit), 如果擷取了鎖定立即傳回 true,如果别的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果擷取了鎖定,就傳回 true,如果等待逾時,傳回 false;
- d) lockInterruptibly:如果擷取了鎖定立即傳回,如果沒有擷取鎖定,目前線程處于休眠狀态,直到或者鎖定,或者目前線程被别的線程中斷
2、synchronized是在JVM層面上實作的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過代碼實作的,要保證鎖定一定會被釋放,就必須将unLock()放到finally{}中
3、在資源競争不是很激烈的情況下,Synchronized的性能要優于ReetrantLock,但是在資源競争很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常态;
2.4.4 threadlocal
ThreadLocal 不是用于解決共享變量的問題的,不是為了協調線程同步而存在,而是為了友善每個線程處理自己的狀态而引入的一個機制,了解這點對正确使用ThreadLocal至關重要。
我們先看一個簡單的例子:
- public class ThreadLocalTest {
- //建立一個Integer型的線程本地變量
- public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
- @ Override
- protected Integer initialValue () {
- return ;
- }
- };
- public static void main(String[] args) throws InterruptedException {
- Thread[] threads = new Thread[ ];
- for ( int j = ; j < ; j++) {
- threads[j] = new Thread( new Runnable() {
- @ Override
- public void run () {
- //擷取目前線程的本地變量,然後累加5次
- int num = local. get();
- for ( int i = ; i < ; i++) {
- num++;
- }
- //重新設定累加後的本地變量
- local. set(num);
- System. out.println(Thread.currentThread().getName() + " : "+ local. get());
- }
- }, "Thread-" + j);
- }
- for (Thread thread : threads) {
- thread.start();
- }
- }
- }
運作後結果:
Thread-0 : 5
Thread-4 : 5
Thread-2 : 5
Thread-1 : 5
Thread-3 : 5
我們看到,每個線程累加後的結果都是5,各個線程處理自己的本地變量值,線程之間互不影響。
詳見:http://my.oschina.net/clopopo/blog/149368
2.4.5 java線程池 Executor架構
要配置一個線程池是比較複雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,是以在Executors類裡面提供了一些靜态工廠,生成一些常用的線程池。
(1)newSingleThreadExecutor
建立一個**單線程**的線程池。這個線程池隻有一個線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的送出順序執行。
MyThread.java
- package threadpool;
- /**
- * Created by hupo.wh on 2016/7/2.
- */
- public class WhThread extends Thread{
- @Override
- public void run() {
- System.out.println( Thread.currentThread().getName() + "正在執行...");
- }
- }
TestSingleThreadExecutor.java
- package threadpool ;
- import java.util.concurrent.ExecutorService ;
- import java.util.concurrent.Executors ;
- /**
- * Created by hupo.wh on 2016/7/3.
- */
- public class TestSingleThreadExecutor {
- public static void main(String[] args) {
- //建立一個可重用固定線程數的線程池
- ExecutorService pool = Executors. newSingleThreadExecutor() ;
- //建立實作了Runnable接口對象,Thread對象當然也實作了Runnable接口
- Thread t1 = new WhThread() ;
- Thread t2 = new WhThread() ;
- Thread t3 = new WhThread() ;
- Thread t4 = new WhThread() ;
- Thread t5 = new WhThread() ;
- //将線程放入池中進行執行
- pool.execute(t1) ;
- pool.execute(t2) ;
- pool.execute(t3) ;
- pool.execute(t4) ;
- pool.execute(t5) ;
- //關閉線程池
- pool.shutdown() ;
- }
- }
輸出結果:
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
pool-1-thread-1正在執行...
(2)newFixedThreadPool
建立固定大小的線程池。每次送出一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。
- //建立一個可重用固定線程數的線程池
- ExecutorService pool = Executors.newFixedThreadPool( ) ;
(3)newCachedThreadPool
建立一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,
那麼就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴于作業系統(或者說JVM)能夠建立的最大線程大小。
- //建立一個可重用固定線程數的線程池
- ExecutorService pool = Executors.newCachedThreadPool() ;
(4)newScheduledThreadPool
建立一個大小無限的線程池。此線程池支援定時以及周期性執行任務的需求。
- package threadpool;
- import java.util.concurrent.ScheduledThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- /**
- * Created by hupo.wh on 2016/7/3.
- */
- public class TestSingleThreadExecutor {
- public static void main(String[] args) {
- ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor( );
- exec.scheduleAtFixedRate( new Runnable() { //每隔一段時間就觸發異常
- @Override
- public void run() {
- System.out.println( "================");
- throw new RuntimeException();
- }
- }, , , TimeUnit.MILLISECONDS);
- exec.scheduleAtFixedRate( new Runnable() { //每隔一段時間列印系統時間,證明兩者是互不影響的
- @Override
- public void run() {
- System.out.println(System.nanoTime());
- }
- }, , , TimeUnit.MILLISECONDS);
- }
- }
ThreadPoolExecutor構造函數
jvm本身提供的concurrent并發包,提供了高性能穩定友善的線程池,可以直接使用。
ThreadPoolExecutor是核心類,都是由它與3種Queue結合衍生出來的。
BlockingQueue + LinkedBlockingQueue + SynchronousQueue
ThreadPoolExecutor的完整構造方法的簽名是:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) .
corePoolSize - 池中所儲存的線程數,包括空閑線程。
maximumPoolSize-池中允許的最大線程數。
keepAliveTime - 當線程數大于核心時,此為終止前多餘的空閑線程等待新任務的最長時間。
unit - keepAliveTime 參數的時間機關。
workQueue - 執行前用于保持任務的隊列。此隊列僅保持由 execute方法送出的 Runnable任務。
threadFactory - 執行程式建立新線程時使用的工廠。
handler - 由于超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程式。
ThreadPoolExecutor是Executors類的底層實作。
在JDK幫助文檔中,有如此一段話:
“強烈建議程式員使用較為友善的Executors工廠方法Executors.newCachedThreadPool()(無界線程池,可以進行自動線程回收)、Executors.newFixedThreadPool(int)(固定大小線程池)Executors.newSingleThreadExecutor()(單個背景線程)
線程池實作原理
先從
BlockingQueue<Runnable> workQueue
這個入參開始說起。在JDK中,其實已經說得很清楚了,一共有三種類型的queue。
所有BlockingQueue 都可用于傳輸和保持送出的任務。可以使用此隊列與池大小進行互動:
(1)如果運作的線程少于 corePoolSize,則 Executor始終首選添加新的線程,而不進行排隊。(如果目前運作的線程小于corePoolSize,則任務根本不會存放,添加到queue中,而是直接抄家夥(thread)開始運作)
(2)如果運作的線程等于或多于 corePoolSize,則 Executor始終首選将請求加入隊列,而不添加新的線程。
(3)如果無法将請求加入隊列,則建立新的線程,除非建立此線程超出maximumPoolSize,在這種情況下,任務将被拒絕。
線程的狀态有 new、runnable、running、waiting、timed_waiting、blocked、dead 一旦線程調用了start 方法,線程就轉到Runnable 狀态,注意,如果線程處于Runnable狀态,它也有可能不在運作,這是因為還有優先級和排程問題。
排隊政策
排隊有三種通用政策:
(1)**直接送出**。工作隊列的預設選項是 SynchronousQueue,ExecutorService newCachedThreadPool():無界線程池,可以進行自動線程回收,是以我們可以發現maximumPoolSize為big big。
(2)**無界隊列**。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)将導緻在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,建立的線程就不會超過 corePoolSize。(是以,maximumPoolSize的值也就無效了。)當每個任務完全獨立于其他任務,即任務執行互不影響時,适合于使用無界隊列;例如,在 Web頁伺服器中。這種排隊可用于處理瞬态突發請求,當指令以超過隊列所能處理的平均數連續到達時,此政策允許無界線程具有增長的可能性。
(3)**有界隊列**。當使用有限的 maximumPoolSizes時,有界隊列(如 ArrayBlockingQueue)有助于防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要互相折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、作業系統資源和上下文切換開銷,但是可能導緻人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU使用率較高,但是可能遇到不可接受的排程開銷,這樣也會降低吞吐量。
keepAliveTime
jdk中的解釋是:當線程數大于核心時,此為終止前多餘的空閑線程等待新任務的最長時間。
有點拗口,其實這個不難了解,在使用了“池”的應用中,大多都有類似的參數需要配置。比如資料庫連接配接池,DBCP中的maxIdle,minIdle參數。
什麼意思?接着上面的解釋,後來向老闆派來的勞工始終是“借來的”,俗話說“有借就有還”,但這裡的問題就是什麼時候還了,如果借來的勞工剛完成一個任務就還回去,後來發現任務還有,那豈不是又要去借?這一來一往,老闆肯定頭也大死了。
合理的政策:既然借了,那就多借一會兒。直到“某一段”時間後,發現再也用不到這些勞工時,便可以還回去了。這裡的某一段時間便是keepAliveTime的含義,TimeUnit為keepAliveTime值的度量。
詳參:http://www.oschina.net/question/565065_86540
5、java反射機制實踐
詳見:http://blog.csdn.net/scythe666/article/details/51704809
反射可以拿到一個類所有的方法和屬性,包括父類和接口。
- package classloader;
- import java.lang.reflect.Method;
- /**
- * Created by hupo.wh on 2016/7/7.
- */
- public class App3 {
- private final static int size = ;
- public static void main(String args[]) throws ClassNotFoundException {
- //System.out.println(size);
- Class clazz = Class.forName( "classloader.Child");
- Method[] methods = clazz.getMethods();
- for ( int i= ;i<methods.length;++i) {
- System.out.println(methods[i]);
- }
- }
- }
- interface Test{
- int te = ;
- public void te();
- }
- abstract class Parent {
- int pa;
- public void pa() {
- System.out.println( "this is parent");
- }
- }
- class Child extends Parent implements Test{
- int ch;
- public void ch() {
- System.out.println( "this is child");
- }
- @Override
- public void te() {
- }
- }
輸出:
- public void classloader .Child .ch()
- public void classloader .Child .te()
- public void classloader .Parent .pa()
- public final void java .lang .Object .wait() throws java .lang .InterruptedException
- public final void java .lang .Object .wait( long, int) throws java .lang .InterruptedException
- public final native void java .lang .Object .wait( long) throws java .lang .InterruptedException
- public boolean java .lang .Object .equals( java .lang .Object)
- public java .lang .String java .lang .Object .toString()
- public native int java .lang .Object .hashCode()
- public final native java .lang .Class java .lang .Object .getClass()
- public final native void java .lang .Object .notify()
- public final native void java .lang .Object .notifyAll()
三、設計模式
1、單例模式
- //懶漢式單例類.在第一次調用的時候執行個體化自己
- public class Singleton {
- private Singleton() {}
- private static Singleton single= null;
- //靜态工廠方法
- public static Singleton getInstance() {
- if (single == null) {
- single = new Singleton();
- }
- return single;
- }
- }
線程安全+懶加載實作:
- public class TestSingleton {
- private TestSingleton() {}
- private static class SingletonHolder {
- static TestSingleton testSingleton = new TestSingleton();
- }
- public TestSingleton getInstance() {
- return SingletonHolder.testSingleton;
- }
- }
詳見:http://blog.csdn.net/jason0539/article/details/23297037
2、原型模式
類圖:
原型模式主要用于對象的複制,它的核心是就是類圖中的原型類Prototype。Prototype類需要具備以下兩個條件:
(1)實作Cloneable接口。在java語言有一個Cloneable接口,它的作用隻有一個,就是在運作時通知虛拟機可以安全地在實作了此接口的類上使用clone方法。在java虛拟機中,隻有實作了這個接口的類才可以被拷貝,否則在運作時會抛出CloneNotSupportedException異常。
(2)重寫Object類中的clone方法。Java中,所有類的父類都是Object類,Object類中有一個clone方法,作用是傳回對象的一個拷貝,但是其作用域protected類型的,一般的類無法調用,是以,Prototype類需要将clone方法的作用域修改為public類型。
原型模式是一種比較簡單的模式,也非常容易了解,實作一個接口,重寫一個方法即完成了原型模式。在實際應用中,原型模式很少單獨出現。經常與其他模式混用,他的原型類Prototype也常用抽象類來替代。
- class Prototype implements Cloneable {
- public Prototype clone(){
- Prototype prototype = null;
- try{
- prototype = (Prototype) super.clone();
- } catch(CloneNotSupportedException e){
- e.printStackTrace();
- }
- return prototype;
- }
- }
- class ConcretePrototype extends Prototype{
- public void show(){
- System.out.println( "原型模式實作類");
- }
- }
- public class Client {
- public static void main(String[] args){
- ConcretePrototype cp = new ConcretePrototype();
- for( int i= ; i< ; i++){
- ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
- clonecp.show();
- }
- }
- }
詳見:http://blog.csdn.net/jason0539/article/details/23158081
3、動态代理模式
一般的設計模式中的代理模式指的是靜态代理,但是Java實作了動态代理
靜态代理的每一個代理類隻能為一個或一組接口服務,這樣一來程式開發中必然會産生過多的代理,而且,所有的代理操作除了調用的方法不一樣之外,其他的操作都一樣,則此時肯定是重複代碼。解決這一問題最好的做法是可以通過一個代理類完成全部的代理功能,那麼此時就必須使用動态代理完成。
來看一下動态代理:
JDK動态代理中包含一個類和一個接口:
InvocationHandler接口:
- public interface InvocationHandler {
- public Object invoke( Object proxy, Method method , Object [] args) throws Throwable;
- }
參數說明:
- Object proxy:指被代理的對象。
- Method method:要調用的方法
- Object[] args:方法調用時所需要的參數
可以将InvocationHandler接口的子類想象成一個代理的最終操作類,替換掉ProxySubject。
Proxy類:
Proxy類是專門完成代理的操作類,可以通過此類為一個或多個接口動态地生成實作類,此類提供了如下的操作方法:
- public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
- InvocationHandler h) throws IllegalArgumentException
參數說明:
- ClassLoader loader:類加載器
- Class<?> [] interfaces:得到全部的接口
- InvocationHandler h:得到 InvocationHandler接口的子類執行個體
Ps:類加載器
在Proxy類中的newProxyInstance()方法中需要一個ClassLoader類的執行個體,ClassLoader實際上對應的是類加載器,在Java中主要有一下三種類加載器;
Booststrap ClassLoader:此加載器采用C++編寫,一般開發中是看不到的;
Extendsion ClassLoader:用來進行擴充類的加載,一般對應的是jre\lib\ext目錄中的類;
AppClassLoader:(預設)加載classpath指定的類,是最常使用的是一種加載器。
動态代理
與靜态代理類對照的是動态代理類,動态代理類的位元組碼在程式運作時由Java反射機制動态生成,無需程式員手工編寫它的源代碼。動态代理類不僅簡化了程式設計工作,而且提高了軟體系統的可擴充性,因為Java 反射機制可以生成任意類型的動态代理類。java.lang.reflect 包中的Proxy類和InvocationHandler 接口提供了生成動态代理類的能力。
- /BookFacade.java
- package jdkproxy;
- /**
- * Created by hupo.wh on 2016/7/4.
- */
- public interface BookFacade {
- public void addBook ();
- public void sayHello ();
- }
- /BookFacadeImpl.java
- package jdkproxy;
- /**
- * Created by hupo.wh on 2016/7/4.
- */
- public class BookFacadeImpl implements BookFacade {
- @Override
- public void