`
chenzehe
  • 浏览: 538192 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

JVM常量池和八种基本数据及字符串

JVM 
阅读更多

    常量池(constant_pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量和符号引用。运行时常量池是方法区的一部分。

     在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值。常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

     类和接口的全限定名

     字段名称和描述符

     方法名称和描述符

 

     Java中八种基本类型的包装类的大部分都实现了常量池技术,它们是Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类(Float、Double)则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127时才可使用对象池。

 

     看下面代码:

/**
 * Huisou.com Inc.
 * Copyright (c) 2011-2012 All Rights Reserved.
 */

/**
 * @description
 * @package
 * @title Test.java
 * @author chenzehe
 * @email
 * @version
 * @updateUser
 * @create 2011-12-21 上午11:27:48
 * @update 2011-12-21 上午11:27:48
 */

public class Test {
	public static void main(String[] args) throws Exception {
		Integer a = 127;
		Integer b = 127;
		Integer c = 128;
		Integer d = 128;
		System.out.println(a == b);// 输出true
		System.out.println(c == d);// 输出false
	}
}

      使用javap查看生成的字节码:

E:\chenzehe\workspace_b2b_3th\test\src>javap -verbose Test
Compiled from "Test.java"
public class Test extends java.lang.Object
  SourceFile: "Test.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method       #6.#21; //  java/lang/Object."<init>":()V
const #2 = Method       #22.#23;        //  java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
const #3 = Field        #24.#25;        //  java/lang/System.out:Ljava/io/PrintStream;
const #4 = Method       #26.#27;        //  java/io/PrintStream.println:(Z)V
const #5 = class        #28;    //  Test
const #6 = class        #29;    //  java/lang/Object
const #7 = Asciz        <init>;
const #8 = Asciz        ()V;
const #9 = Asciz        Code;
const #10 = Asciz       LineNumberTable;
const #11 = Asciz       main;
const #12 = Asciz       ([Ljava/lang/String;)V;
const #13 = Asciz       StackMapTable;
const #14 = class       #30;    //  "[Ljava/lang/String;"
const #15 = class       #31;    //  java/lang/Integer
const #16 = class       #32;    //  java/io/PrintStream
const #17 = Asciz       Exceptions;
const #18 = class       #33;    //  java/lang/Exception
const #19 = Asciz       SourceFile;
const #20 = Asciz       Test.java;
const #21 = NameAndType #7:#8;//  "<init>":()V
const #22 = class       #31;    //  java/lang/Integer
const #23 = NameAndType #34:#35;//  valueOf:(I)Ljava/lang/Integer;
const #24 = class       #36;    //  java/lang/System
const #25 = NameAndType #37:#38;//  out:Ljava/io/PrintStream;
const #26 = class       #32;    //  java/io/PrintStream
const #27 = NameAndType #39:#40;//  println:(Z)V
const #28 = Asciz       Test;
const #29 = Asciz       java/lang/Object;
const #30 = Asciz       [Ljava/lang/String;;
const #31 = Asciz       java/lang/Integer;
const #32 = Asciz       java/io/PrintStream;
const #33 = Asciz       java/lang/Exception;
const #34 = Asciz       valueOf;
const #35 = Asciz       (I)Ljava/lang/Integer;;
const #36 = Asciz       java/lang/System;
const #37 = Asciz       out;
const #38 = Asciz       Ljava/io/PrintStream;;
const #39 = Asciz       println;
const #40 = Asciz       (Z)V;

{
public Test();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 18: 0


public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   Stack=3, Locals=5, Args_size=1
   0:   bipush  127
   2:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   5:   astore_1
   6:   bipush  127
   8:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   11:  astore_2
   12:  sipush  128
   15:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   18:  astore_3
   19:  sipush  128
   22:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   25:  astore  4
   27:  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   30:  aload_1
   31:  aload_2
   32:  if_acmpne       39
   35:  iconst_1
   36:  goto    40
   39:  iconst_0
   40:  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V
   43:  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   46:  aload_3
   47:  aload   4
   49:  if_acmpne       56
   52:  iconst_1
   53:  goto    57
   56:  iconst_0
   57:  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V
   60:  return
  LineNumberTable:
   line 20: 0
   line 21: 6
   line 22: 12
   line 23: 19
   line 24: 27
   line 25: 43
   line 26: 60

  StackMapTable: number_of_entries = 4
   frame_type = 255 /* full_frame */
     offset_delta = 39
     locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte
ger, class java/lang/Integer ]
     stack = [ class java/io/PrintStream ]
   frame_type = 255 /* full_frame */
     offset_delta = 0
     locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte
ger, class java/lang/Integer ]
     stack = [ class java/io/PrintStream, int ]
   frame_type = 79 /* same_locals_1_stack_item */
     stack = [ class java/io/PrintStream ]
   frame_type = 255 /* full_frame */
     offset_delta = 0
     locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte
ger, class java/lang/Integer ]
     stack = [ class java/io/PrintStream, int ]

  Exceptions:
   throws java.lang.Exception
}

 Integer a = 127;对应的指令为:

   0:   bipush  127
   2:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

Integer c = 128;对应的指令为:

   12:  sipush  128
   15:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

     bipush指令意思是将单字节的常量值(-128~127)推送到栈顶

     sipush指令意思是将短型的常量值(-32768~32767)推送到栈顶

     invokestatic指令意思是调用静态方法,这里调用的是常量池中#2指向的方法java/lang/Integer.valueOf,查看Integer.valueOf方法:

	public static Integer valueOf(int i) {
		final int offset = 128;
		if (i >= -128 && i <= 127) { // must cache
			return IntegerCache.cache[i + offset];
		}
		return new Integer(i);
	}

 IntegerCache代码:

    private static class IntegerCache {
	private IntegerCache(){}

	static final Integer cache[] = new Integer[-(-128) + 127 + 1];

	static {
	    for(int i = 0; i < cache.length; i++)
		cache[i] = new Integer(i - 128);
	}
    }

     其它封装类如下:

//Boolean类也实现了常量池技术

Boolean bool1=true;

Boolean bool2=true;

System.out.println(bool1==bool2); //输出true

//浮点类型的包装类没有实现常量池技术

Double d1=1.0;

Double d2=1.0;

System.out.println(d1==d2); //输出false 

 

 

     String s =  new  String( "xyz" );  在运行时涉及 几个String实例?

     两个,一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与"xyz"相同的实例。

 

     String中的final用法和理解:

     final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。

final StringBuffer a = new StringBuffer("111");
final StringBuffer b = new StringBuffer("222");
a=b;//此句编译不通过 

final StringBuffer a = new StringBuffer("111");
a.append("222");//编译通过  

 

String a = "a1";
String b = "a" + 1;
System.out.println((a == b)); //result = true
String a = "atrue";
String b = "a" + "true";
System.out.println((a == b)); //result = true
String a = "a3.4";
String b = "a" + 3.4;
System.out.println((a == b)); //result = true 

    JVM对于字符串常量的"+"号连接,将程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true。

 

String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false 

   JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。

 

 

String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true 

     和上面唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。

 

String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b)); //result = false

private static String getBB() {
    return "b";
}

 JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false。

 

通过上面4个例子可以得出得知:

 

String  s  =  "a" + "b" + "c";  

就等价于String s = "abc";  int i = 1+2+3;也等价于int i = 6;

 

String  a  =  "a";  
String  b  =  "b";  
String  c  =  "c";  
String  s  =   a  +  b  +  c;   

 这个就不一样了,最终结果等于:

StringBuffer temp = new StringBuffer();  
temp.append(a).append(b).append(c);  
String s = temp.toString(); 

 由上面的分析结果,可就不难推断出String 采用连接运算符(+)效率低下原因分析,形如这样的代码:

public class Test {
    public static void main(String args[]) {
        String s = null;
        for(int i = 0; i < 100; i++) {
            s += "a";
        }
    }
} 

     每做一次 + 就产生个StringBuilder对象,然后append后就扔掉。下次循环再到达时重新产生个StringBuilder对象,然后 append 字符串,如此循环直至结束。 如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用StringBuffer或StringBulider对象来进行 append操作。

 

String.intern()解析

Java语言并不要求常量一定只能在编译期产生,运行时也可能将新的常量放入常量池中,这种特性用的最多的就是String.intern()方法。

String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加 一个Unicode等于str的字符串并返回它的引用。

String s0= "xyz";
String s1=new String("xyz");
String s2=new String("xyz");

System.out.println(s0==s1);
s1.intern();
s2=s2.intern(); //把常量池中“pku”的引用赋给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()返回的是常量池中”pku”的引用
true

 

有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串 已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这个全局的String 表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:

String s1=new String("xyz");
String s2=s1.intern();
System.out.println( s1==s1.intern() );
System.out.println( s1+" "+s2 );
System.out.println( s2==s1.intern() );

输出为:
false
xyz xyz
true

 

分享到:
评论

相关推荐

    JVM常量池教程吐血整理干货.md

    ### JVM常量池详解 #### Class常量池(静态常量池) 在Java程序的编译过程中,每个`.class`文件都会包含一个常量池,这个常量池被称为Class常量池或者静态常量池。它存在于每个`.class`文件的`Constant Pool`部分...

    字符数组的存储方式 字符串常量池.docx

    为了提高效率和内存管理,Java虚拟机(JVM)引入了字符串常量池这一概念。字符串常量池是一个特殊的区域,它存储了程序中所有的字符串常量,避免了多次创建相同的字符串对象。本文将详细探讨字符串常量池的存储方式...

    深入解析JVM之内存结构及字符串常量池(推荐)

    "深入解析JVM之内存结构及字符串常量池" JVM(Java Virtual Machine)是Java语言的核心组件之一,负责将Java代码编译成机器代码并执行。JVM的内存结构是Java开发者需要了解的基础知识之一,本文将深入解析JVM之内存...

    java 内存中 堆、栈、常量池、方法区的总结

    常量池的好处是让字符串等数据可以被共享,因此在常量池中的数据不会存在多份副本,它们是唯一的。 接下来是栈,它主要用于方法调用的上下文,每个线程都会拥有一个或多个栈。栈主要用于存储局部变量、操作数栈、...

    06-VIP-JVM调优实战及常量池详解(预习)1

    总的来说,理解和优化JVM的内存管理和字符串常量池是提升Java应用程序性能的关键。通过深入分析GC日志,我们可以找出潜在的性能问题,并通过调整JVM参数来改善。同时,了解常量池的工作机制,有助于我们更好地管理...

    图解JVM的内存结构及字符串常量池方法详解.docx

    在实际开发中,我们还需要关注JVM的其他重要概念,如类加载机制(加载、验证、准备、解析、初始化)、类文件结构(魔数、版本号、常量池等)、垃圾回收算法(如标记-清除、复制、标记-整理、分代收集)以及执行引擎...

    Java String 字符串常量池解析

    Java 中的字符串常量池是一种为了提高性能和减少内存开销的机制。它是 JVM 实例化字符串常量时进行的一些优化,主要是为了减少字符串对象的创建和存储。 字符串常量池的设计思想是基于字符串是不可变的这个特点,...

    java 创建字符串类

    - 字符串常量池是JVM内存中的一个特殊区域,用于存放所有的字符串字面量。当创建一个`String`对象时,如果常量池中已经存在相同内容的字符串,那么将返回该字符串的引用,而不是创建新的对象。 4. **字符串比较**...

    Java中的字符串常量池详细介绍

    Java中的字符串常量池是Java虚拟机(JVM)为了优化字符串对象的使用而设计的一个特殊内存区域。这个池主要用于存储字符串字面量,也就是在程序中直接出现的字符串值,比如`"hello"`。其核心目的是减少内存的消耗和...

    java内存分配之常量池,栈,堆1

    在Java 6及之前,字符串常量池位于永久代,从Java 7开始移到了堆中。 栈和堆之间的区别在于,栈中的数据生命周期短暂,随方法调用结束而释放,而堆中的对象生命周期较长,直到垃圾回收。栈内存速度快但容量有限,堆...

    java入门教程:数据类型_运行时常量池.docx

    数据类型决定了变量可以存储的值的种类和大小,而运行时常量池则是Java虚拟机(JVM)内存模型中的一个重要组成部分。 首先,让我们详细讨论Java的数据类型。Java的数据类型分为两大类:基本数据类型和引用数据类型...

    java常量池分析.pdf

    Java常量池是Java编程语言中的一个重要概念,它在JVM(Java虚拟机)的运行时数据区中占据着核心地位。常量池是每个类或接口在编译时都会生成的一部分,它存储了各种类型的常量,包括字面量(如字符串、整数、浮点数...

    Java 字符串

    Java虚拟机(JVM)维护了一个特殊的区域称为字符串常量池。当创建一个字符串并将其存储在池中时,如果已有相同内容的字符串存在,那么将返回池中已存在的字符串的引用,这就是所谓的字符串 intern() 方法的原理。 ...

    【Java编程教程】详解Java String字符串.pdf

    当使用字符串字面量(即双引号包围的文本)创建字符串时,Java虚拟机(JVM)会首先查看字符串常量池,如果字符串已经存在,就直接返回引用;如果不存在,JVM则会在池中创建新的字符串实例。 创建字符串对象主要有两...

    java字符串 详解 java实例20

    当创建一个字符串并用引号括起来时,JVM会检查常量池中是否存在相同的字符串,如果存在则直接引用,否则创建新实例并放入池中。这种方式提高了内存效率。 三、字符串操作 1. 连接:`+`运算符可以用于连接字符串,...

    深入探索Java常量池

    Java常量池是Java虚拟机(JVM)中一个非常重要的概念,它主要分为两种:静态常量池和运行时常量池。静态常量池是class文件中的常量池,包括字符串(数字)字面值、类和方法的信息,占用了class文件的大部分空间。...

    JVM调优实践 ⼀、JVM调优准备⼯作 实验报告 pdf

    5. **运行时常量池(字符串常量池)**:包含字面量和符号引用。字符串常量池是为了解决大量重复字符串导致的内存浪费问题,通过引用共享同一字符串实例,提高内存利用率。在JDK 6及之前,字符串常量池位于永久代;...

    String 字符串讲解

    - Java虚拟机(JVM)维护了一个字符串常量池,用于存储字符串字面量。如果两个字符串字面量相同,它们在内存中只会有一个实例。 3. **字符串连接**: - 使用`+`运算符可以合并字符串,如`str1 + str2`。这会导致...

    解析JVM内存结构和6大区域

    运行时常量池是方法区的一部分,用于存储常量池的信息,例如字符串、基本类型的值等。 直接内存 直接内存是 JVM 中的一块内存区域,用于存储 native 方法调用的数据。直接内存不是 JVM 的一部分,它是 native 方法...

Global site tag (gtag.js) - Google Analytics