JVM的优势
Java的跨平台性
一次编译,到处运行
JVM跨语言
举个例子
将groovy编译之后的class文件用jvm运行
-
先配置好groovy环境
-
新建HelloWorld.groovy
class HelloWorld { static main(args) { println "hello groovy..."; } }
-
将其编译成class文件
groovyc HelloWorld.groovy
-
用java命令运行groovy编译出来的HelloWorld.class文件
(注:全局搜索groovy-all-xxxx.jar的jar包,将其路径作为classpath后的参数)java -classpath "E:codingEnvironmentIntelliJ IDEA 2019.1.3libgroovy-all-2.4.15.jar;." HelloWorld
JVM整体结构
HotSpot VM
-
方法区和堆区是所有线程共享的内存区域;
-
Java栈又叫做jvm虚拟机栈。
-
执行引擎等同于翻译class文件的语言翻译器。
-
方法区(永久代)在jdk8中又叫做元空间
Metaspace
- 方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器(JIT编译器,英文写作Just-In-Time Compiler)编译后的代码等数据。
- 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
运行时数据区
概述
堆内存:保存所有引用数据的真实信息;
栈内存:基本类型、运算、指向堆内存的指针;
方法区:所以定义的方法的信息都保存方法区中,属于共享区;
程序计数器:是一个非常小的内存空间,用来保证程序依次执行;
本地方法栈:每一次执行递归方法的时候,都会将上一个方法入栈;
方法区(Method Area)
1. 什么是方法区(Method Area)?
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
2.方法区(Method Area)存储什么?
它存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等
域信息(成员变量)和方法信息可以看成在类型信息内
2.1 类信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
- 这个类型的完整有效名称(全名=包名.类名)
- 这个类型直接父类的完整有效名称(
java.lang.Object
除外,其他类型若没有声明父类,默认父类是Object) - 这个类型的修饰符(
public、abstract、final
的某个子集) - 这个类型直接接口的一个有序列表
除此之外还方法区(Method Area)存储类信息还有 - 类型的常量池( constant pool)
- 域(Field)信息
- 方法(Method)信息
- 除了常量外的所有静态(static)变量
方法区(Method Area)存储类信息请参考:参考博客
2.2 常量
- static final修饰的成员变量都存储于 方法区(Method Area)中
2.3 静态变量
- 静态变量又称为类变量,类中被static修饰的成员变量都是静态变量(类变量)
- 静态变量之所以又称为类变量,是因为静态变量和类关联在一起,随着类的加载而存在于方法区(而不是堆中)
- 八种基本数据类型(byte、short、int、long、float、double、char、boolean)的静态变量会在方法区开辟空间,并将对应的值存储在方法方法区,对于引用类型的静态变量如果未用
new
关键字为引用类型的静态变量分配对象(如:static Object obj;
)那么对象的引用obj会存储在方法区中,并为其指定默认值null
;若,对于引用类型的静态变量如果用new
关键字为引用类型的静态变量分配对象(如:static Person person = new Person();
),那么对象的引用person 会存储在方法区中,并且该对象在堆中的地址也会存储在方法区中(注意此时静态变量只存储了对象的堆地址,而对象本身仍在堆内存中);这个过程还涉及到静态变量初始化问题,可以参考博客:静态变量初始化相关
2.4 方法(Method)
- 程序运行时会加载类编译生成的字节码,这个过程中静态变量(类变量)和静态方法及普通方法对应的字节码加载到方法区。
- 但是!!!方法区中没有实例变量,这是因为,类加载先于对应类对象的产生,而实例变量是和对象关联在一起的,没有对象就不存在实例变量,类加载时没有对象,所以方法区中没有实例变量
- 静态变量(类变量)和静态方法及普通方法在方法区(Method Area)存储方式是有区别的
栈(Stack)
栈(Stack):线程私有的内存区域
- 每个方法(Method)执行时,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口信息等
- 栈中所存储的变量和引用都是局部的(即:定义在方法体中的变量或者引用),局部变量和引用都在栈中(包括final的局部变量)
- 八种基本数据类型(byte、short、int、long、float、double、char、boolean)的局部变量(定义在方法体中的基本数据类型的变量)在栈中存储的是它们对应的值
- 栈中还存储局部的对象的引用(定义在方法体中的引用类型的变量),对象的引用并不是对象本身,而是对象在堆中的地址,换句话说,局部的对象的引用所指对象在堆中的地址在存储在了栈中。当然,如果对象的引用没有指向具体的对象,对象的引用则是
null
Java堆(Java Heap)
Java堆(Java Heap) :被所有线程共享的一块内存区域,在虚拟机启动时创建。Java堆(Java Heap)唯一目的就是存放对象实例。所有的对象实例及数组都要在Java堆(Java Heap)上分配内存空间。
- 由关键字new产生的所有对象都存储于Java堆(Java Heap)
- !!! 实例变量(非static修饰的成员变量)和对象关联在一起,所以实例变量也在堆中
- java数组也在堆中开辟内存空间
栈、堆和方法区的关系
Java代码大致执行流程
java源程序--编译javac-->字节码文件.class-->类装载子系统生成反射类(存入方法区)--->运行时数据区(五大块儿)--->执行引擎-->解释执行+编译执行(JIT)-->操作系统(Win,Linux,Mac JVM)
作用
将高级语言转化为机器能听得懂的机器指令
Hotspot中方法区的变动
关于方法区的结构,在过去的版本jdk1.6/1.7/1.8当中均有变动,故在此提前声明:
- jdk1.6及之前:有永久代(permanent generation) ,静态变量、字符串常量池存放在 永久代上。
- jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中。注意:
- jdk1.8及之后: 无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍留在堆空间.
JDK6
JDK7
注意:
jdk1.8及之后: 无永久代,类型信息、字段、方法、常量保存在本地内存的元空间。
但字符串常量池、静态变量仍留在堆空间。
除此之外,元空间(或称方法区),不再使用虚拟机内存,而是使用本地内存。
JDK8
参考资料
(3条消息)JVM学习笔记(三)------内存管理和垃圾回收_走向架构师之路-CSDN博客