java的堆,栈,静态数据区(常量区)详解版权声明
原创在准备面试时,我突然看到一个关于java在数据存储问题上,我收集了一些信息,感觉很好。我分享给你,并转载自: Java右侧,堆栈,静态代码详细信息。
一:在JAVA有六个不同的地方可以存储数据:
- 寄存器(register)。 这是最快的存储区域域,因为它位于与处理器内部其他存储区域不同的位置。然而,寄存器的数量非常有限,因此寄存器由编译器按需分配。你不能直接控制它,也感觉不到程序中有任何注册的迹象。
------最快的存储区域, 由编译器按需分配,我们无法控制程序.
- 栈(stack). 位于一般位置RAM但通过它的“堆栈指针”,您可以从处理器获得支持。如果堆栈指针向下移动,则分配新内存; 记忆力这是一种快速高效的存储分配方法,仅次于寄存器。当创建程序时,JAVA编译器必须知道堆栈中存储的所有数据的确切大小和生命周期,因为它必须生成。 上下移动堆栈指针的相应代码。这种限制限制了程序的灵活性JAVA数据存储在堆栈中,尤其是对象引用,。JAVA对象不存储其 中。
------存储基本类型的变量数据和对象、数组引用,但对象本身不存储在堆栈中,而是存储在堆中(new 对象外)或常量池(字符串常量对象存储在常量池中)
- 堆(heap). 通用内存池(也存在于RAM在)中,用于存储。JAVA对象堆的优点不同于堆栈:编译器不需要知道从堆中分配多少存储区域。 域,您不必知道存储的数据在堆中存活多长时间。因此,在堆中分配存储具有很大的灵活性。当您需要创建对象时,只需new执行时编写一行简单的代码。 这行代码自动分配堆中的存储。当然,必须为这种灵活性支付相应的代码。使用堆的存储分配比使用堆栈的存储花费更多的时间。
------存放所有new离开对象。
- 静态存储(static storage). 此处的“静态”是指“在固定位置”。静态存储存储程序运行时始终存在的数据。您可以使用关键字static为了识别对象的特定元素是静态的,JAVA对象本身从不存储在静态存储空间中。
------存储静态成员(static定义的)
- 恒定存储(constant storage). 常量值通常直接存储在程序代码中,这是安全的,因为它们永远不会更改。有时,在嵌入式系统中,常量本身与其他部分分离,因此在这种情况下,您可以选择将其放入。ROM中
------存储字符串常量和基本类型常量(public static final)
- 非RAM存储如果数据完全存在于程序之外,则它可能不受程序的控制,并且可能在程序未运行时存在。
------永久存储空间,如硬盘
就速度而言,存在以下关系:
寄存器 < 栈 < 堆 < 其他
这里我们主要关注堆栈、堆和常量池,它们可以为堆栈和常量池中的对象共享,而不是为堆中的对象。可以确定堆栈中数据的大小和生命周期,当没有对数据的引用时,数据就会消失。堆中的对象由垃圾收集器收集,因此不需要确定大小和生命周期,并且具有很大的灵活性。
对于字符串:对其对象的引用存储在堆栈中,如果它们是在编译时创建的。(直接用双引号定义)如果是运行期,则存储在常量池中(newOut)存储在堆中。对于equals相等的字符串,在常量池中总是只有一个,在堆中总是有多个。
如以下代码:
Java代码
String s1 = "china";
String s2 = "china";
String s3 = "china";
String ss1 = new String("china");
String ss2 = new String("china");
String ss3 = new String("china");
对于通过 new 生成字符串(假设 ”china” ),将首先前往常量池,以确定是否已经存在 ”china” 对象,如果不是,请在常量池中创建此字符串对象之一,然后在堆中创建另一个常量池。 ”china” 对象的复制对象。这也是一个问题: String s = new String(“xyz”); 生成了多少个对象?如果常量池最初不是 ”xyz”, 只有两个。
对于基础类型的变量和常量:变量和引用存储在堆栈中,常量存储在常量池中。
如以下代码:
Java代码
int i1 = 9;
int i2 = 9;
int i3 = 9;
public static final int INT1 = 9;
public static final int INT2 = 9;
public static final int INT3 = 9;
对于成员变量和局部变量:成员变量是在方法外部和类内部定义的变量;局部变量是在方法或语句块中定义的变量。必须初始化局部变量。
形式参数是局部变量,局部变量的数据存在于堆栈内存中。堆栈内存中的局部变量随着方法的消失而消失。
成员变量存储在堆中的对象中,并由垃圾收集器收集。
如以下代码:
Java代码
class BirthDate {
private int day;
private int month;
private int year;
public BirthDate(int d, int m, int y) {
day = d;
month = m;
year = y;
}
省略get,set方法
}
public class Test{
public static void main(String args[]){
int date = 9;
Test test = new Test();
test.change(date);
BirthDate d1= new BirthDate(7,7,1970);
}
public void change1(int i){
i = 1234;
}
对于上述代码,date是局部变量,i,d,m,y都是形参是局部变量,day,month,year是成员变量。以下是代码执行中的变化分析:
- main方法开始执行:int date = 9;
date局部变量、基础类型、引用和值都存在于堆栈中。
- Test test = new Test();
test是对象引用,存在于堆栈中,对象(new Test())堆中存在。
- test.change(date);
i是局部变量,引用和值存在栈中。当方法change在执行完成之后,i将从堆栈中消失。
- 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()将等待垃圾收集。
-
栈(stack)与堆(heap)都是Java用来在Ram存储数据的地方。具有C++不同,Java自动管理堆栈和堆,程序员不能直接设置堆栈或堆。
-
堆栈的优点是访问比堆快,仅次于直接定位。CPU在中注册。然而,缺点是必须确定堆栈中的数据大小和生存期,并且缺乏灵活性。此外,可以共享堆栈数据。 有关详细信息,请参见编号。3指向堆的优点是可以动态地分配内存大小,并且不必事先将生存期告知编译器,Java垃圾收集器将自动删除不再使用的数据。但缺点是,由于需要 在运行时动态分配内存,访问速度较慢。
-
Java中有两种类型的数据。
一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(请注意,没有string基本类型)。此类型定义为int a = 3; long b = 255L;以自动变量的形式定义。值得注意的是,自动变量存储的是文字值,而不是类的实例,也就是说,它们不是对类的引用,这里没有类。这样的int a = 3; 这里的a是一个指向int对类型的引用,指向3此文字值。由于大小和生存期,这些文字值的数据是已知的。(这些文字值是固定的,并在块中定义,然后块退出。 之后,字段值消失),为了速度,存在于堆栈中。
此外,堆栈有一个非常重要的特殊性,即堆栈中的数据可以共享。假设我们定义两者
int a = 3;
int b = 3;
编 首先处理翻译int a = 3; 首先,它在堆栈中创建一个变量a引用,然后找出是否存在文字值3地址没有找到,只是打开了一个存储器。3此文字值的地址,然后a指向3住址然后 理int b = 3; 创建后b在引用变量之后,因为堆栈中已经有。3此文字值将为b直接指向3住址这样a与b两个点3的情况。
特 不要注意,对这个文本值的引用不同于对类对对象的引用。假设对两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了该对象的内部状态,那么另一个是 类似的参考变量也立即反映了这种变化。相反,通过引用文字值修改其值不会导致对该文字值的另一个引用也发生变化。如上面的示例所示,我们定义了a与 b在的值之后a=4;那么,b不会等于4,还是相等3。在编译器内部,遇到。a=4,它将重新搜索堆栈是否存在。4如果没有,请再次打开地址存储。 放4的值;如果已经存在,直接。a指向此地址。因此a值的变化不影响b的值。
另一种是类似包装器的数据,例如Integer, String, Double类包装相应的基本数据类型。这些类数据都存在于堆中,Java用new()语句向编译器显示,它只在运行时根据需要动态创建,因此更灵活,但缺点是需要更多时间。
示例如下:
Java代码
public class Test {
public static void main(String[] args)
{ int a1=1;
int b1=1;
int c1=2;
int d1=a1+b1;
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(a1==b1); //true 结果1
System.out.println(c1==d1); //true 结果2
System.out.println(c==d); //true 结果3
System.out.println(e==f); //false 结果4
}
}
分析:结果1:a1==b1如上所述,会在栈 打开存储空间以存储数据。
结果2:首先它将在堆栈中。 在中创建变量c1引用,然后找出是否存在文字值2地址没有找到,只是打开了一个存储器。2此文字值的地址,然后c1指向2的地址,d1对于两个文字值的总和也是2, 因为已经有2此文字值将为d1直接指向2住址这样c1与d1两个点3的情况。
在分析以下结果之前,让我们先Java自动开箱和包装完成:在自动包装期间int变成Integer当你是int的值在-128-IntegerCache.high(127) ,回报不是新的new出来的Integer对象,但该对象已缓存在堆中。 中的Integer对象,(我们可以理解,系统已将-128到127之 间的Integer缓存到Integer如果你想放一个数组,那么数组就行了。int变成一个Integer对象,首先到缓存中查找,然后直接找到它 好吧,没必要是新的newA) ,如果不是-128-IntegerCache.high(127) 返回新的new出来的Integer对象。
结果3:由于3它在范围内,所以它从缓存中获取数据,c和d指向同一个对象,结果是true;
结果4:由于321不在范围内,所以不是从缓存中,而是单独的。new对象,e和f并没有指向同一个对象,结果是false;
- String是一种特殊的包装类数据。也就是说,它可以使用String str = new String("abc");表单创建,也可以使用。String str = "abc"; 要创建的表单(相比之下,在JDK 5.0以前,你从未见过Integer i = 3;因为类和文字值不是通用的,。String。而在JDK 5.0这个表达式是可能的!因为编译器在后台。Integer i = new Integer(3)的转换)前者是创建规范类的过程,即e。Java在中,所有的东西都是一个对象,对象是一个类的实例。new()要创建的窗体。Java 中的某些类,例如DateFormat类,它可以通过类的getInstance()返回新创建的类的方法似乎违反了这一原则。事实上,事实并非如此。此类使用单个 示例模式返回类的一个实例,但该实例是在类内部传递的。new()要创建,和getInstance()这个细节是隐藏在外面的。那为什么String str = "abc"; 在,没有通过new()创建实例是否违反上述原则?实际上不是。
4(1)String str = "abc"创建对象的过程。
1 首先查看常量池中的内容"abc"字符串对象
2 如果它不存在,则在常量池中创建。"abc",并让str对对象的引用
3 如果存在,直接让它str对对象的引用
至 于"abc"如何以及在何处保存?常量池是类信息的一部分,反映在JVM记忆模型对应于存在JVM内存模型的方法区域,即该类信息。 方法区域中存在常量池的概念,而方法区域中JVM创建内存模型中的堆。JVM为了分配,所以"abc"可以说,它存在于堆(和一些数据)中,以便放置方法区域。 堆区别于JVM堆,方法区域称为堆栈)。通常,在这种情况下,"abc"它在编译时写入字节码,因此class当加载时,JVM就为"abc"在常量池中 分配内存,所以它与静态区域大致相同。
4(2)String str = new String("abc")创建实例的过程
1 首先在堆中创建一个指定的对象(不是常量池)。"abc",并让str参考指向对象。
2 在字符串常量池中查看是否有内容。"abc"字符串对象
3 如果存在,将new生成的字符串对象与字符串常量池中的对象相关联。
4 如果不存在,请在字符串常量池中创建内容。"abc"并将堆中的对象与它们关联起来。
intern 该方法可以返回对常量池中字符串对象的引用,这可以通过以下代码简单地进行测试。
Java代码
class StringTest {
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc").intern();
System.out.println(str1==str2);
}
}
最初为空的字符串池,由类组成。 String 私人维护。
当调用 intern 方法,如果池已包含等于此的。 String 对象()的字符串。 equals(Object) 方法OK),则返回池中的字符串。否则 String 对象添加到池中并返回 String 对对象的引用。
它遵循以下规则:对于任意两个字符串 s 和 t ,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true 。
所以String str1 = "abc",str1指常量池(方法区域)的对象,而String str2 = new String("abc"),str2它引用堆中的对象,因此内存地址不同,但内容相同,因此==为false,而equals是true
4(3)String str1 = "abc"; String str2 = "ab" + "c"; str1==str2是ture
是因为String str2 = "ab" + "c"当有内容时,将找到常量池。"abc"String对象,如果存在,则直接let。str2对对象的引用,显然String str1 = "abc"如上所述,当它将在常量池中创建时。"abc"对象,因此str1对对象的引用,str2也对对象的引用,所以str1==str2
4(4)String str1 = "abc"; String str2 = "ab"; String str3 = str2 + "c"; str1==str3是false
是因为String str3 = str2 + "c"涉及添加变量(不是所有常量),因此会生成一个新对象,它的内部实现是第一个。new一个StringBuilder,然后 append(str2),append("c");然后让str3引用toString()返回的对象
如果你想了解更多细节,你可以自己检查反编译代码,并检查反编译的代码是否可以使用javap,即
javap -c -verbose 要查看的类文件(.class不要)
例如,上面的代码示例。
javac StringTest.java //编译
javap -c -verbose StringTest //反编译
4(5)String str1 = "abc"; String str2 = "abc"; System.out.println(str1==str2); //true
请注意,我们在这里不使用它。str1.equals(str2); 因为这会比较两个字符串的值是否相等。==号,根据JDK只有当两个引用都指向同一对象时,才会返回真值。我们现在看到的是,str1与str2它们是否都指向同一对象。
结果表明,JVM创建了两个引用str1和str2,但只创建了一个对象,两个引用都指向此对象。
4(6)String str1 = "abc";String str2 = "abc";str1 = "bcd";System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false
这意味着赋值的改变导致类对象引用的改变,str1指向另一个新对象!和str2仍然指向原始对象。在上面的例子中str1的值改为"bcd"时,JVM发现在 如果常量池中没有保存该值的地址,则打开该地址并创建一个新对象,该对象的字符串值指向该地址。
事 实上,String该类被设计为不可变的。(immutable)班如果要更改其值,可以,。JVM在运行时根据新值悄悄创建一个新对象,然后执行此操作。 对象的地址将返回到原始类的引用。虽然这个创建过程是完全自动的,但毕竟需要更多的时间。在对时间要求敏感的环境中,会产生一些不利影响。
4(7)String str1 = "abc"; String str2 = "abc";str1 = "bcd";
String str3 = str1;
System.out.println(str3); //bcd
String str4 = "bcd";
System.out.println(str1 == str4); //true
str3 对该对象的引用直接指向str1指向的对象(注意,str3未创建新对象)。当str1更改其值后,创建另一个String的引用str4,并指向 因str1修改该值以创建新对象。可以发现,这次str4没有创建新对象,因此再次共享堆栈中的数据。
4(8)让我们继续看下面的代码。
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false
创建了两个引用。创建了两个对象。这两个参照指向两个不同的对象。
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); //false
创建了两个引用。创建了两个对象。这两个参照指向两个不同的对象。
只要使用上述两个代码,就会对其进行描述。new()要创建新对象,它们将在堆中创建,并且它们的字符串将单独存储。即使它们与堆栈中的数据相同,也不会与堆栈中数据共享。
-
无法修改数据类型包装类的值。不仅如此String无法修改类的值,并且所有数据类型包装类都无法更改其内部值。
-
结论和建议:
(1) 我们使用这样的String str = "abc"; 当以的格式定义类时,总是假定我们创建了String类的对象str。担心陷阱!对象可能尚未创建!唯一确定的是 String已创建对类的引用。至于这个引用是否指向一个新对象,除非您通过,否则必须根据上下文来考虑。new()方法创建新对象。原因 更准确地说,这是我们创建了一个点。String类的对象的引用变量。str,此对象引用变量指向一个值"abc"的String班清楚地认识到 在排除程序中很难发现这一点。bug很有帮助。
(2)使用String str = "abc"这样,可以在一定程度上提高程序的速度,因为。JVM它将根据堆栈中数据的实际情况自动确定是否需要创建新对象。和String str = new String("abc"); 的代码都会在堆中创建新对象,而不管它们的字符串值是否相等,以及是否需要创建新对象都会增加程序的负担。这个想法应该是 元模型的概念,JDK目前尚不清楚此模式是否已应用于此处的内部实现。
(3)比较包装类中的值是否相等时,请使用equals()方法当测试对两个包装类的引用是否指向同一对象时,请使用==。
(4)由于String类的immutable性质,当String当变量需要频繁转换其值时,应考虑变量。StringBuffer类以提高程序效率
有关更多信息,请参阅 http://dong-shuai22-126-com.iteye.com/blog
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除