JVM内存模型
JVM内存模型分为1.7与1.8版本(其中黄色代表线程共享,白色代表线程私有)
JVM内存模型图(1.7) JVM内存模型图(1.8) JVM运行时数据的直观感受程序计数器
线程在运行期间,会想CPU进行时间片资源的争夺情况,假设现在A线程执行到了if判断,时间片突然发生了抢占,那么程序计数器会帮忙记录每个线程执行的下一条字节指令,等下次线程A拿到时间片继续执行(实际上就是记录当前线程执行到了什么位置,下一条指令是啥,下次接着从这个位置开始)。
- 执行java方法:记录虚拟机正在执行的字节码指令地址。
- 执行native方法:undefined
5个区域唯一不会出现OOM的地方(OutOfMemoryError:所需内存超过虚拟机内存,在进行full gc后依然不够,会抛出OutOfMemoryError)
虚拟机栈
描述方法执行过程的内存模型,每个方法的执行过程就是它所对应虚拟机栈中的入栈出栈的过程,栈帧里面又会包括一些局部变量表(基本数据类型(byte、long、short、int、float、double、char、bolean)、对象引用类型),操作数栈,方法出口等信息
public class Test {
public static void test() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
}
public static void main(String[] args) {
test();
}
}
上面代码对应的虚拟机栈图
局部变量表:abc
操作数栈:像我们的程序后面都会被编译成这种字节码指令,而操作数栈将变量之间的运算入栈,然后存储计算结果,再出栈赋值给局部变量表
方法出口:代码中main()方法调用了test()方法,这时test()执行完成后,需要继续执行main()方法,方法出口记录了test()方法执行完成后的一个出口,也就是回到main()
本地方法栈
native修饰的方法,这些方法不由Java语言实现,而是由Java语言调用C++语言实现;跟虚拟机栈有些类似,只不过由native方法的线程栈帧。
方法区
方法区(又叫永久代),1.8叫元空间
方法区
存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
类信息:即 Class 类,如类名、访问修饰符、常量池、字段描述、方法描述等。 垃圾收集行为在此区域很少发生;不过也不能不清理,对于经常动态生成大量 Class 的应用,如 Spring 等,需要特别注意类的回收状况。
运行时常量池:用于存放编译器生成的各种字面量(就是代码中定义的 static final 常量)和符号引用,这部分信息就存储在运行时常量池中。
注:区分是存放在栈中还是方法区中,就看是(【栈】局部变量)还是(【堆/方法区】成员变量,静态变量,常量)
成员变量:在类范围内定义的变量,局部变量:在一个方法内定义的变量
元空间
JDK1.8后,方法区被叫做元空间,并且内存大小限制不限JVM的内存大小限制,而是直接使用计算机的直接内存,受计算机的内存大小限制。
1.8运行时常量池移到了堆。
堆
主要存放我们程序中new出来的对象,提一点并非所有的对象都是在堆上进行分配,随着线程逃逸,标量替换等技术的发展,对象也有可能在栈上进行分配。
堆也是GC进行回收最主要的区域,容易出现OutOfMemoryError异常
新年代(young):老年代(old) = 1:2
其中新生代中,eden:survivor(From):survive(To)= 8:1:1
Minor GC:新建的对象会首先分配在eden区,Eden 区和 from 指向的 Survivor 区中的存活对象会被复制(此处采用标记 - 复制算法)到 to 指向的 Survivor区中,然后交换 from 和 to指针,以保证下一次 Minor GC时,to 指向的 Survivor区还是空的。
注:from与to只是两个指针,它们变动的,to指针指向的Survivor区是空的
Major GC:定义比较混乱,有点指的是Full GC有的指的是Old GC
Full GC:收集整个堆,包括young、old、永久代(1.8被移除,叫元空间)
Full GC触发条件有:
- System.gc()方法的调用
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC进入老年代的平均大小大于老年代的可用内存
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
举例题目
基于jdk1.8
public static void main(String[] args) {
Integer a = new Integer(100);
Integer b = new Integer(100);
//比较的是对象,false
System.out.println(a == b);
//valueOf方法会走缓存,true
System.out.println(Integer.valueOf(a) == Integer.valueOf(b));
Integer c = 100;
Integer d = 100;
//true
System.out.println(c == d);
Integer e = 128;
Integer f = 128;
//false,缓存的范围是-128-127
System.out.println(e == f);
int h = 128;
int i = 128;
//true
System.out.println(h == i);
int j = 100;
int k = 100;
//true
System.out.println(j == k);
String l = new String("m");
String m = "m";
//false
System.out.println(l == m);
String n = "o";
String o = "o";
//true
System.out.println(n == o);
String p = "q";
String q = new String("q");
//true
System.out.println(p == q.intern());
}
java对String特殊对待,1. 存于常量池的字符串常量 2. 用于存储普通对象及字符串对象
String p = "q",jvm首先在常量池中查找有没有"q",如果没有在常量池中创建,如果有直接取;
String q = new String("q"),new就是在堆中创建一个新的String对象,不管"q"在内存中是否存在,都会在堆中开辟新空间,虽然new String()方法并不会把"abc” 加入到String constant pool中,但是可以手动调用String.intern(),将new 出来的字符串对象加入到String constant pool中。
参考:https://blog.csdn.net/toward_south/article/details/103024109
https://www.cnblogs.com/semi-sub/p/12827335.html
网友评论