JVM内存结构和常量池
Posted zhangsonglin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM内存结构和常量池相关的知识,希望对你有一定的参考价值。
1.虚拟机的构成
虚拟结主要由运行时数据区、执行引擎、类加载器三者构成,我们所说的JVM内存模型指的就是运行时数据区。
2.运行时数据区组成和各个区域的作用
运行时数据区可以分为线程共享和线程不共享两部分,其中堆内存和方法区线程共享,本地方法栈、虚拟机栈、程序计数器线程不共享。
2.1.程序计数器
程序计数器(Program Counter Register),也有称作为PC寄存器。想必学过汇编语言的朋友对程序计数器这个概念并不陌生,在汇编语言中,程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。
2.2虚拟机栈
虚拟机栈也叫java栈,栈中存储的是帧栈,每一个方法对应一个帧栈,方法执行完毕后进行弹栈,让出栈内存,帧栈中存储着方法中定义的变量,如果是基本数据类型,就在栈中进行值的存储,如果是引用数据类型,存储的是引用指向对象的地址。
虚拟机栈也叫栈内存,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就 Over,所以不存在垃圾回收。也有一些资料翻译成JAVA方法栈,大概是因为它所描述的是java方法执行的内存模型,每个方法执行的同时创建帧栈(Strack Frame)用于存储局部变量表(包含了对应的方法参数和局部变量),操作栈(Operand Stack,记录出栈、入栈的操作),动态链接、方法出口等信息,每个方法被调用直到执行完毕的过程,对应这帧栈在虚拟机栈的入栈和出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象的引用(reference类型,不等同于对象本身,根据不同的虚拟机实现,可能是一个指向对象起始地址的引用指针,也可能是一个代表对象的句柄或者其他与对象相关的位置)和 returnAdress类型(指向下一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,在方法在运行之前,该局部变量表所需要的内存空间是固定的,运行期间也不会改变。(我们常常指的栈就是这个虚拟机栈)
2.3本地方法栈
和虚拟机栈功能相似,不过本地方法栈存储针对本地方法。
2.4堆内存
堆内存主要用来存储被创建的对象,一个类new出一个对象,会在堆中开辟内存空间,并在栈中存储一个引用,存储着对象在堆中的地址。堆内存中的对象存储着自己的成员变量,并不保存对象的方法,方法被保存在帧栈中,堆内存也称为gc堆,是主要用来进行垃圾回收的内存。
2.5方法区
方法区是一个非常重要的区域,也是被线程共享的区域,方法区存储了每个类的信息(类的名称、方法信息、字段信息),静态变量、常量以及编译后的代码等。
方法区还包括一个常量池,用来存储编译期间生成的字面量和符号引用。这部分内容在类被加载后,都会存储到方法区中的RCP。值得注意的是,运行时产生的新常量也可以被放入常量池中,比如 String 类中的 intern() 方法产生的常量。
理解:
Public static final MAX_I = 100;// 字面量
Public final User user = new user(“zsl”);// 符号引用
//字面量和符号引用是在编译期间生成的
//这部分内容在class文件中的class文学常量池中在类被加载后,都会存储到方法区中的运行时常量池(RCP)
常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用.
3.常量池的使用
3.1什么是常量?
常量值又称为字面常量,它是通过数据直接表示的。
常量不同于常量值,它可以自定义。
在定义常量时就需要对该常量进行初始化。
? final 关键字不仅可以用来修饰基本数据类型的常量,还可以用来修饰对象的引用或者方法。
? 为了与变量区别,常量取名一般都用大写字符。
? 当常量被设定后,一般情况下不允许再进行更改,如果更改其值将提示错误。
?自定义常量是指被final修饰的变量,值一旦确定就无法改变。
? final可以修饰静态变量、实例变量和局部变量。
3.2Clss文件中的常量池
常量池主要用来存放两大类常量:字面量和符号引用量,字面量相当于Java语言的常量,如文本字符串,声明为final的常量等,符号引用包括以下三种
1.类和接口的全限定名
2.字段名称和描述符
3.方法名称和描述符
3.3方法区中的运行常量池
class文件中的常量池中的内容会在类加载后进入方法区的运行时常量池。相对于常量池,运行时常量池的重要特征是具有动态性,java并不要求常量只有在编译器才会产生,运行期间也可以将新的常量存放入池中,这种特性用的最多的String类中的intern()方法。
举个例子来说明:“java并不要求常量只有在编译器才会产生,运行期间也可以将新的常量存放入池中,这种特性用的最多的String类中的intern()方法。”
eg:
String s1 = "zzz";
String ss1 = new String("zzz");
对于引用数据类型,s是引用存在栈内存中的,而new 对象的对象是存在堆内存中的,而"zzz",
会先去常量池中查找是否已经有了”zzz”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”zzz”对象的拷贝对象。
3.4常量池的作用和==号的意义
常量池是为了避免频繁的创建和销毁对象造成系统性能的浪费,实现了对象的共享。
==号比较基本数据类型,就是比值,比较引用数据类型比较的是内存中存放的地址。
3.5常量池的应用
对于byte、short、long、char、boolean对应的包装器类都有对应的常量池,这五种包装器类默认创建在-128到127的对象会存放在在缓存中。
对于两种浮点数没有实现常量池技术。
那么这些包装器的缓存范围是多少了?
Integer (-128~127缓存)
Boolean: (全部缓存)
Byte: (全部缓存)
Character ( <=127 缓存)
Short (-128~127 缓存)
Long (-128~127 缓存)
Float (没有缓存)
Doulbe (没有缓存)
举例说明:
public class ZhuangXaing
public static void main(String[] args)
Integer i= new Integer(12);
Integer j=12;
Integer k=Integer.valueOf(12);
Integer l= new Integer(232);
Integer m=232;
Integer n=232;
Double q = 232.0;
System.out.println("use ==.......");
System.out.println(i==12);
System.out.println(i==j);
System.out.println(j==k);
System.out.println(l==232);
System.out.println(l==m);
System.out.println(m==n);
System.out.println("use equals.....");
System.out.println(m.equals(n));
System.out.println(m.equals(q));
输出结果:
use ==…
true
false
true
true
false
false
use equals…
true
false
Integer i= new Integer(12); 是指明了在堆内存中创建对象;
Integer j=12; 是自动装箱,调用valueOf 方法,返回return IntegerCache.cache[12 + 128], 得到的是Integer 缓冲池中的对象。Integer k=Integer.valueOf(12); 与Integer j=12; 本质上相同,指向缓冲池中同一对象。包装对象与数值比较,自动拆箱。
而对于大于127 的数值,执行的都是return new Integer(i) 都在堆内存中,但是地址不同。
3.6 String类和常量池
String str1="abc";
String str2=new String("abc");
System.out.println(str1==str2);
结果为false;
String str1="abc";
str1引用会先去常量池中查找”abc”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”abc”对象的拷贝对象。==比的是地址,这里String str2=new String("abc");进行了new 对象,所以对象指向的引用不同了,地址也不同了,所以为false;
以上是关于JVM内存结构和常量池的主要内容,如果未能解决你的问题,请参考以下文章