`

JVM虚拟机-常量池(十一)

阅读更多

转载:http://www.cnblogs.com/iyangyuan/p/4631696.html

小菜先拙劣的表达一下jvm虚拟内存分布:

jvm虚拟内存分布

 

     程序计数器是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被分配到哪去了,所以地址肯定不同。配上一张简图理清思路:

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

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

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

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

 

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

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

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

 

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

 

实践

     

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

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

 

1 String s = "hi";

 

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

 二进制格式的class文件

 

     简单讲解一下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 = new ArrayList<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可能会略有差异,请读者自行探索。

分享到:
评论

相关推荐

    JVM虚拟机从入门到实战视频教程.zip

    目录网盘文件永久链接 001-JVM课程导读 002-第一章-JVM课程简介 003-虚拟机概念 004-JVM的定义 005-JVM规范 006-JVM产品 ...025-【分析】常量池总数 026-【分析】class文件中的常量 027..............

    深入理解Java虚拟机视频教程(jvm性能调优+内存模型+虚拟机原理)视频教程

    第28节Java内存区域-直接内存和运行时常量池00:15:53分钟 | 第29节对象在内存中的布局-对象的创建00:21:19分钟 | 第30节探究对象的结构00:13:47分钟 | 第31节深入理解对象的访问定位00:08:01分钟 | 第32节垃圾...

    JVM教程吐血整理干货.md

    JVM JVM运行时内存分区 程序计数器 程序计数器的特点 Java虚拟机栈 栈帧 局部变量表 操作数栈 ...(永久代和元空间都是方法区的实现),字符串常量池也移动到了heap空间 jdk8之后的jvm内存分区 程

    深入理解JVM内存结构及运行原理全套视频加资料.txt

     第28讲 Java内存区域-直接内存和运行时常量池 00:15:53  第29讲 对象在内存中的布局-对象的创建 00:21:19  第30讲 探究对象的结构 00:13:47  第31讲 深入理解对象的访问定位 00:08:01  第32讲 垃圾回收-...

    java中常量以及常量池

    1、举例说明 变量 常量 字面量  1 int a=10;  2 float b=1.234f;  3 String c="abc";  4 final long d=10L;  a,b,c为变量,d为常量 两者都是左值;...  运行时常量池:是jvm虚拟机在完成类装

    深入Java虚拟机(原书第2版).pdf【附光盘内容】

    8.1.3 常量池解析 8.1.4 解析constant_class_info入口 8.1.5 解析constant_fieldref_info入口 s.1.6 解析constant_methodref_info入口 8.1.7 解析constant_interface-methodref_info入口 8.1.8 ...

    JVM执行子系统-JVM进阶

    常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项 u2 类型的数据,代 表常量池容量计数值(constant_pool_count)。与 Java 中语言习惯不一样的是,这个容 量计数是从 1 而不是 0 开始的 常量池中主要...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    Java内存区域与内存溢出异常 / 24 2.1 概述 / 24 2.2 运行时数据区域 / 25 2.2.1 程序计数器 / 25 2.2.2 Java虚拟机栈 / 26 2.2.3 本地方法栈 / 27 2.2.4 Java堆 / 27 2.2.5 方法区 / 28 2.2.6 运行时常量池 ...

    深入解析JVM之内存结构及字符串常量池(推荐)

    Java作为一种平台无关性的语言,其主要依靠于Java虚拟机——JVM,接下来通过本文给大家介绍JVM之内存结构及字符串常量池的相关知识,需要的朋友可以参考下

    自己动手写Java虚拟机 张秀宏 著

    高清非扫描版 带目录 SBN:978-7-111-53413-6 目录 前言 第1章 命令行工具 1.1 准备工作 1.1.1 安装JDK 1.1.2 安装Go 1.1.3 创建目录结构 ...3.3 解析常量池 3.3.1 ConstantPool结构体 3.3.2 ConstantInfo接口

    java虚拟机规范(java SE7)

    第二章:java虚拟结构(运行时区域内存:寄存器,java虚拟机栈,java堆,方法去,运行时常量池,本地方法栈); 第三章:为java虚拟机编译; 第四章:Class文件格式; 第五章:加载、链接与初始化

    Java进阶教程解密JVM视频教程

    学习字节码指令的的运行流程,字节码指令与常量池、方法区的关系。掌握条件分支、循环控制、异常处理、构造方法在字节码级别的实现原理,利用HSDB工具理解多态原理。还会涉及从编译期的语法糖处理,到类加载的各个...

    JVM性能优化相关问题-面试-进阶

    Java 类加载需要经历一下 7 个过程: ...主次版本号是否在当前虚拟机范围内,常量池中的常量是否 有不被支持的类型. • 元数据验证:对字节码描述的信息进行语义分析,如这个类是 否有父类,是否集成了不被继承的类等。

    dai147444612#JVM#HotSpot虚拟机对象探秘1

    对象的创建加载:先去检测new指令能否再常量池中定位到一个类的符号引用,如果未被加载、解析、初始化过 执行相应的类加载过程分配内存: 为对象分配空间时采用指针碰

    与我一起学 JVM:Java 虚拟机内存组成概况

    与我一起学 JVM:Java 虚拟机内存组成概念前言Java 虚拟机内存划分运行时数据区域程序计数器Java 虚拟机栈局部变量表本地方法栈Java 堆方法区运行时常量池直接内存 前言 刚开始看《深入理解Java虚拟机》,文章主要就...

    一、JVM内存区域1

    JVM 内存区域JVM 内存区域JVM 运行时内存划分程序计数器Java虚拟机栈本地方法栈方法区运行时常量池直接内存HotSpot 虚拟机对象揭秘对象的创建对象

    Java虚拟机

    2.4.3 方法区和运行时常量池溢出 2.4.4 本机直接内存溢出 2.5 本章小结 第3章 垃圾收集器与内存分配策略 3.1 概述 3.2 对象已死吗 3.2.1 引用计数算法 3.2.2 可达性分析算法 3.2.3 再谈引用 3.2.4 生存...

    HotSpot实战高清版本

    HotSpot 虚拟机的工作原理,将隐藏在它内部的本质内容逐一呈现在读者面前,包 括 OpenJDK 与 HotSpot 项目、编译和调试 HotSpot 的方法、HotSpot 内核结构、Launcher、OOP-Klass 对象表 示系统、链接、运行时数据区...

    关于JVM的总结

    解析:将常量池中的符号引用替换为直接引用的过程,虚拟机不会重新再解析而是通过缓存去拿出解析的数据 初始化:在准备阶段已经赋过一个系统要求的初始值,而在初始化阶段则通过程序制定的主管计划去初始化变量和其他...

    Big-Data-Interview:大数据面试知识点

    Big-Data-InterviewJava开发、大数据...封装、继承和多态Java语言数据类型Java的自动类型转换,强制类型转换String的不可变性、虚拟机的常量池中的String字符串、String.intern()的底层原理Java语言中的关键字:finalJa

Global site tag (gtag.js) - Google Analytics