`
Copperfield
  • 浏览: 260979 次
  • 性别: Icon_minigender_1
  • 来自: 上海
博客专栏
C407adc3-512e-3a03-a056-ce4607c3a3c0
java并发编程陷阱
浏览量:25211
社区版块
存档分类

Java 内存分配及String类型详解

阅读更多

Java 把内存划分成两种:一种是栈内存,另一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的

栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动

释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数

组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个

变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为

数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组

和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会

被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定

的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。

实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!


全面解析 Java 中的 String 数据类型
April 6th, 2008  | Categories: Java  | Tags: Java

1. 首先String不属于8种基本数据类型,String是一个对象。
因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。

2. new String()和new String(”")都是申明一个新的空字符串,是空串不是null;

3. String str=”kvill”;String str=new String (”kvill”);的区别:在这里,我们不谈堆,也不谈栈,只先简单引入常量

池这个简单的概念。
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等

中的常量,也包括字符串常量。

看例1:

String s0="kvill";
String s1="kvill";
String s2="kv" + "ill";
System.out.println( s0==s1 );
System.out.println( s0==s2 );
结果为:
true
true

首先,我们要知结果为道Java会确保一个字符串常量只有一个拷贝。
因为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字

符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符

串常量,所以s2也是常量池中” kvill”的一个引用。
所以我们得出s0==s1==s2;用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放

入常量池中,它们有自己的地址空间。

  看例2:
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 );
结果为:
false
false
false

例2中s0还是常量池中”kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半

部分 new String(”ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此

结果了。

4. String.intern():
再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的

一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的

引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了

  例3:
String s0= "kvill";
String s1=new String("kvill");
String s2=new String("kvill");
System.out.println( s0==s1 );
System.out.println( "**********" );
s1.intern();
s2=s2.intern(); //把常量池中"kvill"的引用赋给s2
System.out.println( s0==s1);
System.out.println( s0==s1.intern() );
System.out.println( s0==s2 );
结果为:
false
**********
false //虽然执行了s1.intern(),但它的返回值没有赋给s1
true //说明s1.intern()返回的是常量池中"kvill"的引用
true

最后我再破除一个错误的理解:有人说,“使用 String.intern() 方法则可以将一个 String 类的保存到一个全局 String 表中

,如果具有相同值的 Unicode 字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串

,则将自己的地址注册到表中”如果我把他说的这个全局的 String 表理解为常量池的话,他的最后一句话,”如果在表中没有相

同值的字符串,则将自己的地址注册到表中”是错的:

  看例4:
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );
结果:
false
kvill kvill
true

在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中

新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。
s1==s1.intern()为false说明原来的”kvill”仍然存在;s2现在为常量池中”kvill”的地址,所以有s2==s1.intern()为true。

5. 关于equals()和==:
这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也

就是是否是同一个字符串的引用。

6. 关于String是不可变的
这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”;
就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” ” 生成 “kvill “存在内存

中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的”不可变”产生了很多临时变量,这也就

是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的。

   1.
      mxj said:
      April 11th, 2008 at 11:44 pm

      单独的这样一条语句String str=”kv”+”ill”+” “+”ans”;应该不会产生临时变量吧,等号后面的一长串东西应该在

编译期就直接变成一个常量字符串,放在常量池里了。。
      做了个测试:
      源代码
      public class Test{
      public static void main(String[] s){
      String b=”xxx”;
      String a=”bbb”+”ccc”+”ddd”;
      System.out.println(a+b);
      }
      }
      javac 编译后,用javap看他的机器码。
      public static void main(java.lang.String[]);
      Code:
      0: ldc #2; //String xxx
      2: astore_1
      3: ldc #3; //String bbbcccddd
      5: astore_2
      6: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
      9: new #5; //class java/lang/StringBuilder
      12: dup
      13: invokespecial #6; //Method java/lang/StringBuilder.”":()V
      16: aload_2
      17: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/
      String;)Ljava/lang/StringBuilder;
      20: aload_1
      21: invokevirtual #7; //Method java/lang/StringBuilder.append:(Ljava/lang/
      String;)Ljava/lang/StringBuilder;
      24: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/la
      ng/String;
      27: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/Str
      ing;)V
      30: return

      }
      可以看到只有一个已经编译成”bbbcccddd”的常量字符串。
      可以看到编译器自动调用StringBuilder来处理字符串拼接,编译器还是很AI的。(StringBuilder是1.5的新类,1.4应该是

StringBuffer,两者区别后面说。)

      到底coding的时候要不要用StringBuffer呢?貌似编译器会自动把字符串拼接,用StringBuffer来处理。。
      好比c里面,到底要不要把c/2改成c>>1提高效率,编译器有时候做的比我们想象的好的多。

分享到:
评论

相关推荐

    java内存分配机制详解

    ### Java内存分配机制详解 #### 一、引言 Java作为一种广泛应用的编程语言,其内存管理机制对于确保程序高效稳定运行至关重要。本文旨在详细介绍Java内存分配机制中的几个关键概念:寄存器、栈、堆、静态域、常量...

    java内存分配详解

    ### Java内存分配详解 #### 一、Java内存区域划分 Java程序在运行过程中涉及的内存主要分为两大类:栈内存和堆内存。 1. **栈内存**:主要用于存储局部变量,如基本数据类型变量(int、long、char等)以及对象的...

    Java内存分配原理精讲

    ### Java内存分配原理精讲 #### 一、引言 Java作为一门广泛应用于企业级开发的语言,其内存管理和分配机制是其核心技术之一。本文旨在深入探讨Java内存分配的基本原理及其在不同内存区域的具体表现,帮助读者更好...

    JVM内存分配与垃圾回收详解

    JVM 内存分配与垃圾回收详解 Java 虚拟机(JVM)是 Java 语言的 runtime 环境,它提供了一个平台独立的方式来执行 Java 字节码。JVM 内存分配与垃圾回收是 JVM 中两个非常重要的概念,本文将对 JVM 内存分配与垃圾...

    附录:Java的内存分配

    ### Java的内存分配详解 #### 一、Java内存模型概览 Java的内存管理是Java程序性能的关键之一。为了确保程序高效稳定地运行,开发者必须理解Java如何管理和分配内存资源。Java程序的内存主要分为以下几个部分: -...

    JAVA内存溢出详解.doc

    Java内存主要分为堆内存(Heap)和栈内存(Stack),堆是存储对象实例的主要区域,而栈主要用于存储基本类型和对象引用。除此之外,还有方法区(Method Area)、程序计数器(PC Register)和本地方法栈(Native ...

    创建string对象过程的内存分配:

    ### 创建string对象过程的内存分配详解 #### 一、引言 在Java中,`String` 类是最常用的数据类型之一,用于表示不可变的字符序列。`String` 对象的创建涉及复杂的内存分配机制,特别是在Java虚拟机 (JVM) 的环境中...

    深入Java核心 Java内存分配原理精讲

    ### 深入Java核心:Java内存分配原理精讲 #### 一、Java内存区域概述 在Java程序运行过程中,其内存被分为几个主要部分,包括堆内存、栈内存、方法区等。这些不同的内存区域负责不同的职责,共同保证了Java程序的...

    java内存空间详解

    ### Java内存空间详解 #### 一、Java内存机制概述 Java程序在运行过程中涉及的内存主要分为两大类:栈内存和堆内存。 1. **栈内存**:主要用于存储局部变量和对象的引用变量。栈内存的特点是先进后出,即先压入的...

    详解Java的String类型程序

    ### Java中的String类型详解 在Java编程语言中,`String` 类型是非常基础且重要的数据类型之一,它代表了一系列字符的有序集合。本篇文章将基于一个具体的示例代码,深入探讨`String`对象的创建、比较以及不同字符...

    Java中堆内存和栈内存详解

    ### Java中堆内存和栈内存详解 #### 一、引言 在Java编程语言中,内存管理是一项核心技能。为了更好地理解和使用Java,必须清楚地了解堆内存与栈内存的区别及其工作原理。本文将深入探讨Java中堆内存与栈内存的概念...

    关于Java变量的声明、内存分配及初始化详解

    在Java中,内存分配分为两种情况:对于基本数据类型(如int, double, char等)和引用数据类型(如对象实例)。基本数据类型的变量在声明时会自动分配内存空间,并赋予默认值。例如,`int a;` 会分配一个int类型的...

    Java中堆内存和栈内存详解.doc

    #### 二、Java内存区域概述 Java虚拟机(JVM)将内存分为几个不同的部分,每部分都有其特定的功能。主要分为以下几种内存区域: 1. **堆内存(Heap)**:用于存放对象实例和数组。 2. **栈内存(Stack)**:用于存放...

    String类详解!

    2. **内存分配差异**:简化语法实际上利用了Java的字符串常量池机制,使得相同的字符串仅被创建一次并存储于常量池中,从而节省了内存资源。相比之下,使用`new String()`会额外创建一个对象实例,这可能导致内存...

    模拟内存分配实验报告

    ### 模拟内存分配实验知识点总结 #### 实验背景与目的 本次实验是关于操作系统的模拟内存分配实验,旨在帮助学生深入理解操作系统中内存管理的关键技术。通过实践操作,学生可以掌握不同内存分配策略的工作原理及其...

    Java中堆内存和栈内存详解文.pdf

    Java的内存分配策略与编译原理密切相关。静态存储分配在编译时就确定了存储空间,适用于没有可变数据结构和嵌套/递归结构的场景。栈式分配适用于生命周期短、生存期确定的变量,而堆式分配则适合于需要动态分配和...

    java CharSequence、String、StringBuffer、StringBuilder详解

    通过分析提供的`String.java`、`StringBuffer.java`和`StringBuilder.java`源码,我们可以深入理解这些类的内部实现,例如它们如何处理字符序列,以及在进行字符串操作时的内存分配和性能优化策略。源码阅读有助于...

    详解java堆和栈

    ### 详解Java堆和栈 #### 一、引言 在Java编程中,理解堆(Heap)和栈(Stack)的概念及其区别对于程序员来说至关重要。本文将深入剖析这两个概念,并探讨它们之间的差异以及如何影响程序的运行。 #### 二、Java...

Global site tag (gtag.js) - Google Analytics