学习JVM之后,发现对常量池的理解又深入了一些。下面就从JVM的层面去理解JAVA程序中那些常见的语句。
读过笔者博客“JVM-类的生命周期“
http://yizhenn.iteye.com/blog/2290619的读者都应该已经知道,JVM在加载class文件时经历了装载,连接,初始化的过程,其中连接又包括验证,准备和解析。我们就来说说这个解析。
所谓解析,就是将class文件中的静态常量池中的符号引用解析为直接引用,说白了,就是为class常量池中的一些常量创建对象。比如String str="abc";理论上来讲直接在class静态常量池中存放"abc"即可。但是我们知道,JAVA中除了8种基本类型之外,其他的都是引用。因此,对于静态常量池中的"abc",JVM会在堆中创建一个对象,静态常量池中的str就指向这个刚刚创建的对象。像str这样的常量对象所在的空间,叫做动态常量池。
在JAVA中,String和Integer,Short,Long,Character,Boolean,Byte这六种基本类型的封装类都实现了动态常量池机制。对于六种基本类型的封装类,我们以Integer为代表进行说明。
如下代码:
String a="123";
String b="123";
在解析的时候,发现静态常量池中有a="123",就会在动态常量池中寻找是否有值为"123"的对象,结果没有找到,就会执行new String("123"),然后使静态常量池中的a指向该对象,当发现静态常量池中有b="123",就会在动态常量池中寻找是否有值为"123"的对象,结果找到了之前创建的那个对象,就会把那个对象的地址返回给静态常量池中的b。
上面的过程是在解析的时候做的,当然你也可以认为在执行的时候做的。但不管怎样,一定存在一个常量池,
当执行String str="xx";的时候,先到常量池中去寻找值为xx的对象,如果存在,就直接返回该对象地址,否则在动态常量池中创建一个新对象并返回地址。
如下代码:
String a=new String("123");
String b=new String("123");
第一行代码在执行的时候,
JVM见到"123",会先到动态常量池中看是否有值为"123"的对象,如果没有,在常量池中创建一个对象String("123").这个过程和a没有半毛钱关系,你可以认为这是JVM常量池自学习的过程。接着,在堆区创建一个对象String("123");并将该对象的引用返回给a;
第二行代码也是这样的过程,
所以上面的两行代码可能产生2或3个对象。
对于String类,还存在一个
str.intern()方法,他的作用是检查动态常量池中是否存在值与str对象相同的对象,如果存在,直接返回该对象的引用。如果不存在,就在常量池中创建一个对象,然后返回他的引用。
如下代码:
String a="123";
String b="123";
String c=new String("123");
String d=c.intern();
对于上面的代码,你应该能理解,a和b和d都指向的是动态常量池中的那个对象String("123"),而c指向的是堆中的对象String("123");
介绍了String类的常量池,我们来说Integer类的常量池。Integer类型的取值不像String类型那么广泛,
String常量池中的对象收集自程序。Integer常量池中的对象是固定的,只有取值为-128~127的这256个对象。当Integer.valueOf(i)中i的值是-128~127时,直接返回常量池中的对象,否则在堆区新建一个对象并返回他的引用。这可以从jdk源码中得到证明,在jdk源码中,Integer有这样的一个方法:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
这个方法是自动装包时候用的方法,何谓装包?把int这个基本类型转化为Integer这个引用类型就是装包。
代码如下:
Integer a=1;
Integer b=1;
Integer c=1111;
Integer d=1111;
对于上面的代码,实际上如下:
Integer a=Integer.valueOf(1);
Integer b=Integer.valueOf(1);
Integer c=Integer.valueOf(1111);
Integer d=Integer.valueOf(1111);
因此a和b指向Integer常量池中的同一个对象,他们的值相等。c和d指向堆区中的不同对象,他们的值不等。
如下代码:
Integer a=new Integer(1);
Integer b=new Integer(1);
由于使用了new,强制的在堆区创建了两个对象,没用到常量池优化,因此a和b的值不等。
这里顺便提一下装包和拆包,将基本类型转化为对应的封装类的过程叫装包,反之叫拆包。当基本类型和其对应的封装类执行比较的时候,比如Integer对象和int型比较,这会使用拆包,将Integer类型拆包为int,这个过程调用Integer的intValue()方法。
关于上面提到的其余5中包装类的常量池与Integer类似,在此不再赘述。
分享到:
相关推荐
第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: ...
8. **类文件结构**:分析.class文件的组成,包括魔数、版本号、常量池、访问标志、类索引、接口索引等。 9. **JVM优化案例**:分享实际项目中遇到的问题及解决策略,比如内存泄漏、性能瓶颈等。 这些内容将有助于...
常量池中的符号引用,例如final常量的值、类和接口的全限定名、字段的名称和描述符、方法的名称和描述符,这些都是在解析阶段被转换成直接引用,以便JVM能够准确地定位和访问相应的内存地址。 总结起来,"JVM--字节...
字节码以二进制格式存储,包含了类的方法定义、操作符、常量池等信息。通过字节码,开发者可以跨越平台边界,实现“一次编写,到处运行”。 2. 调用图:调用图是一种图形表示法,用于描绘程序中的函数或方法调用...
当类加载到内存后,JVM就会将class文件常量池中的内容存放到运行时常量池中;在解析阶段,JVM会把符号引用替换为直接引用(对象的索引值)。例如:类中的一个字符串常量在class文件中时,存放在class文件常量池中的...
- 方法区和运行时常量池:存储类和接口的常量、方法信息。 2. **内存管理** - 堆内存:主要分为新生代(Eden、Survivor空间)和老年代,新生代对象生命周期短,老年代对象生命周期长。 - 分代收集:不同代有不同...
### JVM常量池详解 #### Class常量池(静态常量池) 在Java程序的编译过程中,每个`.class`文件都会包含一个常量池,这个常量池被称为Class常量池或者静态常量池。它存在于每个`.class`文件的`Constant Pool`部分...
常量池分为静态常量池、运行时常量池、字符串常量池和整型常量池。静态常量池存在于每个*.class文件中,包含了字面量和符号引用,这部分在类加载的链接阶段会被解析成直接引用。运行时常量池则是在虚拟机运行时载入...
总结来说,JVM-CLASS文件分析脑图强调了class文件的结构细节,从文件头的魔术数到后面的常量池、类定义、字段和方法信息,以及属性表等,都是class文件的重要组成部分。通过分析这些结构,我们可以了解class文件如何...
当一个新字符串被创建时,JVM会检查常量池中是否存在相同的字符串,如果存在,就直接返回其引用,否则会在堆中创建一个新的实例并添加到常量池。 垃圾收集(Garbage Collection, GC)是JVM管理内存的重要机制。GC...
5. **方法区(Method Area)/运行时常量池(Runtime Constant Pool)**:存储类的信息,包括类名、方法信息、常量等。这部分内存也参与垃圾回收。 二、内存模型优化 1. **堆内存优化**: - **对象创建优化**:...
- **Constant Pool OutOfMemory**:常量池溢出发生在常量数量过多时。可以通过增大永久代内存(在JDK 8中已改为元空间)来解决。 - **Direct Memory OutOfMemory**:直接内存溢出时发生。可以通过增加`-XX:...
《JVM调优实战与常量池详解》 在Java开发中,JVM(Java虚拟机)的性能优化是一项至关重要的任务。通过对JVM进行调优,我们可以显著提升应用程序的运行效率,减少内存消耗,避免不必要的垃圾回收(GC)带来的性能...
- JDK 1.8:进一步优化了G1,移除了永久代,元空间使用本地内存,字符串常量池从 PermGen 移到了 Heap 的元空间。 4. **JVM参数设置** - `-XX:MaxPermSize`:在JDK 1.7及更早版本中,用于设置永久代的最大大小,...
3. **方法区**:存储类的信息,如类的元数据、常量池等。在Java 8之后被元空间(Metaspace)取代,以减少对持久代的依赖。 4. **程序计数器**:记录当前线程执行的字节码指令地址,用于实现多线程的切换。 二、JVM...
`,JVM会在常量池中查找是否存在"abc",如果存在则直接引用该字符串;如果不存在,则创建一个新的字符串对象并将其放入常量池。这种方式创建的字符串,一旦创建便不会改变,且可以共享。 而使用new关键字创建字符串...
- **方法区**(在Java 8后变为元空间):存储已加载类的信息,如类的元数据、常量池等。 - **虚拟机栈**:每个线程都有一个独立的虚拟机栈,用于存放方法调用时的局部变量表、操作数栈和方法出口等信息。 - **...
3. **方法区**:存储类信息、常量、静态变量等,HotSpot中的永久代被元空间(Metaspace)取代。 4. **本地方法栈**:非Java方法(如C++ Native方法)的调用栈。 5. **垃圾收集**:包括可达性分析算法、标记-清除、...
- **原因**:常量池的容量计数值(constant_pool_count)是从1开始计数的,这意味着索引值范围从1到N。 - **目的**:这样做是为了方便处理一些特殊的情况,比如索引值为0可以表示“不引用任何常量池项目”。 - **...