JVM内存模型及String对象内存分配

原创 2017年09月14日 11:39:09

  昨天看了一篇关于《Java后端程序员1年工作经验总结》的文章,其中有一段关于String和StringBuffer的描述,对于执行结果仍然把握不准,趁此机会也总结了下JVM内存模型。

1、JVM运行时数据区域

关于JVM内存模型之前也了解过一些,也是看过就忘,好记性比如烂笔头,记下来吧。参考此文章http://chenzhou123520.iteye.com/blog/1585224

图1 JVM运行时数据区域

(1)、程序计数器(Program Counter Register):
     程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
     由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,是‘线程私有’的内存。
(2)、JAVA虚拟机栈(Java Virtual Machine Stack):
     与程序计数器一样,java虚拟机栈也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型:在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
     局部变量表存放了编译期可知的各种基本数据类型()、对象引用和returnAddress类型(指向了一条字节码指令的地址)
     局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

JVM Stack 异常情况:

StackOverflowError:当线程请求分配的栈容量超过JVM允许的最大容量时抛出

OutOfMemoryError:如果JVM Stack可以动态扩展,但是在尝试扩展时无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈时抛出
(3)、本地方法栈(Native Method Stack):
      本地方法栈与虚拟机栈所发挥的作用是非常相似,区别不过是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法(使用Java语言以外的其它语言编写的方法)服务。
     本地方法栈也可以抛出StackOverflowError和OutOfMemoryError异常
(4)、JAVA堆(Java Heap):
      虚拟机管理的内存中最大的一块,同时也是被所有线程所共享的,它在虚拟机启动时创建,此内存区域存在的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。这里面的对象被自动管理,也就是俗称的GC(Garbage Collector)所管理。用就是了,有GC扛着呢,不用操心销毁回收的事儿。
     Java堆的容量可以是固定大小,也可以随着需求动态扩展(-Xms和-Xmx),并在不需要过多空间时自动收缩。
     Java堆所使用的内存不需要保证是物理连续的,只要逻辑上是连续的即可。
     JVM实现应当提供给程序员调节Java 堆初始容量的手段,对于可动态扩展和收缩的堆来说,则应当提供调节其最大和最小容量的手段。
     如果堆中没有内存完成实例分配并且堆也无法扩展,就会抛OutOfMemoryError。
(5)、方法区(Method Area):
      跟堆一样是被各个线程共享的内存区域,用于存储以被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然这个区域被虚拟机规范把方法区描述为堆的一个逻辑部分,但是它的别名叫非堆,用来与堆做一下区别。
(6)、运行时常量池(Runtime Constant Pool):
     运行时常量池是方法区一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

2、String对象内存分配分析
先看以下代码,运行后,结果如代码1,2,3,4,5,6所示
package com.xtli.controller;

public class StringTest {
     public static void main(String[] args) {
          String s1 = "hello";
          String s2 = "world";
          System.out.println(s1+"---"+s2);//1:hello---world
          change(s1,s2);
          System.out.println(s1+"---"+s2);//3:hello---world

          StringBuffer sb1 = new StringBuffer("hello");
          StringBuffer sb2 = new StringBuffer("world");
          System.out.println(sb1+"---"+sb2);//4:hello---world
          change(sb1,sb2);
          System.out.println(sb1+"---"+sb2);//6:hello---worldworld
     }

     public static void change(String s1, String s2) {
          s1 = s2;
          s2 = s1+s2;
          System.out.println("change(s1,s2)---"+s1+"---"+s2);//2:change(s1,s2)---world---worldworld
     }

     public static void change(StringBuffer sb1, StringBuffer sb2) {
          sb1 = sb2;
          sb2.append(sb1);
          System.out.println("change(sb1,sb2)---"+sb1+"---"+sb2);//5:change(sb1,sb2)---worldworld---worldworld
     }
}
对以上代码进行分析说明,如下
public class StringTest {
     public static void main(String[] args) {
        //在main方法的栈中创建引用s1和引用s2,此引用s1和引用s2存放在栈(main方法的栈)中;编译时,在常量池中创建两个常量"hello"和"world",s1和s2分别
        //指向两个常量
          String s1 = "hello";
          String s2 = "world";
          System.out.println(s1+"---"+s2);//1:hello---world
          change(s1,s2);//引用s1和s2作为参数传递到change方法中
          //change方法中的引用s1,s2和main方法中的引用s1,s2存放地址并不同,以下输出的是main方法栈中的s1和s2,并没有发生变化,故代码3有以下输出
          System.out.println(s1+"---"+s2);//3:hello---world

          //以下两行代码将会在main方法栈中创建引用sb1和sb2,并在堆内存中创建两个对象"hello"和"world",sb1和sb2分别指向两个对象
          StringBuffer sb1 = new StringBuffer("hello");
          StringBuffer sb2 = new StringBuffer("world");
          System.out.println(sb1+"---"+sb2);//4:hello---world
          change(sb1,sb2);//引用sb1和sb2作为参数传递到change方法中
          //main方法中的sb1所指向的堆内存地址未发生变化,故仍为"hello",而change(sb1,sb2)方法改变了main方法中sb2所指向的堆内存地址的内容,故代码6有以下输出
          System.out.println(sb1+"---"+sb2);//6:hello---worldworld
     }

     public static void change(String s1, String s2) {//在change方法的栈中创建引用s1和s2,并指向常量池中的常量
          s1 = s2;//将引用s1指向s2的常量池中的"world"
          s2 = s1+s2;//在堆内存中创建"worldworld"对象,并将s2指向此堆内存地址
          System.out.println("change(s1,s2)---"+s1+"---"+s2);//2:change(s1,s2)---world---worldworld
     }

     public static void change(StringBuffer sb1, StringBuffer sb2) {//在change方法的栈(和上面的change方法栈不同)中创建引用sb1和sb2,并指向main方法栈中sb1和sb2所指向的对象
          sb1 = sb2;//将引用sb1指向sb2所引用的对象"world"
          sb2.append(sb1);//引用sb2所指向的对象发生变化,变为"worldworld",注意此时外部main方法中的sb2和此方法中的sb1均指向此堆内存地址,
          //此地址内容发生变化后,外部main方法中的sb2指向的内容也跟着变化
          System.out.println("change(sb1,sb2)---"+sb1+"---"+sb2);//5:change(sb1,sb2)---worldworld---worldworld
     }
}
为了进一步说明change(String s1, String s2)中的结果,可以进行以下验证。
public static void change(String s1, String s2) {
          String s= "world";
          String ss= "worldworld";
          s1 = s2;
          System.out.println(s==s1);//输出true
          s2 = s1+s2;
          System.out.println(ss==s2);//输出false
          System.out.println("change(s1,s2)---"+s1+"---"+s2);//2:change(s1,s2)---world---worldworld
     }
故在change(String s1, String s2)方法中s1=s2后,s1所指向的是常量池中的"world",s2=s1+s2代码执行后,会在堆内存中重新创建对象,并将s2指向此堆内存地址。


以上均为个人总结,如有不正确之处,请指出,大家共同进步。转载请注明出处。

版权声明:本文为博主原创文章,转载请注明作者与出处,http://blog.csdn.net/lixingtao0520

为什么说Java匿名内部类是残缺的闭包

总有那么些十分基础的知识点,值得好好研究

jvm系列(一):java类的加载机制

类加载机制的奥妙。1、什么是类的加载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方...

JVM 内存分配模型概念和java中各种对象的存储

JVM 内存分配模型概念 --在工作中可能用到的机会不多,有个概念的了解 --此文是转载某位读者,应该是在阅读了《深入理解Java虚拟机JVM高级特性与最佳实践》 一书后,总结所得。写的不错,转载哈...

创建string对象过程的内存分配:

  • 2012年04月16日 15:18
  • 50KB
  • 下载

JVM深入浅出(三) -- String.intern的内存分配和工作机制

在 Java 语言 , 一些基本变量存放在栈中 , new 出来的变量存放于堆中 , 对于八个基本数据类型而言 , String 这个数据类型比较特殊 , 它可以通过String str = "str...
  • JacXuan
  • JacXuan
  • 2017年04月26日 21:37
  • 24085

Java String类型剖析及其JVM内存分配详解

前提:先了解下什么是声明,什么时候才算是产生了对象实例 其中x并未看到内存分配,变量在使用前必须先声明,再赋值,然后才可以使用。java基础数据类型会用对应的默认值进行初始化   ...

Java String类型剖析及其JVM内存分配详解

前提:先了解下什么是声明,什么时候才算是产生了对象实例 其中x并未看到内存分配,变量在使用前必须先声明,再赋值,然后才可以使用。java基础数据类型会用对应的默认值进行初始化   ...

JVM对象引用与内存分配策略

前两天对《深入理解虚拟机》一书做了个总结:《JVM理解其实并不难! 》,今天针对垃圾回收和内存分配做个深入了解。 关于引用 在《JVM理解其实并不难! 》一文中提到,JVM是通过可达性分析来判断对...

JVM里java对象的创建及内存分配和访问定位

对象的创建 Java是一门面向对象的编程语言,在Java程序运行过程中无时无刻都有对象被创建出来。在语言层面上,创建对象(例如克隆、反序列化)通常仅仅是一个new关键字而已,而在虚拟机中,对象(文...

JVM对象引用与内存分配策略

http://www.jianshu.com/p/fcc06b1fe6b3 http://www.jianshu.com/p/fcc06b1fe6b3 http://www.jianshu...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JVM内存模型及String对象内存分配
举报原因:
原因补充:

(最多只允许输入30个字)