`

从JVM 内部看String 类型的问题

阅读更多

    关于String 类型,在面试题或者实际编程中都会经常遇到,有很多的网友也曾做过大量的分析。在看完深入Java 虚拟机这本书后,对JVM 处理Java 程序的流程有了一个大概的认识,所以总结一下。

   下面的分析从我们遇到的一些问题实例进行,我觉得这样是最好理解的。

 

1、String 类型对象的生成

     

String s=new String("zhxing");
String s1="zhxing";
Object o=new Object();

    下面来对比下这三条代码反编译后生成的字节码:

 

 (1)

   0:   new     #2; //class java/lang/String
   3:   dup
   4:   ldc     #3; //String zhxing
   6:   invokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/Strin
g;)V
   9:   astore_1

 

(2)

   10:  ldc     #3; //String zhxing
   12:  astore_2

 (3)

   13:  new     #5; //class java/lang/Object
   16:  dup
   17:  invokespecial   #1; //Method java/lang/Object."<init>":()V
   20:  astore_3

 

   涉及到得指令集的解析:

new指令格式:

   new indexbyte1,indexbyte2

 

执行过程:

   要执行new指令,Jvm通过计算(indextype1<<8)|indextype2生成一个指向常量池的无符号16位索引。然后JVM根据计算出的索引查找JVM常量池入口。该索引所指向的常量池入口必须为CONSTANT_Class_info。如果该入口尚不存在,那么JVM将解析这个常量池入口,该入口类型必须是类。JVM从堆中为新对象映像分配足够大的空间,并将对象的实例变量设为默认值。最后JVM将指向新对象的引用objectref压入操作数栈。

 

dup指令格式:

  dup

 

执行过程:

  要执行dup指令,JVM复制了操作数栈顶部一个字长的内容,然后再将复制内容压入栈。本指令能够从操作数栈顶部复制任何单位字长的值。但绝对不要使用它来复制操作数栈顶部任何两个字长(long型或double型)中的一个字长。上面例中,即复制引用objectref,这时在操作数栈存在2个引用。

 

ldc指令格式:

  ldc,index

 

执行过程:

   要执行ldc指令,JVM首先查找index所指定的常量池入口,在index指向的JVM常量池入口,JVM将会查找CONSTANT_Integer_info,CONSTANT_Float_info和CONSTANT_String_info入口。如果还没有这些入口,JVM会解析它们。而对于上面的haha,JVM会找到CONSTANT_String_info入口,同时,将把指向被拘留String对象(由解析该入口的进程产生)的引用压入操作数栈。

 

invokespecial指令格式:
  invokespecial,indextype1,indextype2

 

执行过程:
  对于该类而言,该指令是用来进行实例初始化方法的调用。上面例子中,即通过其中一个引用调用String类的构造器,初始化对象实例,让另一个相同的引用指向这个被初始化的对象实例,然后前一个引用弹出操作数栈。

 

astore_1指令格式:(astore_2、astore_3类似只是局部变量不同
  astore_1

 

astore_1指令过程:
  要执行astore_1指令,JVM从操作数栈顶部弹出一个引用类型或者returnAddress类型值,然后将该值存入由索引1指定的局部变量中,即将引用类型或者returnAddress类型值存入局部变量1。

 

JVM常量池:

   虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。对于String常量,它的值是在常量池中的。而JVM常量池在内存当中是以表的形式存在的,特别要注意的是对于String类型,JVM维护着一张表用来存储文字字符串值(被拘留的字符串值)。--这里有一点不明白的是:到底这张表是保存String 引用还是直接保存String 值(像int 常量池是直接保存值的),这点还不明确。规范上也没有说明,如有大虾知道的话,麻烦短信告诉下。

 

总结:

    看了上面的介绍后,可以知道,String 类型在编码中字面上的对象,是会进行查询String 常量池表的(如果没有就创建一个String 对象,然后再保存在表中)。而new String() 生成的对象是不会查询String 常量池表的,它不管表中是否存在,都会创建一个新的String 对象,所以内存地址是肯定不同的。而如果调用String 方法中的intern()方法,则会在String常量池表中查找,然后把该表中的引用返回。

 

这里有几篇String 相关的文章值得一看的 :

http://blog.csdn.net/ZangXT/archive/2009/08/05/4410246.aspx

http://www.iteye.com/topic/522167

 

 

2、String 类型参数的传递

   记得有篇文章是相关的Java-String类型的参数传递问题 ,(在一年前我还转载过)。

   该文章中的有几点我不大认同:

(1)String的存储实际上通过char[]来实现的。
(2)String就相当于是char[]的包装类。包装类的特质之一就是在对其值进行操作时会体现出其对应的基本类型的性质。

  

  看了JVM规范可以知道,数组在JVM 是一个对象来的,如果改变数组,对其操作是会改变它的值的。所以这和String 作为参数传递不会改变它的值是没有关系的。具体测试看下面:

public class Test {
	public static void main(String[] args) {
		char[] c={'b','a'};
		test(c);
		System.out.println(c[0]);
	}
	
	public static void test(char[] c){
		c[0]='a';
	}

}

 

   那为什么String 作为一个对象引用传递,而不会改变它的值。这是因为String 中成员变量,也就是保存值的char[] 数组是final 类型,而且没有提供任何关于它的修改的方法,当然没办法修改到String 的引用的内容啦。看下面模拟String 的例子:

public class Test {
	public static void main(String[] args) {
		StringTemp s=new StringTemp("bbbb");
		test2(s);
		System.out.println(s);
	}
	public static void test2(StringTemp s1){
		s1=new StringTemp("zhxing");
	}
}
//模拟String类型
public final class StringTemp {
	private final char[] value;
	public StringTemp(String s){
		value=s.toCharArray();
	}
	@Override
	public String toString() {
		return new String(value).toString();
	}
	

}

   在main 方法的s 和test2 方法的参数s1 都只是指向new StringTemp("bbbb"); 这个的引用,当s1 改变时(指向了另外的一个引用new StringTemp("zhxing");),是不会影响到s 的引用的。同理String 类型也是一样,是不会改变的。

  

 

  对于String 类型就了解到这里,不知道还有没相关的应用问题,如果有提出再来补上。。感觉自己写文章讲解有点犯晕,呵呵,文笔不好吧。。

1
1
分享到:
评论

相关推荐

    jvm8虚拟机规范

    JVM8引入了G1垃圾收集器、String去重复、堆内存并行压缩等优化措施,提升了内存管理和性能。同时,还引入了 invokedynamic 指令,支持动态语言的高效运行。 6. 类加载机制: 类加载分为加载、验证、准备、解析和...

    jvm字符转码

    在运行时,这些字符串在JVM内部以`char`数组的形式存在,即使用Unicode编码。 3. **字节流转换(Byte Stream Conversion)**:当Java程序读取或写入文件、网络流或其他字节流时,需要进行字符编码和解码。Java的`...

    Java 6 JVM参数选项大全

    这些参数通常以-XX:开头,允许开发者调整JVM内部的工作机制,以适应特定的应用场景和性能需求。以下是一些关键的JVM选项及其详细解释: 1. -XX:+&lt;option&gt; / -XX:-&lt;option&gt;: 这些选项用于启用或禁用某个特性。例如,...

    jvm常用命令工具

    这些工具能够帮助我们深入了解JVM内部的工作原理,并对应用程序进行更深层次的调试。 #### 二、工具详解 ##### 1. jps - JVM Process Status Tool **用途**:列出当前系统中正在运行的所有HotSpot虚拟机进程,并...

    String类创建对象问题

    在 Java 中,`String` 类并不属于八种基本数据类型之一,而是作为一个对象存在。这意味着 `String` 对象默认值为 `null`。尽管如此,`String` 类拥有其独特之处,比如它是不可变的(final),这保证了字符串一旦创建便...

    JVM性能调优-JVM内存整理及GC回收.pdf

    综上所述,JVM性能调优涉及多个方面,包括理解参数传递机制、掌握不同类型的引用以及深入了解垃圾回收算法和分区处理方式等。通过合理配置和调整这些机制,可以显著提高Java应用程序的性能和稳定性。开发者应根据...

    JVM内幕:java虚拟机详解

    ### JVM内幕:java虚拟机详解 #### 一、概述 ...理解JVM的工作原理对于优化Java程序、解决性能问题具有重要意义。通过深入学习JVM的各个组件及其工作方式,开发人员可以更好地掌握Java编程技巧,提升软件质量。

    JVM的相关概念.doc

    任何一个拥有 public static void main(String[] args) 函数的 class 都可以作为 JVM 实例运行的起点。 2. JVM 实例的运行:main() 作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM 内部有两种线程:...

    JVM方法执行的来龙去脉 - 简书1

    在JVM内部,有一个名为`JavaCalls`的模块,它负责处理Java方法之间的调用。`JavaCalls`包含了多种函数,如`call_virtual()`、`call_special()`、`call_static()`等,它们分别对应于Java中的虚方法调用、私有方法调用...

    JVM指令手册对照表PDF电子版

    - `ldc`:加载int、float或String类型的常量值。 - `ldc_w`:与`ldc`类似,但使用宽索引,可以访问更大范围的常量池项。 - `ldc2_w`:加载long或double类型的常量值,同样使用宽索引。 4. **本地变量加载和存储...

    JVM 45 道面试题及答案.docx

    运行时常量池里的引用类型常量(String 或 Class 类型)。WM 内部数据结构的一些引用,比如 sun. jvm. hotspot. memory. Universe 类。用于同步的监控对象,比如调用了对象的 wait() 方法。JNI handles,包括 global ...

    JVM 50 道面试题及答案.docx

    本文将从 JVM 的内存模型、对象分配、垃圾回收、常量池、类加载等方面进行详细的解析,并提供了相关的知识点。 一、JVM 的内存模型 JVM 的内存模型分为工作内存和主内存两部分。线程无法直接对主存储器进行操作,...

    徐葳《40小时掌握java语言之06String类》内部教材

    此外,String类还提供了isEmpty方法来判断字符串是否为空,其内部实现是判断字符串的长度是否为0。对于空字符串("")和null值的区分也很重要。空字符串表示一个没有任何字符的字符串实例,而null表示没有任何对象...

    java-基础/jvm/多线程

    - 内部类包括成员内部类、静态内部类、局部内部类和匿名内部类,它们可以访问外部类的私有成员。 12. **重载与重写**: - 重载发生在同一个类中,方法名相同但参数列表不同。 - 重写发生在子类中,方法名、返回...

Global site tag (gtag.js) - Google Analytics