JAVA是一座围城,Java开发不需要像C、C++开发人员那样,维护每个对象从开始到终结的职责。因为Java虚拟机会帮助我们完成这些职责,但是一旦发生内存泄漏和溢出,就需要我们排查。
Java虚拟机执行Java程序时,把它管理的整个内存区域称为运行时数据区。同时根据区域的用途,以及创建和销毁时间等因素,将运行时数据区分成不同的区域。
程序计数器表示当前线程所执行字节码指令的行号计数器。字节码解释器通过改变程序计数器的值,选取下一条需要执行的指令。为了保证线程切换之后恢复到正确的执行位置,每条线程都需要独立的程序计数器,所以程序计数器是线程私有的。同时程序计数器是唯一一个在虚拟机规范中没有规定 OutOfMemoryError 的区域。
注:线程执行Java方法,程序计数器记录字节码指令地址;如果执行的是本地(Native)方法,程序计数器为空。
虚拟机栈是Java方法执行的线程内存模型。每个方法的执行,Java虚拟机都会创建一个栈帧存储方法相关变量。每个方法被调用到执行完毕的过程,对应栈帧在虚拟机栈中入栈到出栈的过程。
如下图所示,当虚拟机执行 swap(a,b) 方法时,会创建一个单独的栈帧 swap(a,b) 栈帧,在该栈帧中会存储于方法相关的变量,该栈帧的入栈和出栈操作对应着方法的执行和结束。
每个栈帧都包含了局部变量表、操作数、动态链接、方法返回值。
在 swap 函数执行的过程中, a 、 b 、 temp 都会保存到局部变量表中,其中的赋值操作则通过操作数栈执行,
方法执行完毕返回到调用的地方的地址则存储在返回地址中。
本地方法栈与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法 服务。
Java堆是虚拟机管理的内存中最大的一块,几乎所有对象都在Java堆分配内存。Java堆在虚拟机启动的时候创建,被所有的线程共享。Java堆也会涉及到内存回收的内容,本片文章先不展开了。Java堆无法扩展时,会报出 OutOfMemoryError 异常。
方法区存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码缓存等数据。方法区是各个线程共享的内存区域。
屏幕面前的你,会不会遇到这样的困惑。方法区和永久代有什么关系?和元空间呢?
运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。 一个类加载到 JVM 中后对应一个运行时常量池。
屏幕面前的你,会不会遇到这样的困惑。运行时常量池和Class文件常量池有什么关系?和字符串常量池呢?和缓冲池呢?
Class 文件常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。常量池中主要存放两大类常量:字面量和符号引用。当Class文件常量池加载到方法区时,会把符号引用转换为直接引用,存放到运行时常量池。
字符串常量池是 全局的, JVM 中独此一份 ,因此也称为全局字符串常量池。
其中:
在 jdk1.6(含) 之前也是方法区的一部分,并且其中存放的是字符串的实例; 在 jdk1.7(含) 之后是在堆内存之中, 存储的是字符串对象的引用,字符串实例是在堆中;
在 HotSpot VM 里实现线程池功能的是一个 StringTable 类,它是一个Hash表,默认值大小长度是1009;这个 StringTable 在每个 HotSpot VM 的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了 StringTable 上。
String str1 = "图解Java";
String str2 = new String("图解Java");
System.out.println(str1 == str2);
在这段代码中,当执行 String str1 = "图解Java" 时,先到常量池中查询有没有 "图解Java" 字符串的引用,如果没有,则会在 Java堆 上创建 "图解Java" 字符串,在常量池中存储字符串的地址, str1 则指向字符串常量池的地址。
String str2 = new String("图解Java") ,则会直接在Java堆中创建对象。 str2 指向堆中的地址。
看到这里,屏幕面前的你有没有想到最后的结果是 false 呢。
如果此时还有 String str3 = "图解Java" 那么 str1==str3 的结果是什么?
此时 str3 发现字符串常量池中已经有了 "图解Java" 字符串的引用,则直接返回,不会创建新的对象。
看到这里,屏幕面前的你有没有想到最后的结果是 true 呢。
JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型剩余的6种基本数据类型的包装类,都使用了缓冲池技术,但是 Byte 、 Short 、 Integer 、 Long 、 Character 这5种整型的包装类也只是在对应值在 [-128,127] 时才会使用缓冲池,超出此范围仍然会去创建新的对象。
我们平时写好的Java代码即Java格式的文件,经过编译,会变成Class类型的文件。而Class文件有一部分是Class文件常量池,用于存储字面量和符号引用。
Class文件经过类加载器加载后,之前Class文件常量池的内容会存放到方法区的运行时常量池,需要注意的是Class文件常量池的符号引用会转变直接引用存入运行时常量池。
字符串常量池是 JVM 的一部分,整个 JVM 只有一份,在将Class文件常量池的字面量也会在类加载的时候进入到字符串常量池中。
份数内容Class文件常量池每个类对应一份字面量、符号引用运行时常量池每个类对应一份字面量、直接引用字符串常量池整个 JVM 仅有一份字符串