精华内容
下载资源
问答
  • Java常量,实际上分为两种形态:静态常量和运行时常量。所谓静态常量,即*.class文件中常量,class文件中常量不仅仅包含字符串(数字)字面量,还包含类、方法信息,占用class文件绝大部分空间...

    Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。

    所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。

    而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

    接下来我们引用一些网络上流行的常量池例子,然后借以讲解。

    Strings1 = “Hello”; Strings2 = “Hello”; Strings3 = “Hel”+ “lo”; Strings4 = “Hel”+ newString(“lo”); Strings5 = newString( “Hello”); Strings6 = s5.intern(); Strings7 = “H”; Strings8 =“ello”; Strings9 = s7 + s8; System.out.println(s1 == s2); // trueSystem.out.println(s1 == s3);// trueSystem.out.println(s1 == s4); // falseSystem.out.println(s1 == s9); // falseSystem.out.println(s4 == s5); // falseSystem.out.println(s1 == s6); // trueJava学习交流QQ群: 589809992我们一起学Java!

    首先说明一点,在java 中,直接使用==操作符,比较的是两个字符串的引用地址,并不是比较内容,比较内容请用String.equals()。

    s1 == s2这个非常好理解,s1、s2在赋值时,均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一个内存地址,所以相等。

    s1 == s3这个地方有个坑,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = “Hel” + “lo”;在class文件中被优化成String s3 = “Hello”;,所以s1 == s3成立。

    s1 == s4当然不相等,s4虽然也是拼接出来的,但new String(“lo”)这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。配上一张简图理清思路:

    cf5ef5e91799571080e9b73c1bc9a573.png

    s1 == s9也不相等,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,所以不做优化,等到运行时,s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同。

    d3148d779ce2c22fe24bfa0848c798cc.png

    s4 == s5已经不用解释了,绝对不相等,二者都在堆中,但地址不同。

    s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。

    至此,我们可以得出三个非常重要的结论:

    必须要关注编译期的行为,才能更好的理解常量池。

    运行时常量池中的常量,基本来源于各个class文件中的常量池。

    程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。

    以上所讲仅涉及字符串常量池,实际上还有整型常量池、浮点型常量池等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,只有这个范围的数字可以用到常量池。

    实践

    说了这么多理论,接下来让我们触摸一下真正的常量池。

    前文提到过,class文件中存在一个静态常量池,这个常量池是由编译器生成的,用来存储java源文件中的字面量(本文仅仅关注字面量),假设我们有如下java代码:

    1Strings = “hi”;

    为了方便起见,就这么简单,没错!将代码编译成class文件后,用winhex打开二进制格式的class文件。如图:

    e00ab41280cf873b9ba07da59da9e829.png

    简单讲解一下class文件的结构,开头的4个字节是class文件魔数,用来标识这是一个class文件,说白话点就是文件头,既:CA FE BA BE。

    紧接着4个字节是java的版本号,这里的版本号是34,因为笔者是用jdk8编译的,版本号的高低和jdk版本的高低相对应,高版本可以兼容低版本,但低版本无法执行高版本。所以,如果哪天读者想知道别人的class文件是用什么jdk版本编译的,就可以看这4个字节。

    接下来就是常量池入口,入口处用2个字节标识常量池常量数量,本例中数值为00 1A,翻译成十进制是26,也就是有25个常量,其中第0个常量是特殊值,所以只有25个常量。

    常量池中存放了各种类型的常量,他们都有自己的类型,并且都有自己的存储规范,本文只关注字符串常量,字符串常量以01开头(1个字节),接着用2个字节记录字符串长度,然后就是字符串实际内容。本例中为:01 00 02 68 69。

    接下来再说说运行时常量池,由于运行时常量池在方法区中,我们可以通过jvm参数:-XX:PermSize、-XX:MaxPermSize来设置方法区大小,从而间接限制常量池大小。

    假设jvm启动参数为:-XX:PermSize=2M -XX:MaxPermSize=2M,然后运行如下代码:

    //保持引用,防止自动垃圾回收List《String》list=newArrayList 《String》(); int i =0; while(true){ //通过intern方法向常量池中手动添加常量list.add( String.valueOf(i ++) .intern()); }

    程序立刻会抛出:ExcepTIon in thread “main” java.lang.outOfMemoryError: PermGen space异常。PermGen space正是方法区,足以说明常量池在方法区中。

    在jdk8中,移除了方法区,转而用Metaspace区域替代,所以我们需要使用新的jvm参数:-XX:MaxMetaspaceSize=2M,依然运行如上代码,抛出:java.lang.OutOfMemoryError: Metaspace异常。同理说明运行时常量池是划分在Metaspace区域中。具体关于Metaspace区域的知识,请读者自行搜索。

    本文所有代码均在jdk7、jdk8下测试通过,其他版本jdk可能会略有差异,请读者自行探索。

    参考文献:《深入理解java虚拟机———jvm高级特性与最佳实践》

    展开全文
  • Java常量分为三类:字符串常量、class常量、运行时常量字符串常量从1.7及其之后,字符串常量从方法区移到了堆中字符串池的实现——StringTableString类中并没有Integer中IntegerCache对象,String...

    Java中的常量池分为三类:字符串常量池、class常量池、运行时常量池

    字符串常量池

    从1.7及其之后,字符串常量池从方法区移到了堆中

    字符串池的实现——StringTable

    String类中并没有Integer中IntegerCache对象池,String中有native方法intern()。

    intern

    intern()方法作用是:若字符串常量池中存在(通过#equals(Object)来判定是否存在)该字符串,则直接返回,否则,将该String对象添加到池中并返回它的引用。

    /**

    * Returns a canonical representation for the string object.

    *

    * A pool of strings, initially empty, is maintained privately by the

    * class {@code String}.

    *

    * When the intern method is invoked, if the pool already contains a

    * string equal to this {@code String} object as determined by

    * the {@link #equals(Object)} method, then the string from the pool is

    * returned. Otherwise, this {@code String} object is added to the

    * pool and a reference to this {@code String} object is returned.

    *

    * It follows that for any two strings {@code s} and {@code t},

    * {@code s.intern() == t.intern()} is {@code true}

    * if and only if {@code s.equals(t)} is {@code true}.

    *

    * All literal strings and string-valued constant expressions are

    * interned. String literals are defined in section 3.10.5 of the

    * The Java™ Language Specification.

    *

    * @return a string that has the same contents as this string, but is

    * guaranteed to be from a pool of unique strings.

    */

    public native String intern();

    intern()

    class常量池

    class文件是一组以字节为单位的二进制数据流,当java代码被编译为.class文件格式时,二进制数据存放在磁盘中,其中就包括class文件常量池。

    class常量池包含字面量和符号引用

    1f7c61712d61

    image.png

    public class HelloWorld {

    public static void main(String[] args) {

    String s = "Hollis";

    }

    }

    javac 生成HelloWorld.class文件,然后javap -v HelloWorld.class

    常量池内容如下:

    1f7c61712d61

    image.png

    Java代码在编译时,并不会“拼接”好完整执行文件,而是在虚拟机加载class文件时动态连接。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址中。

    运行时常量池

    Java1.7之前是在方法区,也处于永久代中;Java1.7因为使用永久代存在内存泄漏问题,将永久代中的运行时常量池移动到堆内存中;Java1.8是在元空间。

    只有class文件中的常量池肯定是不够的,因为我们需要在JVM中运行起来。这时候就需要一个运行时常量池,为JVM的运行服务。

    运行时常量池跟class文件的常量池一一对应,它是根据class常量池来构建的。

    运行时常量池分两种类型:符号引用和静态常量

    String s = "a";

    s就是符号引用,需要在运行期进行解析,而a是静态常量,它是不会发生变化的。

    静态常量详解

    运行时常量池中的静态常量是从class文件中的constant_pool构建的,可以分为两部分:String常量和数字常量。

    String常量

    String常量是对String对象的引用,是从class中的CONSTANT_String_info结构构建的:

    CONSTANT_String_info {

    u1 tag;

    u2 string_index;

    }

    tag是结构体的标记,string_index是string在class常量池中的index。

    string_index对应的class常量池的内容是一个CONSTANT_Utf8_info结构体。

    CONSTANT_Utf8_info {

    u1 tag;

    u2 length;

    u1 bytes[length];

    }

    CONSTANT_Utf8_info是啥呢?它就是要创建的String对象的变种UTF-8编码。

    我们知道unicode的范围是从0x0000 至 0x10FFFF。

    变种UTF-8就是将unicode进行编码的方式。那是怎么编码呢?

    1f7c61712d61

    image.png

    上面这个图说实话,我没怎么看懂,TODO待学习

    CONSTANT_String_info运行时String常量的规则(inter):

    如果String.intern之前被调用过,并且返回的结果和CONSTANT_String_info中保存的编码是一致的话,表示他们指向的是同一个String的实例。

    如果不同的话,那么会创建一个新的String实例,并将运行时String常量指向该String的实例。最后会在这个String实例上调用String的intern方法。调用inter方法主要是将这个String实例加入字符串常量池。

    数字常量

    数字常量是从class文件中的CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info和 CONSTANT_Double_info 构建的。

    符号引用详解

    符号引用也是从class中的constant_pool中构建的。

    对class和interface的符号引用来自于CONSTANT_Class_info。

    对class和interface中字段的引用来自于CONSTANT_Fieldref_info。

    class中方法的引用来自于CONSTANT_Methodref_info。

    interface中方法的引用来自于CONSTANT_InterfaceMethodref_info。

    对方法句柄的引用来自于CONSTANT_MethodHandle_info。

    对方法类型的引用来自于CONSTANT_MethodType_info。

    对动态计算常量的符号引用来自于CONSTANT_MethodType_info。

    对动态计算的call site的引用来自于CONSTANT_InvokeDynamic_info。

    展开全文
  • 最近学习了JVM原理,遇到了运行时常量的... 个人认为,静态常量是针对每个被加载进入内存的class文件解析后,存放各个字面量值,符号引用的数据,而运行时常量区就是把所有的静态常量的数据汇总到一起(模糊来说)...

    最近学习了JVM原理,遇到了运行时常量池的区域定义,他是属于JVM运行时内存模型方法区中的一部分,总体分布如下图:

    如下图:整体分布如下

    ac51cb0e4524d607647884338d2301cf.png

    而运行、静态常量池是属于方法区的一部分,一般我通常说的是运行时的常量区,他跟静态常量区区别是什么?

    9e093ee19699631673673cf8f44ba517.png

    个人认为,静态常量池是针对每个被加载进入内存的class文件解析后,存放各个字面量值,符号引用的数据,而运行时常量区就是把所有的静态常量的数据汇总到一起(模糊来说)。

    658a714fd5acac896f15a8b87a0a1013.png

    更底层到汇编层面来说,内存中的各个数据没有类这个概念,每次都是一个类一个方法的调用,就是相当于寄存器的相对变址寻址过程,

    8987c14f77f5a0f707b8c1d24a300fb2.png

    f5ae220174345680776c41efc7f3e363.png

    (上图中#1 #2等是否就是计算实际地址的符号哪?)

    运行时方法区就是把每个类的唯一标识作为他的段地址(DS),而内部的各个变量字段方法等都是偏移地址(BX),等到真正入栈执行时候这些字段方法的相对定位符等被解析成为真正的地址,从而进入CS IP被识别执行;那么进一步猜想java的权限包的概念是否也可以由此得到全部的类信息汇总后进行进一步控哪

    展开全文
  • Java常量,实际上分为两种形态:静态常量和运行时常量。 所谓静态常量,即*.class文件中常量,class文件中常量不仅仅包含字符串(数字)字面量,还包含类、方法信息,占用class文件绝大部分...

    Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。

    1. 所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
    2. 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

    运行时常量池相对于Class文件常量池的另外一个重要特征就是具备动态性,Java语言并不要求常量一定只有在编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法.其中intern()方法描述如下:

    既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

    展开全文
  • 一、数据保存地方:1、 寄存器:这是最快保存区域,因为它位于和其他所有保存方式不同地方:处理器内部。然而,寄存器数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接控制权,也不可能...
  • 文章目录Java中,静态成员存储位置JDK1.8以前JDK1.8以后Java中,常量池的存储位置JDK1.6及以前JDK1.7JDK1.8以后 Java中,静态成员存储位置 JDK1.8以前 在JDK1.8以前,静态成员存储在方法区(永久代)中,此时...
  • 静态)常量:用于存放编译器生成各种字面量和符号引用(符号引用区别于直接引用,后者在class字节码文件被虚拟机解析之后,符号引用将被替换为直接引用)。 运行时常量:(静态)常量内容在类加载...
  • Java常量静态常量与运行时常量

    万次阅读 热门讨论 2018-03-02 11:12:50
    Java常量,实际上分为两种形态:静态常量和运行时常量。 1)所谓静态常量,即*.class文件中常量,class文件中常量不仅仅包含字符串(数字)字面量,还包含类、方法信息,占用class文件绝大部分...
  • 在这里首先明确,静态static关键字和jvm虚拟机常量是两个概念。只是static关键字修饰常量...由final和static共同修饰静态常量如下:测试java静态常量和静态变量区别样例,表明两者加载时区别。Static...
  • 从人工到自动化,从重复到创新,技术演进历程中,伴随着开发者 工具 类产品发展。阿里巴巴将自身在各类业务场景下技术积淀,通过开源、云上实现或工具等形式对外开放,本文将精选了一些阿里巴巴开发者工具,...
  • java的常量

    2020-11-03 10:36:31
    java的常量有三个概念,静态常量,运行时常量,字符串常量 JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。 静态常量用于存放编译期生成的各种字面量和符号...
  • Java常量,实际上分为两种形态:静态常量和运行时常量。1)所谓静态常量,即*.class文件中常量,class文件中常量不仅仅包含字符串(数字)字面量,还包含类、方法信息,占用class文件绝大部分...
  • 字符串常量(String Poll) java6: 存在于永久代中。 字符串常量保存是字符串常量。... java7: ... 字符串常量是字符串常量和堆内字符串对象引用。...静态常量 zld自身理解:java编译后生成...
  • 1、什么是常量 ...Java常量,实际上分为两种形态:静态常量和运行时常量。 1)所谓静态常量,即*.class文件中常量,class文件中常量不仅仅包含字符串(数字)字面量,还包含类、方法...
  • public static final String A; // 常量A public static final String B; // 常量B public static final String C;...C是新对象引用,而D是在常量常量引用 C 不可以在编译时确定下来吗?
  • 以及创建和销毁时间,有区域随着虚拟机进程启动而存在,有些区域则是依赖用户线程启动和结束而建立和销毁 (2)运行时数据区包含内容程序计数器概述当前线程所执行字节码行号指示器执行Java方法时表示字节...
  • 简单了解一下java虚拟机--jvm几个内存区域:方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,常量:常量是方法区的一部分,主要用来存放常量和类中的符号...
  • java 静态源码阅读之 druid 连接 为什么会有连接 作为一个后端开发,我们日常中要去请求各种各样外部资源。例如去做数据库请求查询数据,去做http请求调用其他接口。所有这些,都是需要本地应用与其他服务器...
  • Java的常量

    2017-10-14 16:22:47
    ava中常量,实际上分为两种形态:静态常量和运行时常量。  所谓静态常量,即*.class文件中常量,class文件中常量不仅仅包含字符串(数字)字面量,还包含类、方法信息,占用class文件绝大部分...
  • Java虚拟机内存分区:Java栈、堆、方法区、本地方法栈、PC寄存器。还有一个常量池的概念,虚拟机会为每种类型分配一个常量,而不是实例。例如有一个类有很多子类,那么在父类定义final变量,在子类是不能被...
  • java 栈,堆,静态域,常量 (2010-04-11 20:58:42) 转载▼ 标签: 杂谈   1.寄存器:最快存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型变量...
  • 聊聊Java的常量

    2018-12-28 14:42:00
    Java常量,实际上分为两种形态:静态常量和运行时常量静态常量 class文件中常量,class文件中常量不仅仅包含字符串(数字)字面量,还包含类、方法信息,占用class文件绝大部分空间。 运行...
  • 1.寄存器:最快存储区, 由编译器根据需求进行分配,我们在程序中无法控制.2. 栈:存放基本类型变量...4. 静态域:存放静态成员(static定义)5. 常量:存放字符串常量和基本类型常量(public static final)。6. ...
  • 方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域, 常量:常量是方法区的一部分,主要用来存放常量和类中的符号引用等信息。 堆区:用于存放类的对象...
  • Java的常量通常分为两种:静态常量和运行时常量池静态常量:class文件中的常量,class文件中的常量包括了字符串(数字)字面值,类和方法的信息,占用了class文件的大部分空间。运行时常量:JVM在完成加载...
  • java堆,栈,静态域,常量

    千次阅读 2017-08-17 10:24:54
    静态域:存放在对象中用static定义的静态成员 常量:存放常量 非RAM(随机存取存储器)存储:硬盘等永久存储空间 Java内存分配中的栈栈中主要存放两种数据类型: 在函数中定义的一些基本类型的变量数据,也就是局部...
  • 程序计数器可以看作字节码命令指示器,记录了下个需要执行字节码指令,栈数据主要分为本地方法栈和java虚拟机栈。java虚拟机栈就是用来处理我们程序中代码生成字节码。 我们程序中每个方法在执行时都会被...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,005
精华内容 1,202
关键字:

java的静态池

java 订阅