c语言静态局部变量量存储存储在哪个区

程序运行时,有六个地方都可以保存数据:
  1、 寄存器:这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。
  2、 堆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象)。驻留于常规RAM(随机访问存储器)区域。但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些java数据要保存在堆栈里——特别是对象句柄,但java对象并不放到其中。
  3、 堆:存放用new产生的数据。一种常规用途的内存池(也在RAM区域),其中保存了java对象。和堆栈不同:“内存堆”或“堆”最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相碰的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间
  4、 静态域:存放在对象中用static定义的静态成员。这儿的“静态”是指“位于固定位置”。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但java对象本身永远都不会置入静态存储空间。
  5、 常量池:存放常量。常数值通常直接置于程序代码内部。这样做是安全的。因为它们永远都不会改变,有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。
  6、 非RAM存储:硬盘等永久存储空间。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器,而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技艺就是它们能存在于其他媒体中,一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。
Java内存分配中的栈
  在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。
  当在一段代码块定义一个变量时,Java就在栈中 为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。
  1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中   2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。   3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
Java内存分配中的堆
  堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
  在堆中产生了一个数组或对象后,还可以 在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。& 引用变量就相当于是 为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个名称。
  引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序 运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。
  1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)   2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 
  实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针!&
  Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、 anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存 大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态 分配内存,存取速度较慢。
  栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是 确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。
&&&&&& 栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
  Java代码
<span style="color: # int a = 3;
<span style="color: # int b = 3;
  编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。
  这时,如果再令 a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响 到b的值。
  要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
<span style="color: # int i1 = 9;
<span style="color: # int i2 = 9;
<span style="color: # int i3 = 9;
<span style="color: # public static final int INT1 = 9;
<span style="color: # public static final int INT2 = 9;
<span style="color: # public static final int INT3 = 9;
对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。&形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。&成员变量存储在堆中的对象里面,由垃圾回收器负责回收。&如以下代码:&Java代码&
1 class BirthDate {
private int
private int
private int
public BirthDate(int d, int m, int y) {
<span style="color: #
//省略get,set方法………
<span style="color: #
<span style="color: #
<span style="color: #
public class Test{
<span style="color: #
public static void main(String args[]){
<span style="color: #
int date = 9;
<span style="color: #
Test test = new Test();
<span style="color: #
test.change(date);
<span style="color: #
BirthDate d1= new BirthDate(7,7,1970);
<span style="color: #
<span style="color: #
public void change1(int i){
<span style="color: #
<span style="color: #
<span style="color: #
对于以上这段代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。下面分析一下代码执行时候的变化:&1. main方法开始执行:int date = 9;&date局部变量,基础类型,引用和值都存在栈中。&2. Test test = new Test();&test为对象引用,存在栈中,对象(new Test())存在堆中。&3. test.change(date);&i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。&4. BirthDate d1= new BirthDate(7,7,1970);&&d1 为对象引用,存在栈中,对象(new BirthDate())存在堆中,其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中。 day,month,year为成员变量,它们存储在堆中(new BirthDate()里面)。当BirthDate构造方法执行完之后,d,m,y将从栈中消失。5.main方法执行完之后,date变量,test,d1引用将从栈中消失,new Test(),new BirthDate()将等待垃圾回收。
 方法区:   1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。   2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
  java里面是没有静态变量这个概念的,不信你自己在方法里面定义一个static int i =0;java里只有静态成员变量。它属于类的属性。至于他放在那里?深入jvm里是是翻译为方法区的(应该也可叫静态域吧)。虚拟机的体系结构:堆,方法区,本地方法栈,pc寄存器。而方法区保存的就是一个类的模板,堆是放类的实例的。栈是一般来用来函数计算的。随便找本计算机底层的书都知道了。栈里的数据,函数执行完就不会存储了。这就是为什么局部变量每一次都是一样的。就算给他加一后,下次执行函数的时候还是原来的样子。
常量池 (constant pool)
  常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。
除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,比如:
  ◆类和接口的全限定名;
  ◆字段的名称和描述符;
  ◆方法和名称和描述符。
如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
String是一个特殊的包装类数据。可以用:
  Java代码
String str = new String("abc");
String str = "abc";
  两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。而第二种是先在栈中创建一个对 String类的对象引用变量str,然后通过符号引用去字符串常量池 里找有没有"abc",如果没有,则将"abc"存放进字符串常量池 ,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。
  比较类里面的数值是否相等时,用equals()方法;当两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
  Java代码
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
  可以看出str1和str2是指向同一个对象的。
  Java代码
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
  用new的方式是生成不同的对象。每一次生成一个。
  因此用第二种方式创建多个”abc”字符串,在内存中 其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
  另 一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。
String常量池问题的几个例子
String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );
System.out.println( s0==s2 );
分析:首先,我们要知结果为道Java 会确保一个字符串常量只有一个拷贝。
  因为例子中的 s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字 符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中” kvill”的一个引用。所以我们得出s0==s1==s2;
  Java代码
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 );
  结果为:
分析:用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
s0还是常量池 中"kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分 new String(”ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。
1 String a = "a1";
2 String b = "a" + 1;
3 System.out.println((a == b)); //result = true
5 String a = "atrue";
6 String b = "a" + "true";
7 System.out.println((a == b)); //result = true
9 String a = "a3.4";
<span style="color: # String b = "a" + 3.4;
<span style="color: # System.out.println((a == b)); //result = true
分析:JVM对于字符串常量的"+"号连接,将程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true。
String a = "ab";
String bb = "b";
String b = "a" +
System.out.println((a == b)); //result = false
分析:JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。
String a = "ab";
final String bb = "b";
String b = "a" +
System.out.println((a == b)); //result = true
分析:和[4]中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量 池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。
String a = "ab";
final String bb = getBB();
String b = "a" +
System.out.println((a == b)); //result = false
private static String getBB()
return "b";
分析:JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面 程序的结果为false。
关于String是不可变的
&&&&&& 通过上面例子可以得出得知:
  String& s& =& "a" + "b" + "c";
  就等价于String s = "abc";
  String& a& =& "a";
  String& b& =& "b";
  String& c& =& "c";
  String& s& =&& a& +& b& +&
  这个就不一样了,最终结果等于:
  Java代码
StringBuffer temp = new StringBuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();
  由上面的分析结果,可就不难推断出String 采用连接运算符(+)效率低下原因分析,形如这样的代码:
  Java代码
1 public class Test {
public static void main(String args[]) {
String s = null;
for(int i = 0; i & 100; i++) {
<span style="color: #   
<span style="color: #    }
<span style="color: # }
  每做一次 + 就产生个StringBuilder对象,然后append后就扔掉。下次循环再到达时重新产生个StringBuilder对象,然后 append 字符串,如此循环直至结束。如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用StringBuffer或StringBulider对象来进行 append操作。
  由于String类的immutable性质,这一说又要说很多,大家只 要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的”不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原 因了,因为StringBuffer是可改变的。
String中的final用法和理解
  Java代码
  final StringBuffer a = new StringBuffer("111");
  final StringBuffer b = new StringBuffer("222");
  a=b;//此句编译不通过
  final StringBuffer a = new StringBuffer("111");
  a.append("222");// 编译通过
  可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象 的变化,final是不负责的。
  栈中用来存放一些原始数据类型的局部变量数据和对象的引用(String,数组.对象等等)但不存放对象内容
  堆中存放使用new关键字创建的对象.
  字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常 量池中,而有的是运行时才被创建.使用new关键字,存放在堆中。
阅读(...) 评论()扫二维码下载作业帮
2亿+学生的选择
下载作业帮安装包
扫二维码下载作业帮
2亿+学生的选择
什么数据存放在动态存储区中这是一道选择题原题是下列哪种数据不存放在动态存储区中( )。A. 函数形参变量
B. 局部自动变量C. 函数调用时的现场保护和返回地址
D. 局部静态变量答案是C不知道为什么答案是这个?可以讲解一下吗?
扫二维码下载作业帮
2亿+学生的选择
动态申请的数据存储空间位于动态存储区对于C++是new,对于纯C语言是malloc
为您推荐:
扫描下载二维码博客访问: 269748
博文数量: 119
博客积分: 4290
博客等级: 上校
技术积分: 1321
注册时间:
努力工作,建立一个幸福的家庭。
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: WINDOWS
在C++中,内存分成4个区,他们分别是堆,栈,静态存储区和常量存储区& 1)栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存& 储区.里面的变量通常是局部变量,函数参数等.& 2)堆,又叫自由存储区,它是在程序执行的过程中动态分配的,它最大的特性就是动.& 态性.由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,& 一般一个new就要对应一个delete.如果程序员没有释放掉,那么在程序结束后,& 操作系统会自动回收.如果分配了堆对象,却忘记了释放,就会产生内存泄漏.而& 如果已释放了对象,却没有将相应的指针置为NULL,该指针就是"悬挂指针".& 4)静态存储区.所有的静态对象,全局对象都于静态存储区分配.& 5)常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改& (当然,你要通过非正当手段也可以修改,而且方法很多)& 常量字符串都存放在静态存储区,返回的是常量字符串的首地址.n是全局变量,储存在静态区.进入main函数之前就被创建.生命周期为整个源程序.m是局部变量,在栈中分配.在函数func被调用时才被创建.生命周期为函数func内.n只创建一次.m每次调用func都会创建,函数结束就销毁.
阅读(3506) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。君,已阅读到文档的结尾了呢~~
堆、栈及数据段的存储
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
C语言中局部变量和全局变量_等在内存中的存放位置
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口(1)内存分配的策略
  按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编 译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为 它们都会导致编译程序无法计算准确的存储空间需求.
 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存 储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该 程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
 静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口 处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆 由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
(2)堆和栈的比较
  上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
 从堆和栈的功能和作用来通俗的比较, 堆主要用来存放对象的,栈主要是用来执行程序的 .而这种不同又主要是由于堆和栈的特点决定的:
&& 在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶 向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程 序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
&& 堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的 优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面 向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花 掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).
(3)JVM中的堆和栈
  JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
& 我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译 原理中的活动纪录的概念是差不多的.
& 从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
&& 每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程 共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也 就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
static、final修饰符、内部类和Java内存分配
static修饰符
&&&&&&& static修饰符能够与属性、方法和内部类一起使用,表示静态的。类中的静态变量和静态方法能够与类名一起使用,不需要创建一个类的对象来访问该类的静态成员,所以,static修饰的变量又称作&类变量&。
static属性的内存分配
&&&&&&&& 一个类中,一个static变量只会有一个内存空间,虽然有多个类实例,但这些类实例中的这个static变量会共享同一个内存空间。
static的变量是在类装载的时候就会被初始化,即,只要类被装载,不管是否使用了static变量,都会被初始化。
static的基本规则
& &一个类的静态方法只能访问静态属性
& &一个类的静态方法不能直接调用非静态方法
& &如访问控制权限允许,static属性和方法可以使用类名加&.&的方式调用,也可以使用实例加&.&的方式调用
& &静态方法中不存在当前对象,因而不能使用this,也不能使用super
& &静态方法不能被非静态方法覆盖
& &构造方法不允许声明为static的
& 注,非静态变量只限于实例,并只能通过实例引用被访问。
静态初始器&&静态块
& 静态初始器是一个存在与类中方法外面的静态块,仅仅在类装载的时候执行一次,通常用来初始化静态的类属性。
final修饰符
& 在Java声明类、属性和方法时,可以使用关键字final来修饰,final所标记的成分具有终态的特征,表示最终的意思。
& final的具体规则
&&& &final标记的类不能被继承
&&& &final标记的方法不能被子类重写
&&& &final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次
&&& &final标记的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用
&&& &final标记的局部变量可以只声明不赋值,然后再进行一次性的赋值
&&& &final一般用于标记那些通用性的功能、实现方式或取值不能随意被改变的成分,以避免被误用
& 如果将引用类型(即,任何类的类型)的变量标记为final,那么,该变量不能指向任何其它对象,但可以改变对象的内容,因为只有引用本身是final的。
& 在一个类(或方法、语句块)的内部定义另一个类,后者称为内部类,有时也称为嵌套类。
& 内部类的特点
&&& &内部类可以体现逻辑上的从属关系,同时对于其它类可以控制内部类对外不可见等
&&& &外部类的成员变量作用域是整个外部类,包括内部类,但外部类不能访问内部类的private成员
&&& &逻辑上相关的类可以在一起,可以有效地实现信息隐藏
&&& &内部类可以直接访问外部类的成员,可以用此实现多继承
&&& &编译后,内部类也被编译为单独的类,名称为outclass$inclass的形式
内部类可以分为四种
&&& &类级:成员式,有static修饰
&&& &对象级:成员式,普通,无static修饰
&&& &本地内部类:局部式
&&& &匿名级:局部式
& 成员式内部类的基本规则
&&& &可以有各种修饰符,可以用4种权限、static、final、abstract定义
&&& &若有static限定,就为类级,否则为对象级。类级可以通过外部类直接访问,对象级需要先生成外部的对象后才能访问
&&& &内外部类不能同名
&&& &非静态内部类中不能声明任何static成员
&&& &内部类可以互相调用
& 成员式内部类的访问
&&& 内部类访问外层类对象的成员时,语法为:
&&&&& 外层类名.this.属性
&&& 使用内部类时,由外部类对象加&.new&操作符调用内部类的构造方法,创建内部类的对象。
& 在另一个外部类中使用非静态内部类中定义的方法时,要先创建外部类的对象,再创建与外部类相关的内部类的对象,再调用内部类的方法。
& static内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建。
& 由于内部类可以直接访问其外部类的成分,因此,当内部类与其外部类中存在同名属性或方法时,也将导致命名冲突。所以,在多层调用时要指明。
& 本地类是定义在代码块中的类,只在定义它们的代码块中可见。
& 本地类有以下几个重要特性:
&&& &仅在定义了它们的代码块中可见
&&& &可以使用定义它们的代码块中的任何本地final变量(注:本地类(也可以是局部内部类/匿名内部类等等)使用外部类的变量,原意是希
望这个变量在本地类中的对象和在外部类中的这个变量对象是一致的,但如果这个变量不是final定义,它有可能在外部被修改,从而导致内外部类的变量对象
状态不一致,因此,这类变量必须在外部类中加final前缀定义)
&&& &本地类不可以是static的,里边也不能定义static成员
&&& &本地类不可以用public、private、protected修饰,只能使用缺省的
&&& &本地类可以是abstract的
匿名内部类是本地内部类的一种特殊形式,即,没有类名的内部类,而且具体的类实现会写在这个内部类里。
& 匿名类的规则
&&& &匿名类没有构造方法
&&& &匿名类不能定义静态的成员
&&& &匿名类不能用4种权限、static、final、abstract修饰
&&& &只可以创建一个匿名类实例
Java的内存分配
& Java程序运行时的内存结构分成:方法区、栈内存、堆内存、本地方法栈几种。
& 方法区存放装载的类数据信息,包括:
&&& &基本信息:每个类的全限定名、每个类的直接超类的全限定名、该类是类还是接口、该类型的访问修饰符、直接超接口的全限定名的有序列表。
&&& &每个已装载类的详细信息:运行时常量池、字段信息、方法信息、静态变量、到类classloader的引用、到类class的引用。
&&& Java栈内存由局部变量区、操作数栈、帧数据区组成,以帧的形式存放本地方法的调用状态(包括方法调用的参数、局部变量、中间结果&&)。
&&& 堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
& 本地方法栈内存
&&& Java通过Java本地接口JNI(Java Native Interface)来调用其它语言编写的程序,在Java里面用native修饰符来描述一个方法是本地方法。
& String的内存分配
&&& String是一个特殊的包装类数据,由于String类的值不可变性,当String变量需要经常变换其值时,应该考虑使用StringBuffer或StringBuilder类,以提高程序效率。
Java内存分配、管理小结&
然后是运用层面:
引用类型变量和对象的区别?
什么情况下用局部变量,什么情况下用成员变量?
数组如何初始化?声明一个数组的过程中,如何分配内存?
声明基本类型数组和声明引用类型的数组,初始化时,内存分配机制有什么区?
在什么情况下,我们的方法设计为静态化,为什么
Java中运行时内存结构
&& 1.1 方法区:
方法区是系统分配的一个内存逻辑区域,是JVM在装载类文件时,用于存储类型信息的(类的描述信息)。
方法区存放的信息包括:
&&&&&&&&&&& 1.1.1类的基本信息:
每个类的全限定名
每个类的直接超类的全限定名(可约束类型转换)
该类是类还是接口
该类型的访问修饰符
直接超接口的全限定名的有序列表
&&&&&&&&&&&& 1.1.2已装载类的详细信息:
运行时常量池:
在方法区中,每个类型都对应一个常量池,存放该类型所用到的所有常量,常量池中存储了诸如文字字符串、final变量值、类名和方法名常量。它们以数组形式通过索引被访问,是外部调用与类联系及类型对象化的桥梁。(存的可能是个普通的字符串,然后经过常量池解析,则变成指向某个类的引用)
字段信息:
字段信息存放类中声明的每一个字段的信息,包括字段的名、类型、修饰符。
字段名称指的是类或接口的实例变量或类变量,字段的描述符是一个指示字段的类型的字符串,如private A a=则a为字段名,A为描述符,private为修饰符
方法信息:
类中声明的每一个方法的信息,包括方法名、返回值类型、参数类型、修饰符、异常、方法的字节码。
(在编译的时候,就已经将方法的局部变量、操作数栈大小等确定并存放在字节码中,在装载的时候,随着类一起装入方法区。)
在运行时,JVM从常量池中获得符号引用,然后在运行时解析成引用项的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。
静态变量:
这个没什么好说的,就是类变量,类的所有实例都共享,我们只需知道,在方法区有个静态区,静态区专门存放静态变量和静态块。
到类classloader的引用:到该类的类装载器的引用。
到类class的引用:虚拟机为每一个被装载的类型创建一个class实例,用来代表这个被装载的类。
由此我们可以知道反射的基础:
在装载类的时候,加入方法区中的所有信息,最后都会形成Class类的实例,代表这个被装载的类。方法区中的所有的信息,都是可以通过这个Class类对象反射得到。我们知道对象是类的实例,类是相同结构的对象的一种抽象。同类的各个对象之间,其实是拥有相同的结构(属性),拥有相同的功能(方法),各个对象的区别只在于属性值的不同。
&&& 同样的,我们所有的类,其实都是Class类的实例,他们都拥有相同的结构-----Field数组、Method数组。而各个类中的属性都是Field属性的一个具体属性值,方法都是Method属性的一个具体属性值。
在运行时,JVM从常量池中获得符号引用,然后在运行时解析成引用项的实际地址,最后通过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。
1.2 Java栈
JVM栈是程序运行时单位,决定了程序如何执行,或者说数据如何处理。
在Java中,一个线程就会有一个线程的JVM栈与之对应,因为不过的线程执行逻辑显然不同,因此都需要一个独立的JVM栈来存放该线程的执行逻辑。
对方法的调用:
&&&&&&&&&&& Java栈内存,以帧的形式存放本地方法的调用状态,包括方法调用的参数、局部变量、中间结果等(方法都是以方法帧的形式存放在方法区的),每调用一个方法就将对应该方法的方法帧压入Java栈,成为当前方法帧。当调用结束(返回)时,就弹出该帧。
这意味着:
&&&&&&&&&&& 在方法中定义的一些基本类型的变量和引用变量都在方法的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后(方法执行完成后),Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作它用。--------同时,因为变量被释放,该变量对应的对象,也就失去了引用,也就变成了可以被gc对象回收的垃圾。
因此我们可以知道成员变量与局部变量的区别:
局部变量,在方法内部声明,当该方法运行完时,内存即被释放。
成员变量,只要该对象还在,哪怕某一个方法运行完了,还是存在。
从系统的角度来说,声明局部变量有利于内存空间的更高效利用(方法运行完即回收)。
成员变量可用于各个方法间进行数据共享。
Java 栈内存的组成:
局部变量区、操作数栈、帧数据区组成。
(1):局部变量区为一个以字为单位的数组,每个数组元素对应一个局部变量的
值。调用方法时,将方法的局部变量组成一个数组,通过索引来访问。若为非静态方法,则加入一个隐含的引用参数this,该参数指向调用这个方法的对象。而
静态方法则没有this参数。因此,对象无法调用静态方法。
由此,我们可以知道,方法什么时候设计为静态,什么时候为非静态?
前面已经说过,对象是类的一个实例,各个对象结构相同,只是属性不同。
而静态方法是对象无法调用的。
所以,静态方法适合那些工具类中的工具方法,这些类只是用来实现一些功能,也不需要产生对象,通过设置对象的属性来得到各个不同的个体。
(2):操作数栈也是一个数组,但是通过栈操作来访问。所谓操作数是那些被指令操作的数据。当需要对参数操作时如a=b+c,就将即将被操作的参数压栈,如将b 和c 压栈,然后由操作指令将它们弹出,并执行操作。虚拟机将操作数栈作为工作区。
(3):帧数据区处理常量池解析,异常处理等
1.3 java堆
&&&&& java的堆是一个运行时的数据区,用来存储数据的单元,存放通过new关键字新建的对象和数组,对象从中分配内存。
&&&&& 在堆中声明的对象,是不能直接访问的,必须通过在栈中声明的指向该引用的变量来调用。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
&&& 由此我们可以知道,引用类型变量和对象的区别:
声明的对象是在堆内存中初始化的, 真正用来存储数据的。不能直接访问。
引用类型变量是保存在栈当中的,一个用来引用堆中对象的符号而已(指针)。
堆与栈的比较:
JAVA堆与栈都是用来存放数据的,那么他们之间到底有什么差异呢?既然栈也能存放数据,为什么还要设计堆呢?
1.从存放数据的角度:
&&&&& 前面我们已经说明:
&&&&& 栈中存放的是基本类型的变量or引用类型的变量
&&&&&& 堆中存放的是对象or数组对象.
&&&&&& 在栈中,引用变量的大小为32位,基本类型为1-8个字节。
&&&&&& 但是对象的大小和数组的大小是动态的,这也决定了堆中数据的动态性,因为它是在运行时动态分配内存的,生存期也不必在编译时确定,Java 的垃圾收集器会自动收走这些不再使用的数据。
2.从数据共享的角度:
&&& 1).在单个线程类,栈中的数据可共享
&&& 例如我们定义:
int a=3; int b=3;
译器先处理int a = 3;首先它会在栈中创建一个变量为a 的引用,然后查找栈中是否有3 这个值,如果没找到,就将3 存放进来,然后将a
指向3。接着处理int b = 3;在创建完b 的引用变量后,因为在栈中已经有3这个值,便将b 直接指向3。这样,就出现了a 与b
同时均指向3的情况。
&&& 而如果我们定义:
Integer a=new Integer(3);//(1)&
Integer b=new Integer(3);//(2)&
Integer a=new Integer(3);//(1) Integer b=new Integer(3);//(2)
&& 这个时候执行过程为:在执行(1)时,首先在栈中创建一个变量a,然后在堆内存中实例化一个对象,并且将变量a指向这个实例化的对象。在执行(2)时,过程类似,此时,在堆内存中,会有两个Integer类型的对象。
&&& 2).在进程的各个线程之间,数据的共享通过堆来实现
&&&&&&& 例:那么,在多线程开发中,我们的数据共享又是怎么实现的呢?
& 如图所示,堆中的数据是所有线程栈所共享的,我们可以通过参数传递,将一个堆中的数据传入各个栈的工作内存中,从而实现多个线程间的数据共享
(多个进程间的数据共享则需要通过网络传输了。)
3.从程序设计的的角度:
从软件设计的角度看,JVM栈代表了处理逻辑,而JVM堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。
4.值传递和引用传递的真相
有了以上关于栈和堆的种种了解后,我们很容易就可以知道值传递和引用传递的真相:
1.程序运行永远都是在JVM栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。
但是传引用的错觉是如何造成的呢?
在运行JVM栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为&传引用值&的传值调用,即引用的处理跟基本类型是完全一样的。
但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到JVM堆中的对象,这个时候才对应到真正的对象。
如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了。
从某种意义上来说对象都是由基本类型组成的。
可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。
其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。
面向对象的引入,只是改变了我们对待问题的思考方式,而更接近于自然方式的思考。
当我们把对象拆开,其实对象的属性就是数据,存放在JVM堆中;而对象的行为(方法),就是运行逻辑,放在JVM栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。
阅读(...) 评论()

我要回帖

更多关于 静态局部变量存储在 的文章

 

随机推荐