1.JVM是什么
JVM(Java Virtual Machine) 是Jvm虚拟机的一种规范。
C:\Users\pc>java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
当安装了JDK以后,可以看到默认是已安装HotSpot虚拟机的,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,所以Java语言是跨平台语言。
这也就体现出了Java语言的特点: 一处编译,多处执行,跨平台,安全性(1.摒弃指针 2、GC垃圾回收机制:(自动释放回收长时间不使用对象的内存))
结构:JDK & JRE & JVM三者结构图
- jvm分布结构图
输入class文件到虚拟机,虚拟机输出不同平台的CPU指令。存储器、控制器、运算器 为JVM运行时数据区
2.一个java文件的编译运行过程
工具:javac编译、javap反编译
Java源文件 -> Javac编译成class文件 -> JVM虚拟机运行Class文件实则为转成对应操作系统的机器码 -> 各大操作系统
2.1 javac
Javac将源文件转成字节码(词法分析,语法分析,语义分析,字节码生成码)
class文件 一个存放二进制文件,存放16进制的字节
2.2 javap
将class文件反编译为cpu指令
//原始代码
static int num = 1;
static final int a1 = 2;
int a2 = 2;
//通过 javap -p -v xxx.class 反编译出来的结果
//会在堆内存中开辟一个num=0的空间
static int num;
descriptor: I
//权限
flags: ACC_STATIC
static final int a1;
descriptor: I
flags: ACC_STATIC, ACC_FINAL
//通知虚拟机给静态变量赋值 意味着准备阶段就已经初始化值了
ConstantValue: int 2
//非static变量的实例变量会随着类变量分配到java堆 在初始化阶段由类构造器初始
int a2;
descriptor: I
3.类加载机制
目的:将class类加载进虚拟机内存空间中
3.1 装载Loading
找到class文件所在的全路径,然后装载到内存中。具体实现由类加载器实现ClassLoader
类加载器类型总览
类加载器 | 功能 |
---|---|
Bootstrap ClassLoader 启动类加载器 | 加载$JAVA_HOME中jre/lib/rt.jar里面所有的class或者Xbootclasspath选项指定的jar包 |
ExClassLoader 扩展类加载器 | 加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或者-Djava.ext-dirs指定目录下的包 |
AppClassLoader 系统类加载器 | 加载classpath中指定的jar包及Djava.class.path指定目录下的类和jar包 |
Custom ClassLoader自定义类加载器 | 通过ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如Tomcat,Jboss都会根据规范自行实现ClassLoader |
类加载方式
- 全盘负责
- 只要由一个加载器负责某个类,那么这个类的父类以及子类通通由这个类加载器负责
- 父类委托
- 判断顶层类是否已经加载
- 缓存机制
- 同名的类只会被加载一次 所有必须使用到缓存
自定义类加载器 java实现
实现ClassLoader,加载类不能放到类路径下(否则会被AppClassLoader加载)
3.2 链接Linking
验证Verify:验证类的正确性
准备Prepare:为类或接口的静态字段赋值默认值
private static int a =10; 此时会先赋值默认值0
解析Resolve:动态将运行常量池中的符号引用转为直接引用(物理内存地址)
3.3 初始化Initalization
为变量附上真正的值,例如给上面的static int a赋值为10
什么情况触发初始化
- new
- 访问某个类或接口的静态变量
- 调用类的静态方法
- 反射
- 初始化某个类的子类
- 虚拟机指定启动类
4.JVM运行时数据区
根据前面的jvm分布结构图,分为了5个区域,栈、堆、程序计数器、方法区、本地方法栈
41. 方法区 Method Area
- java线程共享的区域,生命周期和进程绑定
- 存储类的元数据信息,模板信息
- 内存不足时报
OutOfMeoryError: Metespace
- 方法区存放不会轻易改变的内容,在jdk8中又被称为元空间
Metaspace
- 运行时常量池 Run-Time Constant pool
- String str = “hello” hello在常量池,可以被多引用
- String str = new String(“hello”) 1.7以前hello在堆内存,1.8后String对象存放堆内存中,hello存放字符串常量池中
4.2 堆Heap
- 虚拟机最大的一块线程共享区域,生命周期和进程绑定
- 内存不足时报
OutOfMeoryError: Java heap space
,通过-Xms20M -Xmx20M
设置堆内存大小 - 类的或者成员变量的实例对象,例如new String(“hello”) String对象在堆内存
- JDK1.8已经将String常量池从方法区移入堆中了,那么new String(“hello”) 会生成两个对象,String对象存放堆内存中,hello存放字符串常量池中,如果常量池存在则不创建
4.3 栈 Stacks
- 线程私有,通过抢占CPU时间片来执行栈帧
- 栈帧是最小的工作单位,方式是压栈、出栈,栈结构先进后出、后进先出
- 递归导致栈深度不够用会报
StackOverfolwError
,栈帧深度默认大小1M,可以通过-Xss 128k
修改 - 栈帧压栈出栈 thread mian() -> a() ->b() -> c(),mian()一定是最后执行完的,一定是先进后出的
4.4 栈帧的组成结构
public static int calc(int num1,num2){
num1 = 2;
int res = num1 + num2;
return res;
}
calc(1,8);
//通过javap反编译内容 查看下calc栈帧
public static int calc(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
//操作数栈2个 局部变量3个 参数值2个
stack=2, locals=3, args_size=2
0: iconst_2 //push一个int值压入操作栈中 2
1: istore_0 //将操作数栈的值出栈赋值给局部变量的第0个 num1=2
2: iload_0 //第0个局部变量的值压入操作数栈 2
3: iload_1 //第1个局部变量的值压入操作数栈 8
4: iadd //执行一个相加的操作 10
5: istore_2 //将操作数栈的值出栈赋值给局部变量的第2个 res = 10
6: iload_2 //第二个局部变量的值压操作数栈 res
7: ireturn //第二个局部变量出操作数栈 返回10 然后通过方法返回地址到main栈帧
- 局部变量表,存储局部变量的
- 操作数栈,用来保存运算的临时栈
- 动态链接,将符号引用转为直接引用
- 方法的返回,继续执行后面的方法
java中的一行代码,可能是多个汇编指令
4.5 程序计算器PC Register
- 和操作系统中的寄存器一样,当线程执行过程中被其他线程抢占时间片先去执行了,需要记录正在执行的方法和位置
- 线程私有
4.6 本地方法栈Native Method Stacks
- 线程执行某个方法过程中需要调用本地Native方法,这类方法是没有JVM没有维护的,所以每个线程都有一个本地方法栈,调用到的地方通过链接过去
5.创建一个对象在内存中都做了什么?
- 先将指定的class文件加载进内存
- 执行main方法时,在栈内存中开辟了属于main线程的栈空间,分配给变量p
- new关键字向堆内存申请一个实例对象空间,分配一个内存地址值
- 在实例对象空间进行属性的空间分配,并进行了默认初始化
- 对空间中的属性动态将符号引用转为直接引用
- 对实例的构造代码块初始化,调用该实例类的构造方法,进行构造方法初始化
- 将首地址赋值给p,p变量就引用了该实例,指向了该对象
6.堆栈总结
一句话足以:垃圾堆,精华栈
以上就是本章的全部内容了。
上一篇:MongoDB第二话 – MongoDB高可用集群实现
下一篇:JVM第二话 – JVM内存模型以及垃圾回收
人寿几何?逝如朝霜。时无重至,华不再阳