上次面试中被问到了static和final修饰的变量的内部存储空间,虽然我答出来了,但是我也是有点不确定,于是准备总结一波!
运行时的数据区
1.程序计数器:
我们在程序中无法控制。最快的保存区域,位于处理器内部,由编译器分配。主要作用是记录当前线程所执行的字节码的行号。字节码解释器工作时就是通过改变当前线程的程序计数器选取下一条字节码指令来工作。任何分支、循环、方法调用、判断、异常处理、线程等待以及恢复线程、递归等都是通过这个计数器来完成。为了多线程的实现,每条线程都会有独立的程序计数器来记录当前指令的行号,以使得线程等待结束后能恢复到正确的位置执行。这块内存也被称为"线程私有"的内存。如果调用的方法是native的,则寄存器不存储任何信息。
2、JVM栈
在函数中定义的一些基本类型的变量数据(8种)和对象的引用变量都在函数的栈内存中分配。非基本类型的对象(引用类型)在JVM栈上只存放一个指向堆内存的地址,因此Java中的基本类型变量采用值传递,而引用类型对象采用引用传递。
JVM的栈是线程私有的,每个线程创建的同时都会创建JVM栈。因此内存分配上非常高效,且当线程运行完毕后,这些内存也就被自动回收另作它用。当栈请求深度大于允许的深度时,会抛出StackOverflowError的错误,可以通过-Xss来指定栈的大小。而如果内存不足时,会抛出OutOfMemoryError错误。
3、堆
堆内存用来存放由new创建的对象和数组。堆中的对象内存需要等待GC(垃圾回收机制)进行回收,堆内存在32位操作系统上最大为2G,在64位操作系统上则没有限制,其大小通过-Xms(初始堆大小,即启动最小堆内存,默认物理内存的1/64,小于1G)和-Xmx(启动最大堆内存,默认物理内存的1/4)来控制。默认当空余堆内存小于40%时,JVM会增大堆的大小到-Xmx指定的大小,可通过-XX:MinHeapFreeRatio来指定这个比例。当空余堆内存大于70%时,JVM会将堆内存的大小往-Xms指定的大小调整,可通过-XX:MaxHeapFreeRatio=来指定这个比例,但对于运行系统而言,为了避免频繁的改变堆内存的大小,通常都会将-Xms和-Xmx的值设置成一样。当堆中需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
在Java中,程序员基本不用去关心空间释放的问题,Java的垃圾回收机制会自动进行处理。因此这部分空间也是Java垃圾收集器管理的主要区域。另外,堆是被所有线程共享的,在JVM中只有一个堆。
和栈比起来,编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间。因此更加灵活,但是速度更慢些。
4、本地方法栈
本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方法栈的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。
5、方法区
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。用永久代来实现了方法区在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。static修饰的变量存储于静态域就属于方法区。
在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用,这部分内容会在类加载后进入方法区的运行时常量池中存放。
需要注意的:
对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
如以下代码: Java代码
String s1 = "china";
String s2 = "china";
String s3 = "china";
String ss1 = new String("china");
String ss2 = new String("china");
String ss3 = new String("china");
这里解释一下,对于通过 new 产生一个字符串(假设为 ”china” )时,会先去常量池中查找是否已经有了 ”china” 对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此 ”china” 对象的拷贝对象。
也就是有道面试题: String s = new String(“lh”); 产生几个对象?
一个或两个。如果常量池中原来没有 ”lh”, 就是两个。如果原来的常量池中存在“lh”时,就是一个。
对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。
简单总结起来:
- ◆寄存器:我们在程序中无法控制
- ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
- ◆堆:存放用new产生的数据
- ◆静态域:存放在对象中用static定义的静态成员
- ◆常量池:存放常量
- ◆非RAM存储:硬盘等永久存储空间
jdk8的变化:
移除了PermGen,取而代之的是MetaSpace元空间:
一种新的内存空间的诞生。JDK8 HotSpot JVM 使用一个与堆不相连的本地内存来存储类元数据信息并称之为:元空间(Metaspace);这与Oracle JRockit 和IBM JVM’s很相似。这将是一个好消息:意味着不会再有java.lang.OutOfMemoryError:PermGen
问题,也不再需要你进行调优及监控内存空间的使用,但是新特性怒能消除类和类加载器导致的内存泄漏
。你需要使用不同的方法以及遵守新的命名约定来追踪这些问题。
详细可见:http://openjdk.java.net/jeps/122