广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >java常量池图文详解
  • 464
分享到

java常量池图文详解

java 2016-07-05 10:07:32 464人浏览 才女
摘要

java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出,小菜早就对常量池有所耳闻,这次好好总结一下。推荐:java视频教程JVM虚拟内存分布:程序计数器是jvm执行程序的流水线,存放一些跳转指令,这个太高深,小菜不懂。本地方法栈

java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出,小菜早就对常量池有所耳闻,这次好好总结一下。

推荐:java视频教程

JVM虚拟内存分布:

1.jpg

程序计数器是jvm执行程序的流水线,存放一些跳转指令,这个太高深,小菜不懂。

本地方法栈是jvm调用操作系统方法所使用的栈。

虚拟机栈是jvm执行java代码所使用的栈。

方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。

虚拟机堆是jvm执行java代码所使用的堆。

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

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

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

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

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
          
System.out.println(s1 == s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

首先说明一点,在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被分配到哪去了,所以地址肯定不同。配上一张简图理清思路:

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

3.jpg

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

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

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

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

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

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

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

实践

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

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

 String s = "hi";

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

5.jpg

简单讲解一下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 list = new ArrayList();
        
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区域的知识,请读者自行搜索。

更多java知识请关注Java基础教程栏目。

--结束END--

本文标题: java常量池图文详解

本文链接: https://www.lsjlt.com/news/1844.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java常量池图文详解
    java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出,小菜早就对常量池有所耳闻,这次好好总结一下。推荐:java视频教程jvm虚拟内存分布:程序计数器是jvm执行程序的流水线,存放一些跳转指令,这个太高深,小菜不懂。本地方法栈...
    99+
    2016-07-05
    java
  • Java常量池详解
    目录(1)class常量池(2)运行时常量池 (3)基本类型包装类常量池(4)字符串常量池总结java中有几种不同的常量池,以下的内容是对java中几种常量池的介绍,其中最常见的就是...
    99+
    2022-11-12
  • Java 常量池的实例详解
    Java 常量池的实例详解Java的常量池中包含了类、接口、方法、字符串等一系列常量值。常量池在编译期间就已经确定,并保存在*.class文件中一、对于相同的常量值,常量池中只保存一份拷贝。而且,当一个字符串由多个字符串常量链接而成时,多个...
    99+
    2023-05-31
    java 常量池 ava
  • Java字符串常量池示例详解
    为什么会有常量池的概念? 不知道小伙伴们是否有思考过这个问题? 没有思考也无所谓,小编在这里类比一下,大家就会清晰了。什么是池? 我们听的最多的池,应该是数据...
    99+
    2022-11-12
  • 图文详解Java线程和线程池
    目录一、什么是线程,线程和进程的区别是什么二、线程中的基本概念,线程的生命周期三、单线程和多线程四,线程池的原理解析五,常见的几种线程池的特点以及各自的应用场景总结一、什么是线程,线...
    99+
    2022-11-12
  • 详解JVM之运行时常量池
    目录class文件中的常量池运行时常量池静态常量详解符号引用详解String Pool字符串常量池总结class文件中的常量池 之前我们在讲class文件的结构时,提到了每个clas...
    99+
    2022-11-12
  • Java中常用的日期类图文详解
    目录前言Date为什么Date的大部分方法被弃用注释翻译目前可用方法的测试示例可用方法示例Date小结Calendar简单介绍常用的方法获取实例获取日期里的信息日期的加减与滚动日期的...
    99+
    2022-11-13
    java定义日期类 JAVA定义一个日期类 java的date类
  • Java异常架构和异常关键字图文详解
    目录Java异常简介Java异常架构1. Throwable2. Error(错误)3. Exception(异常)运行时异常编译时异常4. 受检异常与非受检异常受检异常非受检异常J...
    99+
    2022-11-13
  • 深入探索Java常量池
    Java的常量池通常分为两种:静态常量池和运行时常量池静态常量池:class文件中的常量池,class文件中的常量池包括了字符串(数字)字面值,类和方法的信息,占用了class文件的大部分空间。运行时常量池:JVM在完成加载类之后将clas...
    99+
    2023-05-30
    java 常量池 ava
  • Java @Accessors注解图文详解
    目录前言1. @Accessors 源码2. @Accessors 属性说明2.1 fluent 属性2.2 chain 属性2.3 prefix 属性总结前言 在你的工作中,有时候...
    99+
    2023-02-03
    Accessors注解 @accessors注解
  • javaClass文件结构解析常量池字节码
    目录Class文件结构整体结构文件头常量池属性表Code_attribute结构函数表/字段表字节码解析Class文件结构 整体结构 ClassFile { u4 ...
    99+
    2022-11-13
  • Java Spring IOC图文详解
    目录IOC简介耦合与内聚工厂模式发展史IoC入门案例总结IOC简介 IoC:控制反转,即Spring反向控制应用程序所需要的资源(本来在传统模式中,主控权是在类里面的,所以才叫控制反...
    99+
    2022-11-12
  • Java中的堆、栈和常量池怎么理解
    这篇文章主要讲解了“Java中的堆、栈和常量池怎么理解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java中的堆、栈和常量池怎么理解”吧!1.寄存器最快的存储区, 由编译器根据需求进行分配...
    99+
    2023-06-17
  • java学习之JVM运行时常量池理解
    运行时常量池 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息时常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将...
    99+
    2022-11-12
  • 如何解析Java常量池与字符串intern
    这期内容当中小编将会给大家带来有关如何解析Java常量池与字符串intern,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。在Java应用程序运行时,Java虚拟机会保存一份内部的运行时常量池,它区别于cl...
    99+
    2023-06-17
  • java线程池详解
    线程池概述 线程池就是一个管理线程的池子,可以降低创建和销毁线程带来的资源消耗因为线程其实也是一个对象,创建一个对象,需要经过类加载过程,销毁一个对象,需要走GC垃圾回收流程,都是需要资源开销的。 提高响应速度,任务到达了相对于从线程池取线...
    99+
    2015-04-06
    java基础 线程池 java
  • Java中继承图文详解
    java继承与合成基本概念继承:可以基于已经存在的类构造一个新类。继承已经存在的类就可以复用这些类的方法和域。在此基础上,可以添加新的方法和域,从而扩充了类的功能。合成:在新类里创建原有的对象称为合成。这种方式可以重复利用现有的代码而不更改...
    99+
    2019-09-16
    Java
  • java中事件图文详解
    学习java事件之前,对java内部类、java常用组件、容器、布局管理器、java抽象窗口工具包这些东西有一定的了解,结合下面的知识点,可以做一些简单的窗口程序。(推荐:java视频教程)Java语言对事件的处理采用的是授权事件模型。在这...
    99+
    2021-10-07
    java
  • JVM常量池的深入讲解
    提示:这里咱们要说的常量池,常量池就是咱们面试中所说的常量池,谈谈你对常量池的认识?面试官一问咱们就懵逼了,你要记得你脑子中有一张图!!! 剩下的就好办了 提示:请各位大佬批评指正!...
    99+
    2022-11-12
  • Java线程池 ThreadPoolExecutor 详解
    目录一 为什么要使用线程池二 线程池原理详解2.1 线程池核心组成2.2 Execute 原理三 线程池的使用3.1 创建线程池3.1.1 自定义线程池3.1.2 功能线程池3.1....
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作