1.java底層原理簡析
往往,在現在開發過程中,有很多操作,雖然功能都能去實現,但是在Jvm的內存分配上,是大有不同的,很可能兩個不同的實現方式,性能上也會有或多或少差異……
例如:
private Integer name = 4;
private static Integer name = 4;
private final static Integer name 4;
private final Integer name =4;
它們的name對應的值都是4,但是否思考過,究竟有何實際區別呢?
所以接下來,我們進入主題,去講解究竟有什么區別吧!
1.1 Java里的堆(heap)棧(stack)和方法區(method)
棧區:
1.每個線程包含一個棧區,棧中只保存基礎數據類型的對象和自定義對象的引用(不是對象),對象都存放在堆區中
2.每個棧中的數據(原始類型和對象引用)都是私有的,其他棧不能訪問。
3.棧分為3個部分:基本類型變量區、執行環境上下文、操作指令區(存放操作指令)。
堆區:
1.存儲的全部是對象,每個對象都包含一個與之對應的class的信息。(class的目的是得到操作指令)
2.jvm只有一個堆區(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身 。
方法區:
1.又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量。
2.方法區中包含的都是在整個程序中永遠唯一的元素,如class,static變量。
Java虛擬機在執行Java程序的過程中會把它所管理的內存划分為若干個不同的數據區域。這些區域都有各自的用途,以及創建和銷毀的時間。有的區域隨着虛擬機進程的啟動而存在,有些區域則是依賴用戶線程的啟動和結束而建立和銷毀。
JVM內存模型可以分為兩個部分,如下圖所示,堆和方法區是所有線程共有的,而虛擬機棧,本地方法棧和程序計數器則是線程私有的。下面我們就來分析一下這些不同區域的作用。
總結一下:
堆棧(Stack)是操作系統在建立某個進程時或者線程(在支持多線程的操作系統中是線程)為這個線程建立的存儲區域,該區域具有先進后出的特性。每一個Java應用都唯一對應一個JVM實例,每一個實例唯一對應一個堆。應用程序在運行中所創建的所有類實例或數組都放在這個堆中,並由應用所有的線程共享,Java中分配堆內存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時從兩個地方都分配內存,在堆中分配的內存實際建立這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。
方法區存着類的信息,常量和靜態變量,即類被編譯后的數據。
JVM內存模型圖:
上述貌似有點難懂,我接下來自己畫圖來解釋一下!
方法區剖析圖:
講了這么多,我們來點干貨演示一下!
比較類里面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,下面用例子說明上面的理論。
示例一:
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); //true
可以看出str1和str2是指向同一個對象的。
示例二:
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2); // false
用new的方式是生成不同的對象。每一次生成一個。
示例一和示例二解析:
用第一種方式創建多個”abc”字符串,在內存中 其實只存在一個對象而已。而對於String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字符串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。
示例三:
String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );
System.out.println( s0==s2 );
結果為:true true
示例四:
String s0="kvill";
String s1=new String("kvill");
String s2="kv" + new String("ill");
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
結果為:false false false
示例三和示例四解析:
因為例子中的 s0和s1中的”kvill”都是字符串常量,它們在編譯期就被確定了,所以s0==s1為true;而"kv”和"ill”也都是字符串常量,當一個字符串由多個字符串常量連接而成時,它自己肯定也是字符串常量,所以s2也同樣在編譯期就被解析為一個字符串常量,所以s2也是常量池中” kvill”的一個引用。所以我們得出s0==s1==s2;用new String() 創建的字符串不是常量,不能在編譯期就確定,所以new String() 創建的字符串不放入常量池中,它們有自己的地址空間。
小問題:String s = new String(“xyz”);產生幾個對象?
答案:1個或者2個。
解析:對於通過new產生一個字符串(假設為”xyz”)時,會先去常量池中查找是否已經有了”xyz”對象,如果沒有則在常量池中創建一個此字符串對象,然后堆中再創建一個引用常量池中此”xyz”對象的拷貝對象。
static final 和final修飾常量的區別:
static 強調只有一份,final 只是說明是一個常量,final定義的基本類型的值是不可改變的,但是final定義的引用對象的值是可以改變的。
不知道在說什么,來點干貨!!
import java.util.Random; /** * 這個例子想說明一下static final 與 final的區別 */ public class Test1 { //47作為隨機種子,為的就是產生隨機數。 private static Random rand = new Random(47); private final int A = rand.nextInt(30); private static final int B = rand.nextInt(30); public static void main(String[] args) { Test1 test1 = new Test1(); System.out.println("test1 : " + "A=" + test1.A); //5 System.out.println("test1 : " + "B=" + test1.B); //8 Test1 test2 = new Test1(); System.out.println("test2 : " + "A=" + test2.A); //13 System.out.println("test2 : " + "B=" + test2.B); //8 } }
最后講一下關於引用變量的內存分配!也是我們最為核心關注的內容!
引用類型:是指除了基本的變量類型之外的所有類型(如通過 class 定義的類型)。所有的類型在內存中都會分配一定的存儲空間(形參在使用的時候也會分配存儲空間,方法調用完成之后,這塊存儲空間自動消失), 基本的變量類型只有一塊存儲空間(分配在棧中), 而引用類型有兩塊存儲空間(一塊在棧中,一塊在堆中)。
接下來我們繼續用模型圖分析!
引用數據類型內存分配圖一:
引用數據類型內存分配圖二:
可以看到引用數據類型內存分配圖一和圖二有所不同,其實圖二只是對圖一的更深層的剖析,圖二這里存在一個句柄的概念。
這里舉兩個具體實例去作分析
實例一:
實例二:
分析:通過上面的代碼和圖形示例不難看出,a 和 b 是不同的兩個引用,我們使用了兩個定義語句來定義它們。但它們的值是一樣的,都指向同一個對象"This is a Text!"。但要注意String 對象的值本身
是不可更改的 (像 b = "World"; b = a; 這種情況不是改變了 "World" 這一對象的值,而是改變了它的引用 b 的值使之指向了另一個 String 對象 a)。
這里我描述了兩個要點:
(1) 引用是一種數據類型(保存在stack中),保存了對象在內存(heap,堆空間)中的地址,這種類型即不是我們平時所說的簡單數據類型也不是類實例(對象);
(2)不同的引用可能指向同一個對象,換句話說,一個對象可以有多個引用,即該類類型的變量。
2.java的反射機制簡析
2.1 反射機制的概念:
指在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法,對於任意一個對象,都能調用它的任意一個方法.這種動態獲取信息,以及動態調用對象方法的功能叫java語言的反射機制.
2.2 反射機制的應用:
生成動態代理,面向切片編程(在調用方法的前后各加棧幀).
2.3 反射機制的原理:
1 首先明確的概念: 一切皆對象----類也是對象.
2 然后知道類中的內容 :modifier constructor field method.
3 其次明白加載: 當Animal.class在硬盤中時,是一個文件,當載入到內存中,可以認為是一個對象,是java.lang.class的對象.
2.4 反射機制簡單實例代碼:
public class CustomService { //登錄 public String login(String name,String pwd){ if("admin".equals(name) && "123".equals(pwd)){ return “success”; } return “fail”; } //退出 public void logout(){ System.out.println("系統已安全退出!"); } } public class ReflectTest { public static void main(String[] args) throws Exception{ //1.獲取類 Class c = Class.forName("CustomerService"); //獲取某個特定的方法 //通過:方法名+形參列表 Method m = c.getDeclaredMethod("login",String.class,String.class); //通過反射機制執行login方法. Object obj = c.newInstance(); //調用obj對象的m方法,傳遞"admin""123"參數,方法的執行結果是retValue Object retValue = m.invoke(obj, "admin","123"); System.out.println(retValue); //success } }