BiBi - JVM -1- Java内存区域
From:深入理解Java虚拟机
- 目录
BiBi - JVM -0- 开篇
BiBi - JVM -1- Java内存区域
BiBi - JVM -2- 对象
BiBi - JVM -3- 垃圾收集算法
BiBi - JVM -4- HotSpot JVM
BiBi - JVM -5- 垃圾回收器
BiBi - JVM -6- 回收策略
BiBi - JVM -7- Java类文件结构
BiBi - JVM -8- 类加载机制
BiBi - JVM -9- 类加载器
BiBi - JVM -10- 虚拟机字节码
BiBi - JVM -11- 编译期优化
BiBi - JVM -12- 运行期优化
BiBi - JVM -13- 并发
1. 程序计数器
占用内存空间较小,是唯一一个没有规定任何OutOfMemoryError的区域。
字节码解释器通过改变这个计数器的值来选取下一条执行的字节码指令,并且分支、循环、跳转、异常处理、线程恢复等功能都需要依赖计数器来完成。
每个线程都有一个独立的计数器,来恢复线程的切换。
执行java方法时,计数器记录虚拟机字节码指令的地址;执行native方法时,计数器值为null。
2. 虚拟机栈
线程私有;每个方法执行都会创建一个栈桢,用于存储局部变量表、操作数栈、动态链接、方法出口等。
Slot:长度为64位的long和double类型的数据会占用2个局部变量空间【Slot】。
局部变量表所需的内存空间在编译期间完成分配,当一个方法进入时,这个方法在栈桢中分配多大的局部空间是完全确定的,在运行期间不会改变局部变量表的大小。
异常抛出情况:
1)栈深度过大,抛出StackOverflowError。
2)栈动态扩展时,无法申请到足够空间,抛出OutOfMemoryError。
3. 本地方法栈
与虚拟机栈相似,抛出异常一样。区别:虚拟机栈对应java方法【字节码】;本地方法栈对应native方法【机器码】。
HotSpot虚拟机将虚拟机栈和本地方法栈合二为一。
程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。
4. 堆
被所有线程共享;所有对象和数组都在堆上分配,但是随着JIT编译器的发展和逃逸分析技术的成熟,栈上分配和标量替换优化技术,使得其不再绝对。
当堆无法扩展时,抛出OutOfMemoryError。
5. 方法区
与堆一样,被所有线程共享。
主要存储已经被虚拟机加载的【类信息,如:类名、访问修饰符、字段描述、方法描述】【常量池】【静态变量】【即时编译器编译后的代码】等。
HotSpot虚拟机将方法区称为:永久代,本质上二者不同,只是使用永久代来实现方法区的GC【把GC分代收集扩展到方法区】,省去了为方法区专门管理GC的工作。对于其他虚拟机【如:BEA的JRockit、IBM的J9】不存在“永久代”的概念。这个区域的内存回收目标主要是针对【常量池的回收】和【对类型的卸载】,GC的时机很少。
由于容易引起内存泄漏,HotSpot虚拟机计划放弃“永久代”,采用Native Memory来实现方法区的规划。目前JDK1.7中已经把原本在“永久代”中的【字符串常量池】移除了。
6. 运行时常量池
是方法区的一部分【jdk1.7之前,之后分离开来】。
主要存放编译期间生成的各种字面量和符号引用。具有动态性,运行期间也可以将新的常量放入池中,如:利用Sting类中的intern()方法,它的作用:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回这个字符串对象;否则,将次String对象包含的字符串添加到常量池,并返回此String对象的引用。
- 例子1
String str = new StringBuilder("计算机").append("软件").toString();
System.out.println(str.intern() == str);
在JDK1.6及以前,结果为false。因为:intern()方法会把首次遇到的字符串“计算机软件”实例复制到永久代,并返回永久代中这个字符串的实例引用,所有结果为false。
字JDK1.7中,结果为true。因为:intern()不会再复制实例,只是在常量池中记录首次出现的实例引用,所以结果为true。
- 例子2
String s = "java";
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
无论什么JDK版本,结果都为false。因为"java"在常量池中已经出现过了,所以str2.intern()返回的是常量池中的引用。
7. 直接内存
一种基于通道和缓冲区的I/O方式,它可以使Native数据直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能够提高性能,避免Java在堆和Native堆中来回复制数据。
直接内存也称为C-Heap,供Java Runtime进程使用,没有相应的参数来控制其大小,其大小依赖于操作系统进程的最大值。Java应用程序都是在Java Runtime Environment (JRE)中运行,而Runtime本身就是由Native语言(如:C/C++)编写程序。Native Memory就是操作系统分配给Runtime进程的可用内存,它与Java Heap Memory不同,Java Heap 是Java应用程序的内存。
- Native Memory的主要作用如下:
- 管理java heap的状态数据(用于GC);
- JNI调用,也就是Native Stack;
- JIT(即使编译器)编译时使用Native Memory,并且JIT的输入(Java字节码)和输出(可执行代码)也都是保存在Native Memory;
- NIO direct buffer;
- Threads;
- 类加载器和类信息都是保存在Native Memory中的。
- 小常识
- Java Heap是对于Java 虚拟机而说的,一般的大小上限是 16M 24M 48M 76M 具体视手机而定。
- Native Heap是对于C/C++直接操纵的系统堆内存,所以它的上限一般是具体RAM的2/3左右。
- 所以对于手机而言,Java Heap 大概76M,而Native Heap是760M左右,相差10倍。