问题
public static void main(String[] args) {
String a = "abc";
String b = "12abc";
String c = " abc";
String d = "abc";
System.out.println(b.substring(2, b.length()) == a);
System.out.println(c.trim() == a);
System.out.println(d == a);
}
运行结果:
false
false
true
原因:
String是一种特殊的包装类, 可以通过俩种方式创建
1. String a = "abc"
2. String a = new String("abc");
第一种方式创建有以下几个步骤:
- 先在栈中创建一个引用变量
String a; - 在
常量池中查找有没有存放abc - 如果存在, 直接将变量
a指向abc - 如果不存在, 则在
常量池中创建一个abc, 并将变量a指向abc
第二种方式创建有以下几个步骤:
- 在
堆中创建一个对象abc, 并将变量a指向堆中的abc - 在
常量池中查看是否有abc的字符串对象 - 如果存在, 将
堆中的abc对象与常量池中的abc联系起来 - 如果不错在, 则在
常量池中创建一个abc的字符常量, 再将堆中的abc对象与常量池中的abc联系起来
而trim()和substring()内部都是返回的string的一个copy的对象, 即是通过new String()产生的新的对象, 地址不一样, 所以==出来是false;
注意:第二种方式的创建, 如果常量池没有aaa字符串对象, 则会一次性创建俩个对象
intern 方法可以返回该字符串在常量池中的对象的引用,可以通过下面代码简单的测试 Java代码
class StringTest {
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc").intern();
System.out.println(str1==str2);
}
}
// ----------------------------
// ----------------------------
// ----------------------------
// 所以一开始的问题代码可以修改一下:
System.out.println(b.substring(2, b.length()).intern() == a);
System.out.println(c.trim().intern() == a);
// 打印结果:
true
true
思考1: java中的数据的存储方式
-
寄存器(register)这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部. ——最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. -
栈(stack)位于通用RAM中,但通过它的”堆栈指针”可以从处理器哪里获得支持. 堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些 内存。这是一种快速有效的分配存储方法,仅次于寄存器. 创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成 相应的代码,以便上下移动堆栈指针. 这一约束限制了程序的灵活性.——存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中) -
堆(heap)一种通用性的内存池(也存在于RAM中),用于存放所有的JAVA对象.堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间.因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配.当然,为这种灵活性必须要付出相应的代码.用堆进行存储分配比用堆栈进行存储存储需要更多的时间. ——存放所有new出来的对象. -
静态存储(static storage)这里的“静态”是指“在固定的位置”.静态存储里存放程序运行时一直存在的数据.你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里. ——存放静态成员(static定义的) -
常量存储(constant storage)常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变.有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中 ——存放字符串常量和基本类型常量(public static final) -
非RAM存储如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在.
思考2: jvm内存与系统(Linux)内存之间的关系
思考1是从java层面去理解运行时数据是如何在jvm管理的内存中存储的, 但是jvm内存与系统内存, 物理内存直接是如何映射的呢?
-
从硬件上看,Linux系统的内存空间由两个部分构成:物理内存和SWAP(位于磁盘)。物理内存是Linux活动时使用的主要内存区域;当物理内存不够使用时,Linux会把一部分暂时不用的内存数据放到磁盘上的SWAP中去,以便腾出更多的可用内存空间;而当需要使用位于SWAP的数据时,必须先将其换回到内存中。
-
从Linux系统上看,除了引导系统的BIN区,整个内存空间主要被分成两个部分:内核内存(Kernel space)、用户内存(User space)。内核内存是Linux自身使用的内存空间,主要提供给程序调度、内存分配、连接硬件资源等程序逻辑使用。用户内存是提供给各个进程主要空间,Linux给各个进程提供相同的虚拟内存空间;这使得进程之间相互独立,互不干扰。实现的方法是采用虚拟内存技术:给每一个进程一定虚拟内存空间,而只有当虚拟内存实际被使用时,才分配物理内存。
- 从进程的角度来看,进程能直接访问的用户内存(虚拟内存空间)被划分为5个部分:代码区、数据区、堆区、栈区、未使用区。代码区中存放应用程序的机器代码,运行过程中代码不能被修改,具有只读和固定大小的特点。数据区中存放了应用程序中的全局数据,静态数据和一些常量字符串等,其大小也是固定的。堆是运行时程序动态申请的空间,属于程序运行时直接申请、释放的内存资源。栈区用来存放函数的传入参数、临时变量,以及返回地址等数据。未使用区是分配新内存空间的预备区域。
–
进程与JVM内存模型
JVM本质就是一个进程, 但有一些区别:
- JVM将许多本来属于操作系统管理范畴的东西,移植到了JVM内部,目的在于减少系统调用的次数;
- Java NIO,目的在于减少用于读写IO的系统调用的开销。
-
永久代永久代本质上是Java程序的代码区和数据区。Java程序中类(class),会被加载到整个区域的不同数据结构中去,包括常量池、域、方法数据、方法体、构造函数、以及类中的专用方法、实例初始化、接口初始化等。这个区域对于操作系统来说,是堆的一个部分;而对于Java程序来说,这是容纳程序本身及静态资源的空间,使得JVM能够解释执行Java程序。 -
新生代和老年代新生代和老年代才是Java程序真正使用的堆空间,主要用于内存对象的存储;但是其管理方式和普通进程有本质的区别(普通进程创建回收对象, 都会触发一次系统调用; 而JVM是一次性向系统申请一整段内存区域, 并由自己管理); -
未使用区未使用区是分配新内存空间的预备区域。对于普通进程来说,这个区域被可用于堆和栈空间的申请及释放,每次堆内存分配都会使用这个区域,因此大小变动频繁;对于JVM进程来说,调整堆大小及线程栈时会使用该区域,而堆大小一般较少调整,因此大小相对稳定。操作系统会动态调整这个区域的大小,并且这个区域通常并没有被分配实际的物理内存,只是允许进程在这个区域申请堆或栈空间。

