之后会接着补充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之间的关系等等吧,有空接着整理