[java]我的数据在哪里?——数据的内存模型
在编写程序时,我们也许会有这样一个问题,我们编写的程序中的数据运行时,会保存在哪里呢?简单直接的回答可能是——内存。这个回答在多数情况下可能都是对的,但事实上并不准确,我们都知道内存,即随机访问存储器可以在程序运行时保存程序所需要的数据,但不是所有数据,而且,内存这个词也并不准确。接下来,让我们看一下在程序运行时,数据可能会被存放的几大位置。
数据的六大存放位置
java为我们提供了不同于以往语言的新特性,其中一个非常方便的特性就是我们在创建新的类型(对象)时不需要手动分配内存,更不需要手动去销毁不再需要的对象,java让我们远离了对内存的繁琐操作。但是,在我们编写java程序时,依然需要做到对程序运行时数据的存放位置做到心中有数,这其中包括了寄存器、内存以及持久化存储。实际上,程序会使用到的数据,可能分配在以下六个位置:
(1) 寄存器。这是最快的保存区域,因为它是位于处理器内部,它的地理优势决定了存取都非常迅速。但是,寄存器的大小十分有限,所以它是根据编译器来分配的,我们没有直接分配的权利。也就是说,在java中,我们无法直接决定将哪些数据存放在寄存器中,也无法从程序中分析哪些数据可能会被存放在寄存器中。

图1 CPU
(2) 堆栈。这是存取速度次快的位置,它驻留于常规RAM(随机访问存储器)区域,也就是我们常说的内存。我们都知道,栈是一种常用的数据结构,而在内存的堆栈区域,它的数据保存方式就是按照栈的方式进行存取,我们可以通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。创建程序时,Java 编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,想象一下,在我们创建对象时,很多字段都是动态创建的,所以尽管有些Java数据要保存在堆栈里——特别是对象引用,但实际的对象并不放到其中。

图2 栈
(3) 堆。一种常规用途的内存池(也在RAM 区域),java对象都会保存在该位置。我们都知道,“堆”同时也是一个基本的数据结构,它是一种特殊的完全二叉树,分为大顶堆和小顶堆,堆在满足一定的存取效率的基础上,提高了查找效率。和堆栈不同,堆是牺牲了存储效率获取了灵活性,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,这也是java对象之所以放置在堆中的原因。进一步讲,正是因为我们有了堆这个结构,java才能够做到方便的创建对象以及创建对象时动态的创建对象的字段。要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!所以在程序的编写中,我们要尽量避免频繁的创建对象,这会大大降低程序运行的时间。
图3 大顶堆
(4) 静态存储。这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM 里)。程序运行期间,静态存储的数据将随时等候调用。我们知道,static可以用来修饰一个字段或者方法是属于一个类的,这个时候static修饰的字段就是静态的,因为它在整个的程序运行过程中都是大小已知的。程序中的常量也存放在这里。
(5) 常数存储。常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。
(6) 非RAM 存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器,比如说网络通信的数据。而对于固定对象,对象保存在磁盘中,比如说图片、数据库中的数据。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中,比如说采用数据库管理响应的数据。一旦需要,能将它们恢复成普通的、基于RAM 的对象。
常见的数据存放位置示例
接下来,我们来看一下在编写程序中我们经常用到的数据,掌握他们的存放位置,会让我们在编写程序时更加得心应手,更好的把握程序耗费的时间。
在上面的六大存储地点中,寄存器是我们无法直接去控制的,非RAM存储一般会交由其他第三方去控制,因此在这里,我们主要探究剩下的几种方式。
首先我们来看下面的代码:
public class Person {
public static final double PI = 3.14;
private String name = "dotgua";
public static void main(String[] args) {
int i = 5;
double d = 0.2;
Person p = new Person();
String str = new String("hello");
String str2 = "world";
}
}
上面这段代码比较短,但是涵盖了我们通常使用的大部分情景。
- 首先,i是main方法中的局部变量,所以i的值会保存在main的方法栈中(如图所示),除了整型,其他的局部变量,同时是基本类型(boolean/char/double/float等)的都会保存在堆栈中。
- 然后,p本身是引用类型,引用类型本身也会保存在堆栈中,而p中存放的是它指向的对象的地址(‘#####’部分)。
- p指向的Person对象会保存在堆中,任何通过new产生的对象都会放置在堆中,对象的成员属性和对象一起存放。请注意,Person类中的PI不会随对象一起存放,因为它本身由static修饰,所以是类成员,会被放置在静态存储中,这里如果有多个Person对象,那么这些对象都会指向同一个PI。
- 最后,比较有意思的是String类型,如果String是通过new产生的,那么它会和普通的对象一样存放在堆中。而如果是通过字符串字面量声明的,那么,字面量会存放在静态存储中(字面量本身是不会改变的),通过str2引用指向它。字符串放在静态存储中可以方便复用,因为字符串本身是不会改变的,所以如果有多个字符串声明的值相同,其实他们指向的是同一个字符串。这里对于字符串有个小技巧,对于通过new创建的字符串,我们可以使用String类的intern()方法将它重新放置在静态存储中。

总结
在java中,程序运行时,可能存放的位置大致有以下六个,分别是:
- 寄存器:位于CPU,速度最快,无法控制;
- 堆栈:内存RAM的一部分,用于存放局部变量;
- 堆:RAM中的一部分,通过new创建的对象都放在堆中,效率较低;
- 静态存储:类成员以及常量存放在这里,一般变化较小;
- 常数存储:常数值会直接存放在程序代码中;
- 非RAM存储:其他存储方式,不放置在内存中;
字符串通过new方式创建时会存放在堆中,通过字面量创建会存放在静态存储部分,可以通过String类的intern方法将new创建的字符串重新放置在静态存储中。
[java]我的数据在哪里?——数据的内存模型的更多相关文章
- 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before
第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...
- JVM运行时数据区与JVM堆内存模型小结
前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...
- Java虚拟机学习(1):体系结构 内存模型
一:Java技术体系模块图 Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆", 它用于存储虚拟机加载的类信息.常量.静态 ...
- 深入理解java虚拟机学习笔记(一)JVM内存模型
上周末搬家后,家里的宽带一直没弄好,跟电信客服反映了N遍了终于约了个师傅明天早上来迁移宽带,可以结束一个多星期没网的痛苦日子了.这段时间也是各种忙,都一个星期没更新博客了,再不写之前那种状态和激情都要 ...
- Java内存模型深度解析:总结--转
原文地址:http://www.codeceo.com/article/java-memory-7.html 处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会 ...
- 浅析java内存模型--JMM(Java Memory Model)
在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...
- java内存模型-总结
处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM 和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JMM 和处理器内存模型在设计时会对顺序一致性模型做一些放松,因为如果完全按照 ...
- Java锁(一)之内存模型
想要了解Java锁机制.引发的线程安全问题以及数据一致性问题,有必要了解内存模型,机理机制了解清楚了,这些问题也就应声而解了. 一.主内存和工作内存 Java内存模型分为主内存和工作内存,所有的变量都 ...
- 深入理解Java内存模型(七)——总结
处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JMM和处理器内存模型在设计时会对顺序一致性模型做一些放松,因为如果完全按照顺序 ...
- 【转】深入理解Java内存模型(七)——总结
处理器内存模型 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照.JMM和处理器内存模型在设计时会对顺序一致性模型做一些放松,因为如果完全按照顺序 ...
随机推荐
- Pivot For和UNPivot For
一.使用PIVOT和UNPIVOT命令的SQL Server版本要求 1.数据库的最低版本要求为SQL Server 2005 或更高. 2.必须将数据库的兼容级别设置为90 或更高. 3.查看我的数 ...
- flask更改已有的response
今天遇到个问题,需要更改返回的response,但框架已经生成了一个response,所以需要直接更改. 试着找了找解决办法,最终解决方式如下: #下文中payload的类型是 # class Res ...
- Python12/10--前端之display/overflow使用/清浮动的方式
display: 1.inline 同行显示,当一行显示不下.多余的就会换行显示, 不支持的css样式:不支持宽高,不支持行高(行高会映射给父级) 不支持margin上下,content由 文本内容撑 ...
- vue子传父多个值
子组件:this.$emit("methdosName",data1,data2,data3) 父组件: <child @methodsName="xxx(argu ...
- UVaLive 5760 Alice and Bob (博弈 + 记忆化搜索)
题意:有 n 堆石子,有两种操作,一种是从一堆中拿走一个,另一种是把两堆合并起来,Alice 先拿,谁不能拿了谁输,问谁胜. 析:某些堆石子数量为 1 是特殊,石子数量大于 1 个的都合并起来,再拿, ...
- springmvc接收数组方式总结
1.接受正常的数组 如param1=aaa¶m1=bbb¶m1=3 对于这种,在实体参数中,使用String param1[] 这种参数既可以获取数组的值 2.接受数组 ...
- yii2.0 点击验证码图片不刷新
修改vender\yiisoft\yii2\captcha\CaptchaAction下的run方法 public function run(){ if (Yii::$app->request- ...
- Android studio 中的TabWidget
1.TabWidget 的 layout文件 <?xml version="1.0" encoding="utf-8"?> <TabHost ...
- 关于 SQL 注入的问题
拼串 (Statement)方式 1.编译次数多,效率比较低:会出现SQL注入问题(数据安全问题):先传参数再编译. 2.Sql文对应的字符串不一样,需要再次编译.Sql文对应的字符串一样,不会再编译 ...
- JavaScript实现LUHN算法验证银行卡号有效性
一般验证银行卡有效性用到一种叫做LUHN的算法,简介请参考这篇博客:基于Luhn算法的银行卡卡号的格式校验 注意: 1.LUHN算法只是能校验卡号是否有效,并不能校验卡号和用户名是否一致. 2.如果有 ...
