概述
- 线程私有(运行指令):程序计数器、虚拟机栈、本地方法栈
- 线程共享(存储数据):堆、方法区(Java8去掉了)
程序计数器
唯一不会OOM
指向当前线程正在执行的字节码指令的地址(行号)
栈
存储当前线程方法所需的数据、指令、返回地址
-Xss 1M
每个方法在执行的时候都会创建一个栈帧
- 局部变量表
- 操作数帧
- 动态连接
- 返回地址
本地方法栈
保存native方法的信息,调用native方法时,不在虚拟机栈中创建栈帧,JVM只是简单动态链接并直接调用native方法
HotSpot直接把本地方法栈和虚拟机栈合二为一了。
方法区
JDK1.7及之前称为永久代
JDK1.8及之后称为元空间
- 类信息
- 常量 final
- 静态变量
- 即时编译期编译后的代码
堆
-Xms、-Xmx、-Xmn
几乎所有对象都是在堆上分配的。
JVM各版本内存区域变化
-
运行时常量池:
Class文件中的常量池(编译期生成的各种字面量和符号引用)会在类加载后放入这个区域
1.1 符号引用:在编译时,引用的对象并不知道实际的内存地址,因此只能用一个符号引用来代替,而在类装载之后是知道内存地址的,此时会将符号引用替换成实际地址。
1.2 文本字符String a = “abc”,这个abc 就是字面量;八种基本类型int a = 1; 这个1 就是字面量;声明为final 的常量 -
JDK1.6 :
常量池在方法区 -
JDK1.7及之后
常量池在堆 -
JDK1.8
去方法区,使用元空间来代替(元空间大小只受机器内存限制)
永久代存储类信息、常量等数据可能会内存溢出
对永久代调优很困难
直接内存
JVM直接管理不了的,不是JVM规范中定义的内存区域
- 如果使用NIO,直接内存会经常使用,在java中可以使用directByteBuffer对象直接引用并操作
- 这块内存不受堆大小限制,可以通过MaxDirectMemorySize来设置,默认与最大堆大小一致,所以也会OOM
- 避免在堆和本地内存中来回复制数据
问题
-
为什么需要程序计数器
Java是多线程的,意味着线程是要切换的,同时确保多线程情况下程序能够正常运行。 -
字符串创建与比较
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true
}
2.1 s1在常量池创建了常量
2.2 s2由于常量池已有了Hello常量,与s1的指向常量的地址是一样的
2.3 s3 Hel和lo是字面量,编译期会进行优化,编译时就替换成Hello了,与s2一样
2.4 lo是通过new对象创建出来的,需要在运行期才能确定具体的地址,因此和s1的地址不同
2.5 new出来的对象,虽然Hello在常量池是和s1一样,但是当前对象返回给s5的地址是堆中的对象地址
2.6 返回的是常量池中的Hello的地址,与s1一样
2.7 s9 是由两个变量拼接的,编译期无法优化,只能在运行期才知道具体的地址