一、JVM概述
虚拟机
在windows中,虚拟一个运行环境
分为系统虚拟机VMware,程序虚拟机JVM
jvm作用
负责将字节码加载到内存中(运行时数据区)
负责存储数据
把字节码翻译为机器码,执行
垃圾回收
jvm组成部分
1.类加载器(负责加载字节码文件)
2.运行时数据区(存储时数据,堆,java虚拟机栈(运行java自己的方法),方法区,程序计数器,本地方法栈)
3.执行引擎(更底层,把字节码翻译为机器码)
4.本地方法接口
5.垃圾回收
二、类加器
作用
负责从硬盘/网络中加载字节码信息
加载到内存中(运行时数据区的方法区中)
类加载过程
加载
使用IO读取字节码文件
转换并存储,为每个类创建一个Class类的对象
存储在方法区中
链接(验证,准备,解析)
验证:对字节码文件格式进行验证,文件是否被污染
对基本的语法格式进行验证
准备:为静态的变量进行内存分配
public static int value = 123; value 在准备阶段后的初始值是0,而不是123
静态常量在编译期间就初始化
解析:将符号引用转为直接引用
将字节码中的表现形式,转为内存中表现(内存地址)
初始化
类的初始化,为类中定义的静态变量进行赋值
public static int value = 123; value 在初始化阶段后的初始值是123.
类什么时候会被加载(初始化)
1.在类中运行main方法
2.创建对象
3.使用类中的静态变量
4.反射Class.forName("类的地址");
5.子类被加载
以下两种情况类不会被初始化
static finall int b = 20;//编译期间赋值的静态常量
System.out.println(User.b)
User[] user = new User[10];
类加载器
具体的负责加载类的一些代码
1.引导类加载器,用c/c++语言开发的,jvm底层的开发语言,负责加载java核心类库.
与java语言无关的.
2.扩展类加载器
java语言编写的,由 sun.misc.Lanucher$ExtClassLoader 实现,继承ClassLoader类
从JDK系统安装目录的jre/lib/ext子目录(扩展目录)下加载类库
3.应用程序类加载器
java语言编写的,由 sun.misc.Launcher$AppClassLoader 实现,派生于ClassLoader类
4.自定义类加载器
双亲委派机制
加载一个类时,先委托给父类加载器,如果父类加载器还有父类,就继续向上委托,直到引导类加载器父级找到就返回,父级如果最终没有找到,就委派给子级加载器最终没有找到,报 ClassNotFoundException.
为了先确保加载系统类
双亲委派机制,是Java提供的类加载的规范,但不是强制不能改变的。 我们可以通过自定义的类加载器,改变加载方式。
打破双亲委派机制
可以通过继承ClassLoader类,重写loadClass/findClass方法。实现自定义的类加载。典型的tomcat中,加载部署在tomcat中的项目时,就使用的是自己的类加载器
三、运行时数据区
1.程序计数器
是一块很小的内存空间,用来记录每个线程的指令位置,
是线程私有的,每个线程都拥有一个程序计数器,生命周期与线程一致
是运行时数据区中,唯一一个不会出现内存溢出的空间
运行速度最快
2.本地方法栈
用来运行本地方法的区域
是线程私有的
空间大小可以调整可能会出现栈溢出
3.java栈
基本作用特征
栈是运行单位,管理方法的调用运行
是用来运行Java方法的区域。
可能会出现栈溢出。
是线程私有的
运行原理
先进后出的结构
最顶部的称为当前栈帧
栈帧结构
一个栈帧中包含:
局部变量表(存储在方法中声明的变量)
操作数栈(实际计算运行)
动态链接
String name;
void A(){
B();//B方法的地址
}
方法返回地址
4.堆
基本作用特征
是存储空间,用来存储对象,是内存空间最大的一块区域,
在Jvm启动时就被创建,大小可以调整
本区域是存在垃圾回收的,是线程共享的区域
堆空间的分区:
年轻代(新生区/新生代)
伊甸园区(对象刚刚创建存储在此区域)
幸存者1区
幸存者2区
老年代(老年区)
为什么要分区
可以根据对象的存活的时间放在不同的区域,可以区别对待
频繁回收年轻代,较少回收老年代。
创建对象,在堆内存中分布
1.新创建的对象,都存储在伊甸园区
2.当垃圾回收时,将伊甸园中垃圾对象直接销毁,将存活的对象,移动到幸存者1区
3.之后创建的新对象还是存储在伊甸园区,再次垃圾回收到来时,将伊甸园区中的存货对象移动到幸存者2区,同样将幸存者1区的存活对象移动待幸存者2区,每次保证一个幸存者区是空的,相互转换
4.每次垃圾回收时,都会记录此对象经历的垃圾回收次数,当一个对象经历过15次回收,仍然存活,就会被移动到老年代 垃圾回收次数,在对象头中有一个4bit的空间记录,最大值只能是15.
5.老年区回收次数较少,当内存空间不够用时,才会回收老年代
堆空间的配置比例
默认的新生代与老年代的比例:1:2 可以通过 -XX:NewRatio=2 进行设置
如果项目中生命周期长的对象较多,就可以把老年代设置更大
在新生代中,伊甸园区和幸存者区比例:8:1:1
可以通过 -XX:SurvivorRatio=8进行设置
对象垃圾回收的年龄 -XX:MaxTenuringThreshold=<N>
分代收集思想 Minor GC、Major GC、 Full GC
对年轻代进行垃圾回收成为 Minor GC/yong GC 是频繁进行的回收
对老年代进行垃圾回收称为Major GC/old GC 回收的次数较少
Full GC 整堆收集 尽量避免:
System.gc();时 程序员几乎不用
老年区空间不足
方法区空间不足
堆空间的参数设置
官方文档
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
字符串常量池
在jdk7之后,将字符串常量池的位置从方法去移动到了堆空间中,
因为方法去的回收,在整堆收集时发生,回收频率低
堆空间回收频率高
5.方法区
作用:主要用来加载存储的类信息,以及即时编译期编译后的信息,以及运行时常量池
特点:在jvm启动时创建,大小也是可以调整的,是线程共享,也会出现内存溢出
方法区,堆,栈交互信息
方法去存储类信息(元信息)
堆中存储创建的对象
栈中存储对象引用
方法区大小设置
-XX:MetaspaceSize 设置方法区的大小
windows jdk默认的大小是21MB
也可以设置为-XX:MaxMetaspaceSize 的值为-1,既没有限制,没有限制,就可以使用计算机内存
可以将初始值设置较大一点,减少了FULL GC 发生
方法区的内部结构
类信息
即时编译期编译后的信息、
运行时常量池(指的就是类中各个元素的编号)
方法区的垃圾回收
在FULL GC 时方法区发生垃圾回收
主要时回收类信息,类信息回收条件比较苛刻,满足一下3点即可
1.在队中,该类及其子类的对象都不存在了
2.该类的类加载器不存在了
3.该类的Class对象不存在了
也可以认为类一旦被加载就不会被卸载了
特点总结
程序计数器,java栈,本地栈是线程私有的
程序计数器不会出现内存溢出
Java栈,本地栈,堆,方法区可能会出现内存溢出
Java栈,本地栈,堆,方法区大小是可以调整的
堆,方法区是线程共享的,是会出现垃圾回收的
四、本地方法接口
什么是本地方法
用native关键字修饰的方法,没有方法体
为什么用本地方法
java语言需要与外部的环境进行交互(例如需要访问内存,硬盘,其他的硬件设备),直接访问操作系统的接口即可
java的jvm本身开发也是在底层使用到了C语言
五、执行引擎
作用:将加载到内存中的字节码(不是直接运行的机器码),解释/编译为不同平台的机器码
.java---编译--->.class 在开发期间,有jdk提供的编译器(javac)及进行源码编译(前端编译)
.class(字节码)---解释/编译--->机器码(后端编译,在运行时,由执行引擎完成的)
翻译器:将字节码逐行解释执行,效率低
编译器:(JIT just in time 即时编译器):将字节码编译,缓存起来,执行更高效,不会立即使用编译器,将一些频繁执行的热点代码进行编译,并缓存到方法区中,以后执行效率提高了
程序启动后,先使用解释器立即执行,省去了编译时间
程序运行一段时间后,堆热点编译进行缓存,提高后续执行效率
采用的解释器和编译器结合的方案