下面我们这篇博客来说一下java中是怎么合理的利用内存的,怎么将内存进行划分,做到各司其责,使系统平稳高效运行。
首先要知道的是java程序运行在JVM上,可以把JVM理解成java程序和操作系统之间的桥梁。因此JVM是内存分配原理的基础和前提。
一个完整的java运行程序会涉及一下内存区域:
(1) 寄存器(registers):JVM内部虚拟寄存器,这是速度最快的最快的存储场所,这是由于它位于处理器的内部。在内存中的寄存器区域是由编译器根据需要来分配的。它对我们是透明的,程序开发人员不能通过代码来控制这个寄存器的分配,也没有办法在程序里感知寄存器的任何存在的身影。
(2) 栈(stack):处理器经由类似指针的东西提供直接支持。当程序分配一块新的内存时,栈指针便往后移;释放内存时,指针往前移。这种方式快,效率高,速度仅次于寄存器。它主要用来保存局部变量的值,包括:基本数据类型、对象的引用、方法的参数值。它由编译器自动分配内存,具体方法执行结束后,系统自动释放JVM内存资源。
(3) 堆(heap):用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
Heap不同于stack之处在于,编译器不需知道究竟得从heap中配置多少空间,也不需知道从heap上配置的空间究竟需要存在多久。因此,自heap配置存储空间可以获得高度的弹性。每当你需要产生对象,只需在程序中使用new,那么执行的时候,便会自heap配置空间。当然,你得为这样的弹性付出代价:从heap配置空间,比从stack配置,所耗费的时间多了不少。它一般由程序员分配释放, 若程序员不释放,在jvm不定时查看这个对象时,如果没有引用指向这个对象就会GC回收。
(4) 数据段:用来存放static定义的静态成员、字符串。不会被释放。
(5) 代码区:用来存放程序中方法的二进制代码,而且是多个对象共享一个代码控件区域。
(6) 常量池:常量池存在于堆中。JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。
下面写个小例子,分析一下内存是怎么分配的:
1、 JVM自动寻找main方法,开始执行下面的代码;
2、 “Cat.sid=100;”静态变量存放在数据区,则数据区中有一个 “sid”,其值为100;
3、 “Cat mimi=new Cat("mimi");”内存分析为:首先栈内存中有一个“mimi”的引用变量,里面存放着cat对象所在的地址,可以找到我们堆空间里new出来的cat;
4、 接下来后面的字符串“mimi”,存放在数据区中,其字符串也是引用类型,可以将字符串也理解为对象;
5、 接下来调用构造函数,在栈空间中给形参“name“分配一个空间,接下来将数据区中”mimi“这个字符串传给栈中的”name“,相当与把”mimi“对象的这个引用交到”name“里面,所以”name“指向的是”mimi“;
6、 接下来看构造函数中的”this.name=name; “,这里的this.name是堆中的”name”,它等于栈中的”name”,则自身成员变量的name也指向了数据区中的字符串”mimi“;
7、 接下来构造方法完成,堆中的引用变量”name“消失;
8、 接下来构造函数中的” id=sid++;“,则可得堆中的”id“为100,完成后”sid“为101;
9、 “pipi.info();“最后将其打印出来即可
总之,java内存分配这里博大精深,自己理解和掌握的有限,在后面的学习会不断的汇总,加以整理,使其思路更加清晰。
原文:http://blog.csdn.net/lvshihua/article/details/38339713