JVM-java内存区域

 

 note:

在周志明那本深入java虚拟机中有说到,到了JDK1.7时,字符串常量池就被移出了方法区,转移到了堆里了。

那么我们可以推断,到了JDK1.7以及之后的版本中,运行时常量池并没有包含字符串常量池,运行时常量池存在于方法区中,而字符串常量池存在于堆中。

1. 程序计数器

  程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器。各个线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是Natvie方法,这个计数器值则为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

2.java虚拟机栈

  线程私有,虚拟机栈描述的是java方法执行的内存模型,每个方法被执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
这个区域规定了两种异常状况
(1) 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。

(2) 如果虚拟机扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

栈是存放线程调用方法时存储局部变量表,操作,方法出口等与方法执行相关的信息,Java栈所占内存的大小由Xss来调节,方法调用层次太多会撑爆这个区域。

  • 局部变量表:顾名思义,他就是用来存储方法中的局部变量(包括在方法中生命的非静态变量以及函数形参),对于基本数据类型,直接存值,对于引用类型的变量,存储指向该对象的引用。由于它只存放基本数据类型的变量、引用类型的地址和返回值的地址,这些类型所需空间大小已知且固定,所以当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全可以确定的,在方法运行期间也不会改变局部变量表的大小。
  • 指向运行常量池的引用:在方法执行过程中难免会使用到类中定义的常量,因此栈帧中要存放一个指向运行时常量池的引用。
  • 方法返回地址:当一个方法执行结束后,要返回到之前调用它的地方,因此在栈帧中需要保存一个方法返回地址。

3.本地方法栈  

本地方法栈是为虚拟机使用到的Native方法服务。本地方法栈也会抛出StackOverflowError异常和OutOfMemoryError异常。

4. java堆

  java堆是被所有线程共享的一块内存区域。
所有对象实例以及数组要在堆上分配,但随着JIT编译器的发展与逃逸分析技术的逐渐成熟,所有对象在堆上分配也渐渐变得不是那么绝对了。
java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,将会抛出OutOfMemoryError异常。

堆所占内存的大小由-Xmx指令和-Xms指令来调节

5. 方法区

  方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。
相对而言,垃圾收集行为在这个区域是比较少出现的,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。

  

method(方法区)又叫静态区,存放所有的①类(class),②静态变量(static变量),③静态方法,④常量和⑤成员方法。

1.又叫静态区,跟堆一样,被所有的线程共享。

2.方法区中存放的都是在整个程序中永远唯一的元素。这也是方法区被所有的线程共享的原因。

(顺便展开静态变量和常量的区别: 静态变量本质是变量,是整个类所有对象共享的一个变量,其值一旦改变对这个类的所有对象都有影响;常量一旦赋值后不能修改其引用,其中基本数据类型的常量不能修改其值。)

方法区的大小由-XX:PermSize和-XX:MaxPermSize来调节,类太多有可能撑爆永久代。静态变量或常量也有可能撑爆方法区

6、运行常量池(也属于方法区的)

  运行时常量池是方法区的一个部分,class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容会在类加载后进入方法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

  这儿的“静态”是指“位于固定位置”。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java对象本身永远都不会置入静态存储空间。

这个区域属于方法区。该区域存放类和接口的常量,除此之外,它还存放成员变量和成员方法的所有引用。当一个成员变量或者成员方法被引用的时候,JVM就通过运行常量池中的这些引用来查找成员变量和成员方法在内存中的的实际地址。

7、用一张图总结

 

 note: 

解释下String 的intern方法

例如我们调用了t.intern()。

在JDK1.6的时候,调用了这个方法之后,虚拟机会在字符串常量池在查找是否有内容与"tt"相等的对象,如果有,则返回这个对象,如果没有,则会在字符串常量池中添加这个对象。注意,是把这个对象添加到字符串常量池。

到了JDK1.7之后,如果调用了intern这个方法,虚拟机会在字符串常量池在查找是否有内容与"tt"相等的对象,如果有,则返回这个对象,如果没有。则会在堆中把这个对象的引用复制添加到字符串常量池中。注意,这个时候添加的是对象在堆中的引用。

例:1:

 1 public static void main(String[] args){
 2     String t1 = new String("1");
 3     t1.intern();
 4     String t2 = "1";
 5     System.out.println(t1 == t2);
 6 
 7     String t3 = new String("2") + new String("2");
 8     t3.intern();
 9     String t4 = "22";
10     System.out.println(t3 == t4);
11 }

答案输出:

JDK1.6是 false false。

JDK1.7是 false true;

例子2:(颠倒 intern的位置)

 1 public static void main(String[] args){
 2     String t1 = new String("2");
 3     String t2 = "2";
 4     t1.intern();
 5     System.out.println(t1 == t2);
 6 
 7     String t3 = new String("2") + new String("2");
 8     String t4 = "22";
 9     t3.intern();
10     System.out.println(t3 == t4);
11 }

答案输出: false false

原文地址:https://www.cnblogs.com/jingpeng77/p/12422607.html