Start with JVM

周志明先生著-《深入理解Java虚拟机》,书买回来好几天了,但是最近才准备开始搞一搞了(哭瞎…..)。首先是第一章的Java以及JVM发展历史,大概知道了现行的应用最广泛的Java虚拟机是HotSpot,当然一些商业公司也有使用自己的虚拟机。

JVM运行时数据区

这是放在Java内存区域与内存溢出异常里面的必备知识,描述了Java虚拟机在运行时的数据区域
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

私有
- 程序计数器:记录当前线程所执行字节码的行号指示器
- 虚拟机栈:存放了当前线程调用方法的局部变量表、操作数栈、动态链接、方法返回值等信息(可以理解为线程的栈)
- 本地方法栈:为虚拟机使用的Native方法提供服务,后多与JVM Stack合并为一起

共享
- Java堆:占据了虚拟机管理内存中最大的一块(没想到吧),唯一目的就是存放对象实例(与引用是两个概念),也是垃圾回收器主要管理的地方,故又称GC堆。先开坑,后面讲垃圾回收机制再详述
- 方法区:存储加载的类信息、常量区、静态变量、JIT(即时编译器)处理后的数据等,类的信息包含类的版本、字段、方法、接口等信息。需要注意是常量池就在方法区中,也是我们这次需要关注的地方。

提一下这个Native方法

指得就是Java程序调用了非Java代码,算是一种引入其它语言程序的接口

看一下方法区

方法区因为总是存放不会轻易改变的内容,故又被称之为“永久代”。HotSpot也选择把GC分代收集扩展至方法区,但也容易遇到内存溢出问题。可以选择不实现垃圾回收,但如果回收就主要涉及常量池的回收和类的卸载(这里开坑,后续补上链接)

运行时常量池

回归本次讨论正题,主要是在看Java和C++的一些原理时,老是有“常量池”这个我一知半解的讨厌的字词,烦的一批,今天我就来探一探究竟。

JVM中运行时常量池在方法区中,因为是建立在JDK1.7/1.8的基础上来研究这个,所以我先认为String常量池在堆中。Class文件中除了类的版本、字段、方法、接口等描述信息,还有常量池,用于存放编译期生成的各种字面量和符号引用

运行时常量池与Class文件常量池区别

  • JVM对Class文件中每一部分的格式都有严格的要求,每一个字节用于存储那种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行;但运行时常量池没有这些限制,除了保存Class文件中描述的符号引用,还会把翻译出来的直接引用也存储在运行时常量区
  • 相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是String类的intern()方法
  • 在方法区中,常量池有运行时常量池和Class文件常量池;但其中的内容是否完全不同,暂时还未得知

String.intern()

检查字符串常量池中是否存在String并返回池里的字符串引用;若池中不存在,则将其加入池中,并返回其引用。
这样做主要是为了避免在堆中不断地创建新的字符串对象

那class常量池呢?

具体的等分析到Class文件格式再来填这个坑,先来看常量池中的内容:

看一下dalao的博客Class文件中常量池详解

看一看String常量池(的特殊姿势)吧

在研究这个的时候我也上网看了别人的博客,有的人做出了实验,我也试一下

实验一

  1. public class Test{
  2. public static String a = "a";
  3. public static void main(){
  4. String b = "b";
  5. }
  6. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用Java自带的反编译工具反编译一下,编译后输入javap -verbose Test.cass


可以发现两个静态String变量都放入了常量池中

实验二

  1. public class Test2{
  2. public static String str = "laji" + "MySQL";
  3. public static void main(){
  4. }
  5. }
  • 1
  • 2
  • 3
  • 4
  • 5

在编译前先分析一波,按理说,既然是静态String常量,那么理应出现在常量池(Constant Pool)中,但

来看看进阶版的Test2_2

  1. public class Test2_2{
  2. public static void main(String[] args){
  3. String string1 = "laji";
  4. String string2 = "MySQL";
  5. String string3 = string1+string2;
  6. String string4 = string1+"C";
  7. }
  8. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这个的结果就更有意思了↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

总商量个实验,可以看出

  • 对于直接做+运算的两个字符串(字面量)常量,并不会放入String常量池中,而是直接把运算后的结果放入常量池中
  • 对于先声明的字符串字面量常量,会放入常量池,但是若使用字面量的引用进行运算就不会把运算后的结果放入常量池中了
  • 总结一下就是JVM会对String常量的运算进行优化,未声明的,只放结果;已经声明的,只放声明

实验三

  1. public class Test3{
  2. public static void main(String[] args){
  3. String str = "laji";
  4. String str2 = new String("MySQL");
  5. String str3 = new String("laji");
  6. System.out.println(str==str3);// 运行后结果为false
  7. }
  8. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

结果为:

这个实验三包含了很多内容,首先是new一个对象时,明明是在堆中实例化一个对象,怎么会出现常量池中?

  • 这里的"MySQL"并不是字符串常量出现在常量池中的,而是以字面量出现的,实例化操作(new的过程)是在运行时才执行的,编译时并没有在堆中生成相应的对象
  • 最后输出的结果之所以是false,就是因为str指向的”laji”是存放在常量池中的,而str3指向的”laji”是存放在堆中的,==比较的是引用(地址),当然是false

实验四

主要是为了解释一下intern()方法的用处

  1. public class Test4{
  2. public static void main(String[] args){
  3. String str = "laji";
  4. String str2 = new String("laji");
  5. String str3 = null;
  6. System.out.println(str==str2);// 运行后结果为false
  7. str3 = str2.intern();
  8. System.out.println(str==str3);// 运行后结果为true
  9. }
  10. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

显然,str3在初始化的时候是从字符串常量池中获取到的值

String常量池随JDK的改变

JDK1.7中JVM把String常量区从方法区中移除了;JDK1.8中JVM把String常量池移入了堆中,同时取消了“永久代”,改用元空间代替(Metaspace)

  1. import java.util.ArrayList;
  2. public class TestString {
  3. public static void main(String[] args) {
  4. String str = "abc";
  5. char[] array = {'a', 'b', 'c'};
  6. String str2 = new String(array);
  7. //使用intern()将str2字符串内容放入常量池
  8. str2 = str2.intern();
  9. //这个比较用来说明字符串字面常量和我们使用intern处理后的字符串是在同一个地方
  10. System.out.println(str == str2);
  11. //那好,下面我们就拼命的intern吧
  12. ArrayList<String> list = new ArrayList<String>();
  13. for (int i = 0; i < 10000000; i++) {
  14. String temp = String.valueOf(i).intern();
  15. list.add(temp);
  16. }
  17. }
  18. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

这个实验最早是2014年有人实验过的,ta得出的结论是Exception in thread "main" java.lang.OutOfMemoryError: PermGen space,然而时至今日,我自己按照ta的代码跑了一遍,并没有出现上述的错误,虽然一段时间内内存资源占用呈上升状态。猜想:所使用JDK版本不同,对于String常量池存放的位置已经发生了改变;或者是两者的电脑硬件不同
实验出处

然后,我又看到了这个新的实验证明String常量池的位置
JVM参数设置:-Xmx5m -XX:MaxPermSize=5m

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class TestString2 {
  4. public static void main(String[] args) {
  5. // TODO Auto-generated method stub
  6. List<String> list = new ArrayList<String>();
  7. int i = 0;
  8. while(true){
  9. list.add(String.valueOf(i++).intern());
  10. }
  11. }
  12. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

因为JDK版本不同的原因,我无法按照上述的代码得出原博文相同的结果,这是我自己运行出的结果

sun官方说明:并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。

对照着结果以及上面的博客可以得知,这显然是在堆中的垃圾回收发生了异常所致。在内存满后,会进行垃圾回收,但又会intern新的字符串到String常量池中,那么就会导致垃圾回收器一直不停的干着没有意义的活,时间一久,自然报错。同时原文中所提及的这一句话我觉得需要注意一下:

另外一点值得注意的是,虽然String.intern()的返回值永远等于字符串常量。但这并不代表在系统的每时每刻,相同的字符串的intern()返回都会是一样的(虽然在95%以上的情况下,都是相同的)。因为存在这么一种可能:在一次intern()调用之后,该字符串在某一个时刻被回收,之后,再进行一次intern()调用,那么字面量相同的字符串重新被加入常量池,但是引用位置已经不同。

综上,虽自己没有太多的明确结果证明,但是我想这已经能够印证JDK版本变化导致的String常量池位置的改变。

日常summary

这个本来是今天计划打算进行的一部分,结果好像进入牛角尖了,一定要深入一下…..,结果垃圾回收也没有看多少,明天继续。
但终于算是把这一块搞的一清二楚了,

JVM-String常量池与运行时常量池的更多相关文章

  1. JVM 常量池、运行时常量池、字符串常量池

    常量池: 即class文件常量池,是class文件的一部分,用于保存编译时确定的数据. 保存的内容如下图: D:\java\test\out\production\test>javap -ver ...

  2. Class常量池、运行时常量池、字符串常量池的一些思考

    Class常量池.运行时常量池.字符串常量池 class常量池 java代码经过编译之后都成了xxx.class文件,这是java引以为傲的可移植性的基石.class文件中,在CAFEBABE.主次版 ...

  3. 彻底搞清楚class常量池、运行时常量池、字符串常量池

    彻底搞清楚class常量池.运行时常量池.字符串常量池 常量池-静态常量池 也叫 class文件常量池,主要存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference ...

  4. String放入运行时常量池的时机与String.intern()方法解惑

    运行时常量池概述 Java运行时常量池中主要存放两大类常量:字面量和符号引用.字面量比较接近于Java语言层面的常量概念,如文本字符串.声明为final的常量值等. 而符号引用则属于编译原理方面的概念 ...

  5. JVM详解之:运行时常量池

    目录 简介 class文件中的常量池 运行时常量池 静态常量详解 String常量 数字常量 符号引用详解 String Pool字符串常量池 总结 简介 JVM在运行的时候会对class文件进行加载 ...

  6. 类的加载,链接和初始化——1运行时常量池(来自于java虚拟机规范英文版本+本人的翻译和理解)

    加载(loading):通过一个特定的名字,找到类或接口的二进制表示,并通过这个二进制表示创建一个类或接口的过程. 链接:是获取类或接口并把它结合到JVM的运行时状态中,以让类或接口可以被执行 初始化 ...

  7. java 运行时常量、编译时常量、静态块执行顺序

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt223 常量是程序运行时恒定不变的量,许多程序设计语言都有某种方法,向编译器告 ...

  8. java中的编译时常量与运行时常量

    常量是程序运行期间恒定不变的量,许多程序设计语言都有某种方式,向编译器告知一块数据是恒定不变的,例如C++中的const和Java中的final. 根据编译器的不同行为,常量又分为编译时常量和运行时常 ...

  9. 对JVM运行时常量池的一些理解

    1.JVM运行时常量池在内存的方法区中(在jdk8中,移除了方法区) 2.JVM运行时常量池中的内容主要是从各个类型的class文件的常量池中获取,对于字符串常量,可以调用intern方法人为添加,而 ...

随机推荐

  1. Jenkins和Docker以及Kubernetes结合考虑

    今天搞了一下Jenkins和Docker的结合,我在想几个问题: 构建是经常的,构建最大的目标还是生成一个包或者应用 发布应用也是频繁的,发布本质上是把这个包放到可运行的环境中便于测试 如果每次构建我 ...

  2. http 使用curl发起https请求报错的解决办法

    使用curl发起https请求的时候报错:“SSL certificate problem, verify that the CA cert is OK. Details: error:1409008 ...

  3. PHP定时执行计划任务

    一.Windows计划任务 在web 服务下新建需要执行的文件 二.新建bat文件,命名为test.bat,内容如下: D:\php\php.exe -q D:\website\test.php 三. ...

  4. Nginx负载均衡+监控状态检测

    Nginx负载均衡+监控状态检测 想用Nginx或者Tengine替代LVS,即能做七层的负载均衡,又能做监控状态检测,一旦发现后面的realserver挂了就自动剔除,恢复后自动加入服务池里,可以用 ...

  5. Solidworks如何自动打开和关闭特征识别FeatureWorks

    如果直接对已有的零件识别特征,可能会报错   删除多余的特征,先只保留一个输入(注意没有必要连草图也删掉,草图不会影响识别特征,你识别完了之后草图再接着该拉伸拉伸,该切除切除),然后再次执行识别特征, ...

  6. 请远离include_once和require_once[转]

    来自:http://www.poluoluo.com/jzxy/201306/216921.html 尽量使用include, 而不是include_once, 理由是 include_once需要查 ...

  7. python安装libxml2和pyquery

    安装.net framework 4.5.2 https://www.microsoft.com/zh-CN/download/details.aspx?id=42641 安装C编译器 python2 ...

  8. CPU利用率与Load Average的区别?

    CPU利用率,是对一个时间段内CPU使用状况的统计,通过这个指标可以看出在某一个时间段内CPU被占用的情况,如果CPU被占用时间很高,那么就需要考虑CPU是否已经处于超负荷运作,长期超负荷运作对于机器 ...

  9. 从MVC和三层架构说到ssh整合开发-下

    这章主要讲整合开发,直接从实战讲起,对与ssh的单方面了解,请继续等待我的兴许文章. 解说不到位的地方欢迎大家指正:联系方式rlovep.com 具体请看源码凝视: 全部代码下载(csdn):链接 G ...

  10. mysql中如何统计某字段里某个字符的个数

    select * from order where length(order_num)-length(replace(order_num,'8','')) = 4