java8以后字符串常量池的位置,以及元空间的探秘,使用VisualVM进行实战验证

java8以后字符串常量池的位置,以及元空间的探秘,使用VisualVM进行实战验证  在网上看了很多博客,解释也比较多,关于字符串常量池的具体位置难以分辨谁真谁假。  对于jdk8以后的版本有人说字符串常量池在元空间中,也有人说字符串常量池存在堆中。  到底谁说的对?他们的说法有依据吗?  今天让我们来一起探讨一下这个问题有人说字符串常量池在java堆中,可又有人说常量池存在元空间中。分享几篇知乎文章关于jvm运行时数据区的模型:1、面试官|JVM为什么使用元空间替换了永久代?2、Java方法区与元空间为了解决这个问题,下面我们通过Idea、VisualVm

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

  在网上看了很多博客,解释也比较多,关于字符串常量池的具体位置难以分辨谁真谁假。

  对于jdk8以后的版本有人说字符串常量池在元空间中,也有人说字符串常量池存在堆中。

  到底谁说的对?他们的说法有依据吗?


  今天让我们来一起探讨一下这个问题

有人说字符串常量池在java堆中,可又有人说常量池存在元空间中。

分享几篇知乎文章 关于jvm运行时数据区的模型:
1、面试官 | JVM 为什么使用元空间替换了永久代?
2、Java方法区与元空间

在这里插入图片描述


为了解决这个问题,下面我们通过Idea、VisualVm、JDK(我用的是jdk14) 和 一段测试代码来探讨一下字符串常量池的位置

将下面代码粘贴到Idea中

import java.util.ArrayList;

public class StringConstantPoolTest { 
   

    public static void main(String[] args) { 
   
        ArrayList<String> arrayList = new ArrayList<>();
        for (long i = 1; ; i++) { 
   // 死循环
            arrayList.add(String.valueOf(i).intern());
            if (i % 1000_0000 == 0) { 
   
                arrayList.clear();// 清除引用,让前面产生的对象进行回收,因为内存往往不足
                System.out.println(i);
            }
        }
    }
}

在启动参数上面我们设置内存空间的值
一个是设置堆内存最大值1G,另一个是设置元空间内存最大10M

-Xmx1G -XX:MaxMetaspaceSize=10M

在这里插入图片描述

然后我们执行这段程序打开VisualVM看下内存占用,插件市场里点击装上visualGC
下面是i执行到这个值时的的情况
在这里插入图片描述
程序启动后,就需要一直观看我框出来的这些参数
在这里插入图片描述

宿主机内存情况
在这里插入图片描述

在我写的测试代码中,利用死循环,不断向ArrayList中添加对象,然后每隔1000_0000次集合被清空一次,因为考虑内存原因我才在这来这样做,不然内存很快被消耗完,清空引用后内存不足就会进行gc。
arrayList.add(String.valueOf(i).intern());

是利用String的native修饰的intern()方法,将字符串存入常量池中,当然这是有一个过程的,首先需要在堆中创建一个字符串对象也就是String.valueOf(i),因为字符串常量池没有这个字符串对象所以会将堆中这个字符串对象存入字符串常量池中,但是现在我们还不知道常量池的位置


排除字符串常量池在虚拟机栈、程序计数器、本地方法栈的情况(Java虚拟机规范要求的),字符串常量池要么在堆中要么就在方法区中


假设字符串常量池在堆中

  通过看VisualVM我们应该是判断不出字符串常量池是否在堆中的,因为字符串对象也在堆中创建,我们无法根据内存变化判断除字符串常量池在堆中。这种假设就没法继续推断了,进行另外一种假设

假设字符串常量池在元空间

  元空间有一个特点,那就是使用的是本地内存,也就是宿主机的直接内存,如果没有设置最大值10M,那么只受宿主机内存限制。
  通过观察,我们发现元空间不会像堆内存变化那么明显,它是一点一点增加的,而且我们设置的内存最大值才10M,按照old区的变化早就撑爆了,可是并没有发生OOM。

  那么元空间中应该不存在字符串常量池

假设字符串常量池在方法区(元空间的一部分)

  如果字符串常量引用被去除了,那么内存不够会触发gc回收字符串常量池中的对象,下面的测试代码就是想让字符串常量池的对象不被回收(又要保证不OOM导致程序退出终止),如果常量池在方法区,那么方法区应该会增大,那么宿主机的内存就会被使用。

通过两个线程来完成,A线程就是前面的逻辑,而B线程则是从字符串常量池中去取出常量。

import java.util.ArrayList;

public class StringConstantPoolTest { 
   

    public static void main(String[] args) { 
   

        new Thread(()->{ 
   
            ArrayList<String> arrayList = new ArrayList<>();
            for (long i = 1; ; i++) { 
   
                arrayList.add(String.valueOf(i).intern());
                if (i % 1000_0000 == 0) { 
   
                    arrayList.clear();// 清除引用,让前面产生的对象进行回收,因为内存往往不足
                    System.out.println(i);
                }
            }
        },"A").start();

        new Thread(()->{ 
   
            for (long i = 1; ; i++) { 
   
                String.valueOf(i).intern();
                if (i % 1000_0000 == 0) { 
   
                    System.out.println(i);
                }
            }
        },"B").start();
    }
}

测试结果是任务栏管理器的内存没有太大的变化,推测字符串常量池不在方法区。
在看《深入理解java虚拟机》第三版时关于运行时常量池的说明 运行时常量池 != 字符串常量池是两样东西。
在这里插入图片描述

public class TestDemo { 
   
    
    @Test
    public void test01() { 
   
        //
        String str1 = new StringBuilder("hello").append("World").toString();
        System.out.println(str1.intern());
        System.out.println(str1 == str1.intern());

        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern());
        System.out.println(str2 == str2.intern());

        String str3 = new StringBuilder("hello").toString();
        System.out.println(str3.intern());
        System.out.println(str3 == str3.intern());
    }
    
}

为什么打印出

helloWorld
true
java
false
hello
false

  根据VisualVM和宿主机内存的数据,我们大致可以推断出字符串常量池与元空间存在关联关系,因为我们通过intern()会不断往字符串常量池中添加数据,因此存有字符串常量池的那块空间,整体占用空间是不断增大(不考虑GC的情况,GC一般不会发生,只会在需要的时候才会主动进行gc)。

  元空间存的字符串常量只是一个地址引用,因此占用空间很小,10M内存执行了很久都没有报OOM,并且宿主机内存始终在一个稳定值,并没有因为不断添加常量进常量池而导致宿主机内存被占用内存一直增大。

  程序一直执行,元空间最终肯定也会被占满,但关于堆中常量的引用已经被gc回收,那么元空间应该也会回收一部分空间(清除元空间引用关于堆中被gc的对象),然后就会维持在一个值的范围波动起伏而不会一直增然后OOM。

最终结论

  真正意义上字符串常量池在堆中存储,元空间可能有引用堆中字符串常量,运行时常量池在方法区中。

  根据变化情况,推出字符串常量池在堆的old区,字符串在Young的Eden区产生,调用intern()就是先看old区有没有,如果没有将这个对象存入old区中,而字符串之所以通过==比较会返回true可能是jvm底层做的一件事情,移动了对象,再次查找对象时会找到移动后的那个对象(gc也会导致对象的移动,会将Eden区的对象移动到S0或S1,甚至可能移动到Old区。因此gc导致的移动至少jvm是肯定要做处理的,至于怎么处理的则需要继续深入才能探究,故推断intern()方法的作用就是将字符串移动到常量池中)


字符串常量池再深入

运行时常量池的再深入,从jvm的内存分配角度谈谈这道字符串常量池的面试题。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/164769.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • [一个互联网思想信徒]:今天突破69个听众

    [一个互联网思想信徒]:今天突破69个听众

    2021年11月13日
  • 目标检测百度百科_综述的提纲

    目标检测百度百科_综述的提纲目标检测目标检测要解决的核心问题目标检测最新进展目标检测应用

    2022年10月13日
  • Portraiture Mac(PS磨皮滤镜插件) v3.5.1已注册版「建议收藏」

    Portraiture Mac(PS磨皮滤镜插件) v3.5.1已注册版「建议收藏」portraituremac激活成功教程版是大家熟知的一款专业磨皮滤镜插件。本次与大家分享的Portraiture插件Mac激活成功教程版专为photoshop软件设计,功能强大,能够智能的对图像中的肤色、毛发以及眉毛等部位进行滤镜抛光处理,细节处理,以减少瑕疵。portraituremac激活成功教程版基本上是人人都能用得上的ps辅助工具,有了它处理人像效果更加显著。原文及下载地址:www.mac69.c…

  • window10编译器_windows shell编程

    window10编译器_windows shell编程原文地址http://www.cctry.com/forum.php?mod=viewthread&tid=250698&extra=page%3D1&page=1&如何让我们的c++程序可以支持脚本,尤其是支持JavaScript是件很有意思的事情的,那样可以为软件的灵活性,扩展性提供可能。你可能会说用JavaScript引擎,对,JavaScript引擎有很多,有Googlev8,s

    2022年10月10日
  • table array什么意思_html中table属性

    table array什么意思_html中table属性IamcreatingalargeHTMLtableandIhaveproblemwithpagebreaksasyoucanseeinthefollowingimage:Isthereamethodsettledowntheproblemautomatically?Orwhatisthewaytodoit?Tryaddi…

  • 黑客手册中文版_黑客大追踪PDF

    黑客手册中文版_黑客大追踪PDF非安全黑客手册0911PDF电子书目录:新闻时评2颠覆杀毒市场,360强势插入!策划7功夫熊猫Hacker系漫游记4赤龙记得当初阿宝接触网络时,总是喜欢聊天,电脑只要开着,总会发现右下角有一个小企鹅。不知道何时,这个企鹅出现的几率比以往少了很多,但偶尔还是会出来冒个泡。冒泡…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号