精华内容
下载资源
问答
  • 最近看java虚拟机,书上说 字符串常量池在jdk1.7移除方法区了,但是运行时常量池还是方法区的一部分,那意思是不是字符串常量池 和 运行时常量池 就是两个东西 ?没有关系?那String的intern()方法是往添加到哪个池?
  • 概述Java运行时常量池是JVM运行内存模型的重要部分.我对常量池的理解大都来自于周志明大大的《深入理解Java虚拟机》, 书中对常量池有较多的描述与解释, 在内存管理, 类文件结构等部分章节中都有说明. 正如我之前...

    概述

    Java运行时常量池是JVM运行时内存模型的重要部分.我对常量池的理解大都来自于周志明大大的《深入理解Java虚拟机》, 书中对常量池有较多的描述与解释, 在内存管理, 类文件结构等部分章节中都有说明.
    正如我之前的博文–JVM内存管理对常量池描述的一样,常量池会存储字面量和符号引用,但我有个疑问:
    常量池与final修饰符的关系是怎么样的?

    Code

    public class Test{
        public static Test test = new Test();
        public final Test test2 = new Test();
    
        public final static String s1 = "1";
        public static String s2 = "2";
        public String s3 = "3";
    
        public Test(){
    
        }
    
        public static void stMethod(){
            final int sla = 10;
            final String sls = "slsv";      
        }
    
        public void tMethod(int tp){
            final int la = 11;
            final String ls = "lsv";
        }
    }

    反编译后的常量池部分代码

    public class Test
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #10.#30        // java/lang/Object."<init>":()V
       #2 = Class              #31            // Test
       #3 = Methodref          #2.#30         // Test."<init>":()V
       #4 = Fieldref           #2.#32         // Test.test2:LTest;
       #5 = String             #33            // 3
       #6 = Fieldref           #2.#34         // Test.s3:Ljava/lang/String;
       #7 = Fieldref           #2.#35         // Test.test:LTest;
       #8 = String             #36            // 2
       #9 = Fieldref           #2.#37         // Test.s2:Ljava/lang/String;
      #10 = Class              #38            // java/lang/Object
      #11 = Utf8               test
      #12 = Utf8               LTest;
      #13 = Utf8               test2
      #14 = Utf8               s1
      #15 = Utf8               Ljava/lang/String;
      #16 = Utf8               ConstantValue
      #17 = String             #39            // 1
      #18 = Utf8               s2
      #19 = Utf8               s3
      #20 = Utf8               <init>
      #21 = Utf8               ()V
      #22 = Utf8               Code
      #23 = Utf8               LineNumberTable
      #24 = Utf8               stMethod
      #25 = Utf8               tMethod
      #26 = Utf8               (I)V
      #27 = Utf8               <clinit>
      #28 = Utf8               SourceFile
      #29 = Utf8               Test.java
      #30 = NameAndType        #20:#21        // "<init>":()V
      #31 = Utf8               Test
      #32 = NameAndType        #13:#12        // test2:LTest;
      #33 = Utf8               3
      #34 = NameAndType        #19:#15        // s3:Ljava/lang/String;
      #35 = NameAndType        #11:#12        // test:LTest;
      #36 = Utf8               2
      #37 = NameAndType        #18:#15        // s2:Ljava/lang/String;
      #38 = Utf8               java/lang/Object
      #39 = Utf8               1
    

    可以看到, 常量池中并没有存储test2的值, 只存储了test2这个符号, 所以引用类型有final修饰符也不会进入常量池, 而没有final修饰符的s2, s3都进入了常量池(当然原因是他们是字面量), 可以看到是否进入常量池与final修饰符是没有关系的.

    展开全文
  • 根据《深入理解Java虚拟机》和网上搜索的资料总结一下这三个的区别 类的常量池(存在于字节码文件中) 类的常量池存在于字节码文件中, 也就是.class文件. 要注意的是, 类的常量池并不在内存中, 而是字节码文件的一段...

    welcome to my blog

    根据《深入理解Java虚拟机》和网上搜索的资料总结一下这三个的区别

    类的常量池(存在于字节码文件中)

    类的常量池存在于字节码文件中, 也就是.class文件. 要注意的是, 类的常量池并不在内存中, 而是字节码文件的一段内容
    常量池中主要存放两大类常量: 字面量(Literal)和符号引用(Symbolic References)
    字面量比较接近于Java语言层面的常量概念, 如文本字符串、使用final修饰的常量值等
    符号引用则余数编译原理方面的概念,主要包括下面几类常量:

    1. 被模块到处或者开放的包(Package)
    2. 类和接口的全限定名(Fully Qualified Name)
    3. 字段的名称和描述符(Descriptor)
    4. 方法的名称和描述符
    5. 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
    6. 动态调用点和动态常量(Dynamically-Computed Call Site\Dynamically-Computed Constant)
      当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中

    运行时常量池 (存在于方法区中)

    运行时常量池位于方法区中,与类的常量池不同的地方在于,类的常量池存在于字节码文件中, 而运行时常量池存在于内存中。
    JVM在类加载阶段将字节码中的常量池内容加载到方法区的运行时常量池中

    类加载的准备阶段为类变量(静态变量)分配内存并设置初始值。从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意的是方法区本身是一个逻辑上的区域,在JDK7及之前,HotSpot使用永久代来实现方法区时,这些变量所使用的内存都应当在方法区中进行分配的说法是正确的;而在JDK8及以后,类变量则会随着Class对象一起存放在Java堆中,这时候“类变量在方法区”就完全是一种对逻辑概念的表述了。

    运行时常量池和字节码文件中的常量池的两个区别

    1. JVM对于字节码文件每一部分(包括常量池)的格式都有严格规定,如每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、加载和执行,但对于运行时常量池,《Java虚拟机规范》并没有做出任何细节的要求,不同的提供商实现的JVM可以按照自己的需要来实现这个内存区域,不过一般来说,除了保存字节码文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中。
    2. 运行时常量池相对于字节码文件常量池的另一个重要特征是具有动态性。Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入字节码文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用的比较多的辨识String类的intern()方法。(这里我有个疑问,intern()涉及的是字符串常量池, 但字符串常量池在堆中,不在运行时常量池啊)

    字符串常量池 (存在于堆中)

    (这里我有个疑问)使用双引号创建的字符串, 比如说String a = “show time”;该字符串以前没有出现过, 那么这个字符串是直接放到字符串常量池中, 还是说先在堆中创建一个对象, 然后字符串常量池使用引用指向堆中的这个对象

    使用String s = new String(“abc”); 创建的字符串对象直接存放到堆中, 不会主动放到字符串常量池中. 可以使用s.intern()方法让常量池使用引用指向这个对象

    局部变量表(存在虚拟机栈中)

    确切地说, 局部变量表存在于虚拟机栈的栈帧中
    执行一个方法时, JVM会为该方法生成一个栈帧, 方法的执行和返回对应着栈帧在虚拟机栈中的的入栈和出栈;
    栈帧不仅包含局部变量表, 还包含操作站,动态链接,返回地址等信息

    例子: 各个变量存在的位置

    public class Free {
        //位于方法区
        public static int m1 = 10;
        //位于方法区
        public static ListNode f1 = new ListNode(11);
    
        public static void main(String[] args) throws ClassNotFoundException {
            //位于字符串常量池
            String a1 = "abc";
            //位于堆
            String a2 = new String("efg");
            //位于堆
            ListNode f2 = new ListNode(12);
            //位于虚拟机栈
            int m2 = 10; 
        }
    }
    
    class ListNode {
        int val;
        ListNode next;
    
        ListNode(int val) {
            this.val = val;
        }
    }
    
    展开全文
  • 这几天在看Java虚拟机方面的知识,看到了有几种不同常量池的说法,然后我就去CSDN、博客园等上找资料,里面说的内容真是百花齐放,各自争艳,因此,我好好整理了一下,将我自认为对的理解写下来与大家共同探讨: ...

    简介:

    这几天在看Java虚拟机方面的知识时,看到了有几种不同常量池的说法,然后我就去CSDN、博客园等上找资料,里面说的内容真是百花齐放,各自争艳,因此,我好好整理了一下,将我自认为对的理解写下来与大家共同探讨:

    在Java的内存分配中,总共3种常量池:

    1.字符串常量池(String Constant Pool):

    1.1:字符串常量池在Java内存区域的哪个位置?

    • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
    • 在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。

    1.2:字符串常量池是什么?

    • 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
    • 在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
    • 在JDK7.0中,StringTable的长度可以通过参数指定:
    -XX:StringTableSize=66666

    1.3:字符串常量池里放的是什么?

    • 在JDK6.0及之前版本中,String Pool里放的都是字符串常量;
    • 在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。关于String在内存中的存储和String#intern()方法的说明,可以参考我的另外一篇博客:

    需要说明的是:字符串常量池中的字符串只存在一份!
    如:

    String s1 = "hello,world!";
    String s2 = "hello,world!";

    即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。(这里具体的字符串如何分配就不细说了,可以看我的另一篇博客)

    2.class常量池(Class Constant Pool):

    2.1:class常量池简介:

    • 我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);
    • 每个class文件都有一个class常量池。

    2.2:什么是字面量和符号引用:

    • 字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
    • 符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

    3.运行时常量池(Runtime Constant Pool):

    • 运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用
    • JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。
    展开全文
  • 在之前在看jvm虚拟机的书,结果看到常量池的时候,看得一脸懵逼,去网上查也是云里雾里.所以这里自己花几天摸清楚后,在这里做个笔记 因为字符串常量池现在网上争议颇多,官方文档也说得很含糊,以下几点并不是很明确: ...

    在之前在看jvm虚拟机的书,结果看到常量池的时候,看得一脸懵逼,去网上查也是云里雾里.所以这里自己花几天摸清楚后,在这里做个笔记


    因为字符串常量池现在网上争议颇多,官方文档也说得很含糊,以下几点并不是很明确:
    比如jdk1.7后的字符串常量池所存储的是否都是引用?还是对象和引用都有?
    jdk1.7后intern方法将字符串放到常量池,到底是在堆中创建对象,然后放的堆中的对象的引用,还是在直接常量池新建一个对象?
    以下若有说得不对之处,欢迎大佬们指出赐教

    1. 几种常量区

    Java中的常量池分为三种类型:

    • 类文件中常量池(The Constant Pool)
    • 运行时常量池(The Run-Time Constant Pool)
    • String常量池

    1.1 类文件中的常量池

    我们写的程序源码经过javac的编译会转变成class类型的文件,也被称为字节码文件,该文件记录了整个程序或者说当前这个类的所有相关信息,其中有一个很重要的部分被称为常量池。

    常量池存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);

    • 字面量:
      • 1.文本字符串
      • 2.八种基本类型的值
      • 3.被声明为final的常量等;
    • 符号引用:
      • 1.类和接口的全限定名(Fully Qualified Name)
      • 2.字段的名称和描述符(Descriptor)
      • 3.方法的名称和描述符

    这里写一个Test.java

    public class Test {  
        private String str = "hello";  
        private int nn = 233;
        private Integer mm = 332;
    
        void aa(){  
            System.out.println(65535);  
        }  
    }
    

    在cmd中, 用javac Test.java编译成class文件,然后再用javap -v Test.class查看,就可以看到有一个常量池,里面正是上面所说的字面量和符号引用

    1.2 运行时常量池

    相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是String类的intern()方法

    • jdk1.6及以下版本:它位于永久代-方法区中
    • jdk1.7,逐步开始抛弃方法区,将字符串常量池移至堆区.这里jdk文档并没有说运行时常量池是否也跟着移到堆区,也就是说运行时常量依然在方法区,永久代仍存在于JDK1.7中

    在这里插入图片描述
    绿色是线程私有,蓝色是线程共享。

    jdk文档:
    https://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

    区域: HotSpot
    概要:在JDK 7中,实现的字符串不再分配在Java堆的永久生成中,而是分配在Java堆的主要部分(称为年轻和老一代),以及另一个应用程序创建的对象。此更改将导致更多数据驻留在主Java堆中,并且永久生成中的数据更少,因此可能需要调整堆大小。由于此更改,大多数应用程序只会看到堆使用中的相对较小的差异,但是加载许多类或大量使用该String.intern()方法的较大应用程序将看到更显着的差异。

    • jdk1.8,JVM移除了永久区,取而代之的是元空间(Metaspace) ,也就是将本地内存用来存储.容量取决于是32位或是64位操作系统的可用虚拟内存大小).这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间.
    1.2.1 运行时常量的包装类

    我们知道,Integer是int的包装类,而包装类是对象,创建对象就需要消耗资源.
    java中的基本类型的包装类基本都实现了常量池技术.

    即Byte,Short,Integer,Long,Character,Boolean。这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据

    但是超出此范围仍然会去创建新的对象。 两种浮点数类型的包装类Float,Double并没有实现常量池技术。

    比如我们运行下面的代码:

    @Test
      public void fun07(){
        Integer a = 10;
        Integer b = 10;
        System.out.println(a == b);
        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d);
        Long e = 200L;
        Long f = 200L;
        System.out.println(e == f);
        Long g = 20L;
        Long h = 20L;
        System.out.println(g == h);
        Double i = 20.0;
        Double j = 20.0;
        System.out.println(i == j);
      }
    

    得到的是:
    true
    false
    false
    true
    false

    需要注意的是:

    • 使用new,仍然会创建新对象. 比如 Integer i1 = new Integer(40);
    • Integer a = 40;Java在编译的时候会直接将代码封装成Integer a =Integer.valueOf(40);,从而使用常量池中的对象。

    1.3 字符串常量池的移动

    在jdk1.6及之前,字符串常量池是属于运行时常量池中的

    jdk1.7 , 也就是上面说的,字符串常量池从方法区中被单独拿到堆中了

    2. 字符串常量池

    1. 字符串常量池,即为了避免多次创建字符串对象,而将字符串在jvm中开辟一块空间,储存不重复的字符串.
    2. 在直接使用双引号""声明字符串的时候, java都会去常量池找有没有这个相同的字符串,如果有,则将常量池的引用返回给变量. 如果没有,会在字符串常量池中创建一个对象,然后返回这个对象的引用
    3. 使用new关键字创建,比如String a = new String("hello");, 这里可能创建两个对象. 一个是用双引号括起来的hello,按照上面的逻辑, 如果常量池没有,创建一个对象. 另一个是必须会创建的,new 关键字必然会在堆中创建一个新对象. 最终返回的是new 关键词创建的对象的地址

    2.1 字符串怎样加入到常量池中?

    这里先说一说String的intern方法,这个方法,能动态的将字符串加入到常量池中.

    /**
         * Returns a canonical representation for the string object.
         * <p>
         * A pool of strings, initially empty, is maintained privately by the
         * class {@code String}.
         * <p>
         * 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.
         * <p>
         * 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}.
         * <p>
         * All literal strings and string-valued constant expressions are
         * interned. String literals are defined in section 3.10.5 of the
         * <cite>The Java&trade; Language Specification</cite>.
         *
         * @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用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后 返回引用。(这里的加入该对象对于java1.7前后的处理方式不同, 往后看)

    看下面这个例子

     @Test
      public void fun06() {
        String str = "aa";
        String str2 = new String("aa");
    
        System.out.println(str == str2);
        String str3 = str2.intern();
        System.out.println(str == str3);
    }
    

    结果是:
    false
    true

    这个下面再解释.

    2.2 不同的字符串创建方式

    下面说的常量池都是字符串常量池

    • 情况一: 直接用双引号声明
    String s1= "ram";
    String s2= "ram";
    String s2= "ram";
    

    当我们第一次执行String s1 =“ram”时,JVM将在常量池中创建一个新对象,s1将引用该对象,即“ram”。
    当我们第二次执行String s2 =“ram”时,JVM将检查字符串常量池中是否存在任何值为“ram”的对象。截至现在是的,我们已经在“字符串常量”池中存在“ram”,因此它不会创建新对象,只是s2引用变量将指向现有的“ram”对象。对于String s3 =“ram”,将发生相同的过程。

    • 情况二: 使用new关键字
    String str1 = new String(“mohan”);
    

    jvm会第一步检查常量池是否有"mohan", 发现没有,创建一个新对象在常量池中.然后因为有new关键词,所以会在堆中创建对象,然后将这个对象的地址引用返回

    • 情况三: 组合
    String st1 =“rakesh”;
    
    String st2 = new String(“rakesh”);
    

    当我们执行String st1 =“rakesh”时,JVM将在常量池中创建一个对象,st1将引用它

    执行第二步的时候,JVM将检查字符串常量池中是否有任何可用的名称为“rakesh”的对象,现在是的,我们已经在字符串常量池中使用了“rakesh”,因此JVM不会在字符串常量池中创建任何对象。
    因为有new关键词,它将在堆中创建一个对象,st2将指向该对象。

    当我们使用new运算符创建String对象时,JVM将首先在SCP(字符串常量池)中检查,该对象是否可用。如果SCP内部没有该对象,JVM将创建两个对象,一个在SCP内部,另一个在SCP外部。但是如果JVM在SCP中找到相同的对象,它只会在SCP外部创建一个对象。

    再看下面的代码

    public class StringExample1 {
    	public static void main(String[] args) {
     
    		String s1 = "india";
    		String s2 = s1 + "is";
    		s1.concat("great");
    		s2.concat(s1);
    		s1 += "country";
    		System.out.println(s1 + "   " + s2);
     
    	}
    }
    

    将总共创建8个对象

    1. "india"会直接在scp(字符串常量池)中创建

    2. s1 + “is”, 此时因为编译器无法知道s1是什么,而字符串String是一个final不可修改的类, 所以这里的 " + " 会被编译成(等同于):String s2 = new StringBuilder("india").append("is").toString();
      我们看看StringBuilder的toString方法:
      在这里插入图片描述
      也就是会堆中重新new一个字符串对象

    3. s1.concat(“great”)也一样,会重新new一个String对象,但是因为"great"是双引号声明的,所以同样在scp中创建一个"great"对象

    4. s2.concat,同理,会重新new一个String对象

    5. s1 += “country” 也就是 s1 = s1 + “country”,和2一样,创建两个对象

    总结

    1. 对于双引号""直接声明的字符串, 比如String a = "aa",会直接在scp中创建对象
    2. 对于两个声明的字符串使用 " + " 拼接, 因为jvm的优化,会将拼接后的结果放入常量池.但是两个声明的字符串不会,(String s = “abc”+ “def”, 会直接生成“abcdef"字符串常量 而不把 “abc” "def"放进常量池)
    3. 对于其中有一个不是声明的字符串,用变量相加的,编译器无法得知结果,会用StringBuilder进行创建新对象,不会将结果放到scp中

    2.3 通过intern来验证

    1.例子一
     @Test
      public void fun06() {
        String str = "aa";
        String str2 = new String("aa");
    
        System.out.println(str == str2);
        String str3 = str2.intern();
        System.out.println(str == str3);
    }
    

    上面2.1的代码,跑的结果是
    false
    true

    第一个相信大家也知道了,String str = “aa”;
    在scp创建了一个对象"aa",
    而String str2 = new String(“aa”);这里其实有两步
    第一步java去scp找"aa",发现scp有"aa"
    第二步,在堆中创建对象.所以两者地址不一样,str == str2 为false.

    第二个,在经过 String str3 = str2.intern();后,intern发现scp已经有"aa"了, 所以直接将scp的地址返回给str3, 所以str == str3都是scp的地址,所以为true.

    2.例子二 两个声明字符串相加,两个值不放到scp中,最终结果放到scp

    jdk1.8环境

    @Test
    	public void fun06() {   
    	String s = new String("a") + new String("b");
    
        String s1 = "ab" + "cd";
        String s2 = s.intern();
        String s3 = "ab";
    
        System.out.println(s == s2);
        System.out.println(s3 == s2);
       
      }
    
    

    结果为true,true
    这是因为第一行代码, 据上面所知,不管"a"和"b",只会在堆中创建"ab"对象
    第二行代码,我们用两个声明字符串相加,可知jvm会优化,直接在scp中创建"abcd"
    第三行代码,s调用intern方法,发现scp没有"ab",将s在堆中的引用地址给s2
    第四行代码,java先去scp找"ab",发现有,直接将其地址返回给s3
    所以s指向堆中的地址,s2也是这个地址,所以相同. s3==s2同理,相同

    • 这里如果将String s3 = "ab"放到前面:
    @Test
    	public void fun06() {   
    	String s = new String("a") + new String("b");
    
        String s1 = "ab" + "cd";
        String s3 = "ab";
        String s2 = s.intern();
    
        System.out.println(s == s2);
        System.out.println(s3 == s2);
       
      }
    

    false
    true

    这是为什么呢?
    第一行代码和第二行代码如上
    第三行代码,java发现scp没有"ab",在scp创建新对象,然后返回地址给s3
    第四行代码,s调用intern方法,发现scp"ab",将s在scp中的引用地址给s2
    所以s是堆中的地址,s2和s3是scp的地址,所以结果是false和true

    • 如果再回到第一次的true,true的代码,这次不调整代码顺序,而是将"ab" + “cd”,两个声明式相加,变成其中一个是变量
    @Test
    	public void fun06() {   
    	String s = new String("a") + new String("b");
    
        String s1 = "ab" + s;
        String s2 = s.intern();
        String s3 = "ab";
    
        System.out.println(s == s2);
        System.out.println(s3 == s2);
       
      }
    

    false
    true

    这是因为s1不再是两个声明式相加,编译器无法得知结果,所以将String s1 = "ab" + s变成:

    String var = "ab";
    String s1 = var + s;
    

    所以"ab"会在scp中,s2拿到的也会是var的地址,所以s == s2 为false

    2.例子二 jdk的变化

    上面说到,jdk1.7以后, scp从方法区移至了堆区,其实改变的不仅仅是位置

    我们看最上面的intern方法的官方注释:

    如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后 返回引用

    这是jdk1.6及之前的,因为当时scp在方法区,也叫非堆区,所以intern的方法,是将该对象加入到常量区中

    但是jdk1.7及以后,scp移至了堆区,字符串的创建也在堆区,为了节省开支,intern方法,不再是把该字符串直接加入scp,而是将其地址引用放到scp

    public static void main(String[] args) {
        String s = new String("1");
        s.intern();
        String s2 = "1";
        System.out.println(s == s2);
    
        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);
    }
    
    

    jdk6 下false false
    jdk7 下false true

    ---------------a.jdk6的情况---------------

    看图:

    下面的图长方形表示变量存储的对象引用地址, 圆圈表示真实对象

    在这里插入图片描述

    注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。

    如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm 区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。

    ---------------b.jdk7以上的情况---------------

    在这里插入图片描述

    • 在第一段代码中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是”11”,但此时常量池中是没有 “11”对象的。

    • 接下来s3.intern();这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个 “11” 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。

    • 最后String s4 = "11"; 这句代码中”11”是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。

    • 再看 s 和 s2 对象。 String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。

    • 接下来String s2 = "1"; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。图中画的很清晰。
      在这里插入图片描述

    • 来看第二段代码,从上边第二幅图中观察。第一段代码和第二段代码的改变就是 s3.intern(); 的顺序是放在String s4 = "11";后了。这样,首先执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。

    • 第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。

    展开全文
  • (静态)常量池:用于存放编译器生成的各种字面量和符号引用(符号引用区别于直接引用,后者在class...相对于静态常量池,运行时常量池具有动态性,在程序运行的时候可能将新的常量放入运行时常量池中,比如使用St...
  • java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分一下这几个...
  • Java常量池(静态常量池与运行时常量池)

    万次阅读 热门讨论 2018-03-02 11:12:50
    Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。 1)所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分...
  • 从String的不同创建方式谈起 字符串常量池、运行时常量池和class文件常量池理解 符号引用和直接引用
  • 我们先来介绍下虚拟机运行时数据区的结构: 我们项目中的每一个线程在运行时,都会有拥有自己独立的栈数据和程序计数器。程序计数器可以看作字节码命令的指示器,记录了下个需要执行的字节码指令,栈数据主要分为...
  • java.lang.outofmemoryerror: pergen space这也间接说明了运行时常量池其实属于方法区的一部分 方法区内存溢出的原因: 不断创建大量的类,导致类信息过多等。比如使用框架或者对代码进行修改的时候,不会对原来...
  • 如果要向运行时常量池中添加内容,最简单的做法就是使用 String.intern()这个 Native 方法。该方法的作用是:如果池中已经包含一个等于此 String 对象的字符串,则返回代表池中这个字符串的String 对象;否则,将此 ...
  • 文章目录引入:方法区常量池概述字符串常量池class常量运行时常量池 这里介绍 字符串常量池、class常量池 和 运行时常量池 这三个常量池的概念。 引入:方法区常量池概述 方法区包含运行时常量池、自动和方法数据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 277,189
精华内容 110,875
关键字:

java运行时常量

java 订阅