之後會接着補充javascript
說到域,假如給小學生中學生或者沒有學過編程語言的人理解,更好說明其本質,就是做用範圍,而這也就是上下文的意思,無須去理解的那麼抽象。接下來就從函數講起吧。java
這裏拿js的閉包來講,不解釋那麼多,貼幾段代碼的 變量的做用域無非就是兩種:全局變量和局部變量。
Javascript語言的特殊之處,就在於函數內部能夠直接讀取全局變量。c++
Js代碼編程
var n=999; function f1(){ alert(n); } f1(); // 999
另外一方面,在函數外部天然沒法讀取函數內的局部變量。
Js代碼c#
function f1(){ var n=999; } alert(n); // error
這裏有一個地方須要注意,函數內部聲明變量的時候,必定要使用var命令。若是不用的話,你實際上聲明瞭一個全局變量!
Js代碼tomcat
function f1(){ n=999; } f1(); alert(n); // 999
2、如何從外部讀取局部變量?
出於種種緣由,咱們有時候須要獲得函數內的局部變量。可是,前面已經說過了,正常狀況下,這是辦不到的,只有經過變通方法才能實現。
那就是在函數的內部,再定義一個函數。
Js代碼閉包
function f1(){ n=999; function f2(){ alert(n); // 999 } }
在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的全部局部變量,對f2都是可見的。可是反過來就不行,f2內部的局部變量,對f1 就是不可見的。這就是Javascript語言特有的「鏈式做用域」結構(chain scope),
子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。
既然f2能夠讀取f1中的局部變量,那麼只要把f2做爲返回值,咱們不就能夠在f1外部讀取它的內部變量了嗎!app
Js代碼jvm
function f1(){ n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999
==上面例子中的f2函數,就是閉包,其實就是兩個容器,大的套個小的,有點相似套娃,再往大天然延伸一點: f1函數就是河,而f2函數就是河裏的魚,魚的新陳代謝的產物就是其返回的結果,一樣是否是相似於咱們和空氣的關係,吸進去的是氧氣,返回的結果的是二氧化碳==編程語言
一樣能夠聯繫下Java中的類的函數,是否是也是一個道理,這就是函數的上下文
接着由這個能夠聯想到代碼中的逃逸分析,其實本就不是什麼高深的東西,和現實聯想下一個本應被銷燬的東西沒有被處理掉,會形成多大的浪費,好比地雷,戰時有很大做用,和平時期,假如在居民區範圍,那就有很大的問題;又好比犯人,一個越了域(用這個字以爲更形象)的恐怖分子,會形成多大的恐慌和社會問題,因此說逃逸分析,分析的就是做用域
先拿Java來講,逃逸常見分三種狀況:
給全局變量賦值,發生逃逸;
方法的返回值,會發生逃逸
實例的引用傳遞,也會發生逃逸
這裏又引出了一個優化的問題,Java對象老是在堆中分配的,所以,Java對象的建立和回收對系統的開銷也是很大的,而Java被詬病的一個地方認爲其性能慢的一個緣由就是Java不支持運行時棧分配對象,沒有想c++裏面的struct結構或者是c#裏的值對象。jdk6中的swing呢剛纔和性能消耗的瓶頸就是因爲發生逃逸形成的,棧內置保存了對象的指針,當對象再也不被引用後,須要依靠GC來遍歷引用書並回收內存,若是對象的數量比較多,就會給GC帶來比較大的壓力,也就間接影響了應用的性能,因此,減小臨時對象在堆內分配的數量,無疑是最有效的優化方法。
再次回到域這個話題,在Java應用裏咱們很容易想到,通常在方法體內,咱們聲明瞭一個局部變量,且該變量的方法執行生命週期內未發生逃逸,由於在方法體內未將引用暴露給外面,按照JVM內存分配機制,首先會在堆裏new出變量類的實例,而後將返回的對象指針壓入調用棧,再繼續執行,這是jvm未優化以前的走方式。
經過逃逸分析,咱們能夠對jvm的這個過程進行優化,即對棧的從新分配,首先須要分析並找到這期間爲發生逃逸的變量,將變量類的實例化直接在棧裏發生,即不須要通過堆,分配完成後,繼續在棧內執行,最後線程結束,棧空間被回收,一樣,局部變量也會跟着棧消失,優化後和優化前的區別就是減小了臨時對象在堆內的分配數量(即未逃逸的對象無須堆內建立)。
逃逸分析不能在靜態編譯時進行,必須在JIT裏完成,由於咱們在正常的代碼過程當中運行時會經過動態代理改變一個類的行爲,這時就沒法得志類已經變化。
舉個例子,好比一個方法的返回值是void,方法內部有對象的建立,如:
public void use_a() { A a=new A(); //a.xx(); ... a=null }
從上面代碼能夠看出,a這個局部對象,沒有返回,沒有賦值全局變量等操做,因此,其是沒有發生逃逸的,因爲整個生命週期都在一個方法體內,這樣的對象就能夠在運行時棧進行分配和銷燬。
JIT在編譯時,假如能分析出這種代碼,那麼非逃逸的對象的建立和回收就能夠在棧上進行,從而大大提升Java的運行性能。
每每咱們會碰到這個狀況,就是方法內調用另外一個方法,有點相似js的閉包了吧,這時就要進行內聯分析,由於每每一些對象在被調用過程當中建立並返回給調用,好比上面的a.xx(),假若有返回值賦值給另外的局部變量的過程(這下應該好理解這個概念了吧),在調用過程當中使用完就被銷燬回收了,其實都清楚程序的執行過程是自上而下的,其實函數的調用也無非是把一段代碼嵌入到另外一段代碼裏順序執行處理而已,說白了兩個方法內聯成一個方法體,而這個也是咱們日常方法重構的一個過程,這種原來經過返回傳遞的對象就變成了方法內的局部對象,也就變成了非逃逸對象了,這樣,這些對象就能夠在同一個棧上進行分配了。
Java7已經開始支持對象的棧分配和逃逸分析,這樣除了上述的優化外還會帶來 同步消除和矢量替代,關於這兩個能夠查閱相應資料
這裏對方法逃逸再貼兩段代碼,方便你們加深認識:
public static StringBuffer craeteStringBuffer(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb; }
StringBuffer sb是一個方法內部變量,上述代碼中直接將sb返回,這樣這個StringBuffer有可能被其餘方法所改變,這樣它的做用域就不僅是在方法內部,雖然它是一個局部變量,稱其逃逸到了方法外部。
甚至還有可能被外部線程訪問到,譬如賦值給類變量或能夠在其餘線程中訪問的實例變量,稱爲線程逃逸。
上述代碼若是想要StringBuffer sb不逃出方法,能夠這樣寫:
public static String createStringBuffer(String s1, String s2) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); return sb.toString(); }
不直接返回 StringBuffer,那麼StringBuffer將不會逃逸出方法。
一樣,咱們分析下js中閉包在此的行爲:
== 最大用處有兩個,一個是前面提到的能夠讀取函數內部的變量,另外一個就是讓這些變量的值始終保持在內存中==。
Js代碼
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證實了,函數f1中的局部變量n一直保存在內存中,並無在f1調用後被自動清除。
爲何會這樣呢?緣由就在於f1是f2的父函數,==而f2被賦給了一個全局變量,這致使f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。==
這段代碼中另外一個值得注意的地方,就是「nAdd=function(){n+=1}」這一行,==首先在nAdd前面沒有使用var關鍵字,所以 nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個
匿名函數自己也是一個閉包,因此nAdd至關因而一個setter,能夠在函數外部對函數內部的局部變量進行操做。==
==因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。==
暫時先到這裏吧,想到的比較多,好比tomcat容器,SpringMVC和Spring的父子容器,solrhome和solrcore之間的關係等等吧,有空接着整理