为了账号安全,请及时绑定邮箱和手机立即绑定

请教大神,当Java运行时常量池是什么?

/ 猿问

请教大神,当Java运行时常量池是什么?

幕布斯5086720 2019-11-11 20:09:35

Java运行时常量池是什么?


查看完整描述

3 回答

?
慕田峪4524236

class文件中,“常量池”是最复杂也最值得关注的内容。
  Java是一种动态连接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值还,还包含一些以文本形式出现的符号引用,比如:
  类和接口的全限定名;
  字段的名称和描述符;
  方法和名称和描述符。
  在C语言中,如果一个程序要调用其它库中的函数,在连接时,该函数在库中的位置(即相对于库文件开头的偏移量)会被写在程序中,在运行时,直接去这个地址调用函数
  而在Java语言中不是这样,一切都是动态的。编译时,如果发现对其它类方法的调用或者对其它类字段的引用的话,记录进class文件中的,只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。
  所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很非富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。
java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,不同于使用new关键字创建的对象所在的堆空间。本文只从java使用者的角度来探讨java常量池技术,并不涉及常量池的原理及实现方法。个人认为,如果是真的专注java,就必须对这些细节方面有一定的了解。但知道它的原理和具体的实现方法则不是必须的。
常量池中对象和堆中的对象

[java] view plain copy
public class Test{

Integer i1=new Integer(1);
Integer i2=new Integer(1);
//i1,i2分别位于堆中不同的内存空间

System.out.println(i1==i2);//输出false

Integer i3=1;
Integer i4=1;
//i3,i4指向常量池中同一个内存空间

System.out.println(i3==i4);//输出true

//很显然,i1,i3位于不同的内存空间

System.out.println(i1==i3);//输出false

}

8种基本类型的包装类和对象池

java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。以下是一些对应的测试代码:

[java] view plain copy
public class Test{

public static void main(String[] args){

//5种整形的包装类Byte,Short,Integer,Long,Character的对象,

//在值小于127时可以使用常量池

Integer i1=127;

Integer i2=127;

System.out.println(i1==i2)//输出true

//值大于127时,不会从常量池中取对象

Integer i3=128;

Integer i4=128;

System.out.println(i3==i4)//输出false

//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也实现了常量池技术

String类也是java中用得多的类,同样为了创建String对象的方便,也实现了常量池的技术,测试代码如下:
[java] view plain copy
public class Test{

public static void main(String[] args){

//s1,s2分别位于堆中不同空间

String s1=new String("hello");

String s2=new String("hello");

System.out.println(s1==s2)//输出false

//s3,s4位于池中同一空间

String s3="hello";

String s4="hello";

System.out.println(s3==s4);//输出true

}

}
最后
细节决定成败,写代码更是如此。
在JDK5.0之前是不允许直接将基本数据类型的数据直接赋值给其对应地包装类的,如:Integer i = 5;

但是在JDK5.0中支持这种写法,因为编译器会自动将上面的代码转换成如下代码:Integer i=Integer.valueOf(5);

这就是Java的装箱.JDK5.0也提供了自动拆箱. Integer i =5; int j = i;

Integer的封装:

[java] view plain copy
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);
}

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);
}
}

由于cache[]在IntegerCache类中是静态数组,也就是只需要初始化一次,即static{......}部分,所以,如果Integer对象初始化时是-128~127的范围,就不需要再重新定义申请空间,都是同一个对象---在IntegerCache.cache中,这样可以在一定程度上提高效率。



查看完整回答
反对 回复 2019-11-16
?
互换的青春

(1).程序计数器
是一块较小的内存空间,其作用可以看作是当前线程所执行的字节码的行号指示器,字节码解析器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令。程序的分支、循环、跳转、异常处理以及线程恢复等基础功能都是依赖程序计数器来完成。
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间片来实现,在任何一个时刻,一个处理器只会执行一条线程指令,因此,为了确保线程切换之后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,因此程序计数器是线程私有的内存。
程序计数器是java虚拟机中唯一一个没有规定任何内存溢出OutOfMemoryError的内存区域。
(2).java虚拟机栈:
Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法被执行时都会同时创建一个栈帧用于存放局部变量表、操作数栈、动态连接和方法出口等信息。每个方法被调用直至执行完成过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。
Java虚拟机栈的局部变量表存放了编译器可知的8种java基本类型数据、对象引用(注意不是对象实例本身)、方法返回地址returnAddress。
Java虚拟机栈的局部变量表空间单位是槽(Slot),其中64位长度的double和long类型会占用两个slot,其余的数据类型只占用一个slot。局部变量表所需内存空间在编译期间完成分配,当进入一个方法时,该方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
Java虚拟机栈有两种异常状况:如果线程请求的栈深度大于虚拟机所允许的最大深度时,抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够内存时会抛出OutOfMemoryError异常。
(3).本地方法栈:
本地方法栈与java虚拟机栈作用非常类似,其区别是:java虚拟机栈是为虚拟机执行java方法服务,而本地方法栈是为虚拟机调用的操作系统本地方法服务。
Java虚拟机规范没有对本地方法栈的实现和数据结构做强制规定,Sun HotSpot虚拟机直接把java虚拟机栈和本地方法栈合二为一。
与java虚拟机栈类似,本地方法栈也会抛出StackOverflowError异常和OutOfMemoryError异常。
(4).堆:
堆是java虚拟机所管理的内存区域中最大一块,java堆是被所有线程所共享的一块内存区域,在java虚拟机启动时创建,堆内存的唯一目的就是存放对象实例。几乎所有的对象实例都是在堆分配内存
Java堆是垃圾收集器管理的主要区域,从垃圾回收的角度看,由于现在的垃圾收集器基本都采用的是分代收集算法,因此java堆还可以初步细分为新生代和年老代。
Java虚拟机规范规定,堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。在实现上即可以是固定大小的,也可以是可动态扩展的。如果在堆中没有内存完成实例分配,并且堆大小也无法在扩展时,将会抛出OutOfMemoryError异常。
(5).方法区:
方法区与堆一样,是被各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是方法区却有一个别名叫Non-Heap(非堆)。
Sun HotSpot虚拟机把方法区叫永久代(Permanent Generation),方法区中最重要的部分是运行时常量池。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面变量、符号引用、直接引用等,这些内容将在类加载后存放到方法区的运行时常量池中,另外在运行期间也可以将新的常量存放到常量池中,如String的intern()方法。
方法区和运行时常量池在无法满足内存分配时,也会抛出OutOfMemoryError异常。
(6).直接内存:
直接内存并不是java虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是在java开发中还是会使用到。
JDK1.4中新引入的NIO(new I/O),引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,可以使用操作系统本地方法库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为堆外直接内存的引用进行操作,避免了java堆内存和本地直接内存间的数据拷贝,可以显著提高性能。
虽然直接内存并不直接收到java虚拟机内存影响,但是如果java虚拟机各个内存区域总和大于物理内存限制,从而导致直接内存不足,动态扩展时也会抛出OutOfMemoryError异常。
java虚拟机内存结构中的程序计数器、虚拟机栈和本地方法栈这三个区域随线程创建而生,随线程销毁而灭,因此这三个区域的内存分配和回收是确定的,java垃圾收集器重点关注的是java虚拟机的堆内存和方法区内存。



查看完整回答
反对 回复 2019-11-16
?
Cats萌萌

可能原因一:java类中没有main方法。必须有下面的main方法执行入口才可以:

public static void main(String[] args) {

}

可能原因二:说明当前的路径不是java文件存放路径。解决办法:

第一步:在cmd上通过cd进入相应java类路径。如(cd  D:\Remit\icardpay-boss);

第二步:直接输入盘符即可完成路径切换(如 D:)


查看完整回答
反对 回复 2019-11-16

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信