jvm架构
- 类加载器:加载.class文件到内存,创建对应的class对象
- 执行引擎:对命令进行解析
- 本地方法库:融合不同开发语言的原生库为java所用
- 运行时数据区:包含方法区、堆、程序计数器、java虚拟机栈、本地方法栈
jvm运行时数据区 | jvm内存模型
运行时数据区也叫作jvm内存模型,JVM Memory Model,JMM
- 线程共享数据区:方法区、堆,所有线程共享;
- 线程隔离数据区:程序计数器、java虚拟机栈、本地方法栈,线程独有,生命周期与所在线程的生命周期一致
上面的图片只是jvm规范,不同种类的jvm实现可能有差异。
主流的jvm是HotSpot,HotSpot实现方法区的方式如下
- jdk7及之前:用永久代(PermGen)实现方法区,并把永久代放到堆中,作为堆的一部分,使用堆内存,受堆内存大小限制。
- jdk8及之后:以后使用元空间(MetaSpace)实现方法区,使用本地内存,不使用堆内存,受本地内存(机器内存)限制。
程序计数器
Program Counter。程序计数器占用很小的一块内存空间,每条线程都有1个程序计数器,用来记录当前线程的执行到的位置
线程是一个独立的执行单元,由CPU控制执行,当前线程的时间片用完之后,程序计数器记录执行到的位置,重新获取时间片后,恢复到该位置,从该位置继续往下执行。
java虚拟机栈
java虚拟机栈用于方法执行。
jvm执行每个方法时都会创建一个栈帧(Stack Framel),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法调用到执行完毕,对应栈帧在java虚拟机栈中入栈到出栈的过程。
局部变量表存储编译期可知的局部变量,如果是局部变量是对象,则存储该对象的引用。
public void a(){
}
public void b{
a();
}
b()的执行流程:
- 先创建b的栈帧,b的栈帧入栈
- 执行到调用a()时,先创建a的栈帧,a的栈帧入栈
- a()执行完毕=>a的栈帧出栈,执行b的栈帧
- b()执行完毕,b的栈帧出栈
java虚拟机栈,栈,先进后出
如果递归调用、且递归没有出口,则执行时会给递归方法创建无数栈帧,java虚拟栈内存耗尽,最终栈溢出,抛出 StackOverflowError 异常。
本地方法栈
Native Method Stack。本地方法栈和java虚拟机栈差不多,区别是:
- java虚拟机栈用于执行java语言编写的方法
- 本地方法栈用于执行本地方法(native方法),native方法就是用其它语言编写的方法,比如c、c++写的,因为用关键字native修饰,所以叫做native方法
方法区
Methoad Area。方法区用于存储已加载的类的信息、常量、静态变量等。
方法区之常量池
常量池是方法区的一部分,加载类的class对象后,类、方法的信息,及类中的字面量、符号引用会存储在方法区的常量池中。
字面量指的是8种基本类型、String的常量值,常量池会缓存这些类型的常量值,使得在运行过程中速度更快、更节省内存。
String str1="hello";
String str2="hello";
String str3=new String("hello");
String str4=new String("hello");
System.out.println(str1==str2); //true
System.out.println(str3==str4); //false
System.out.println(str1==str3); //false
常量池使用HashSet存储数据,没有重复的数据(常量)。
创建str2时,常量池中已经有了"hello",str2直接指向这个已存在的常量,str1==str2;
str3、str4都是new出来的,new出来的是对象,对象存储在堆中,每次new都是重新分配内存空间。str1是常量池中的常量,str3是堆中的对象,不相等;str3、str4都是堆中的对象,但地址不同,不相等。
==与equals()
==,如果是基本数据类型、""的String,根据值(内容)来比较;如果是引用类型(包括new出来的String),根据对象地址来比较;如果一个是前者,一个是后者,自然不等。
equals(),根类Object的equals()和==完全等价,因为本来就是使用==来判断
// Object类的equals()源码
public boolean equals(Object obj) {
return (this == obj);
}
但jdk自带的类基本都重写了equals()、hashCode(),equals()根据内容来比较,我们自定义的类,如果没有继承jdk自带的类、没有重写equals(),那equals()就是继承Object的,和==等价。
堆
Heap。堆是jvm中最大的一块内存,是垃圾收集器管理的主要区域,几乎所有的数组、对象都在堆中分配。
堆、栈的区别
直接内存 Direct Memory
jvm运行时数据区还有一块可操作的内存区域,直接内存。只是直接内存并非像其它5个区域一样是jvm进程私有,所以一些划分方案没有划分到jvm运行时数据区中。
在 JDK 1.4 中引入了 NIO,可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在堆内存和堆外内存之间的数据拷贝,提高了性能。使用的不是jvm进程本身的内存,也叫作直接内存。