什么是虚拟机
负责解释执行字节码文件的就是虚拟机,虚拟机是Java跨平台的关键.
上层代码编译为统一的class文件,执行时统一由虚拟机将字节码文件解释为操作系统对应的机器码
垃圾收集
如何判断对象是否需要回收
- 引用计数:每个对象分配一个计数器,对象被引用时计数器+1,引用断开时计数器-1.
- 可达性分析:选取一部分对象做为根节点,从根节点能到达的对象即存活,根节点不能到达的对象则需要回收。根节点的选取
垃圾回收算法
标记-清除算法
判定算法决定哪些对象需要回收,JVM统一回收被标记的对象。
缺点:清除后空闲空间不连续
标记-复制算法
将空间分成三部分,一块较大的Eden空间,两块较小的Survivor空间(虚拟机默认分配是8:1:1)
对象分配时只使用Eden空间+一块Survivor空间,回收时将这两个空间中被标记清除的对象清除,将空间中幸存的对象复制到另外一块Survivor空间。
缺点:当对象存活率高时,复制操作过多影响效率
一般在新生代使用
标记-整理算法
根据回收算法标记后将幸存对象移动到内存的一端,再进行清除。
GCROOT
- 方法区中的静态变量
- 局部变量表中的变量
- Class对象
- 常量池中的对象
- synchronized锁对象
JVM的内存区域
方法区、堆、虚拟机栈、本地方法栈、程序计数器
程序计数器:用于指示下一条要执行的字节码,线程私有
虚拟机栈:每个方法执行时会在虚拟机栈中创建对应的栈帧,方法的调用和结束对应着栈帧的入栈和出战。栈帧的大小在类被加载进虚拟机时即确定。栈帧中保存着局部变量表、操作数栈、动态连接、方法返回地址等信息 线程私有的
堆:Java中所有的对象都在这里进行分配,存放着Java中所有引用对象的实例。线程共享
方法区:类的信息、静态变量、常量、即时编译后的代码缓存。线程共享
本地方法栈:存放着本地方法执行时的栈帧信息,作用类似虚拟机栈。不过是针对本地方法的。
类加载
概念
类加载流程
加载-验证-准备-解析-初始化
加载:①通过全限定名获取字节码文件二进制字节流②将字节流中的数据转换为方法区中运行时数据③在堆中生成对应的Class对象做为方法区数据的访问入口
验证:①验证class文件中的信息是否符合规范②对字节码中的数据进行各种验证
准备:①为类变量分配内存并设置初值,这个初值是各数据的默认值
解析:将常量池中的符号引用解析为直接引用,比如在调用方法时,只有符号引用是没有办法定位方法的。解析阶段可能会在后面真正调用方法是才做
初始化:调用执行类的init方法和clinit方法。init方法是编译器将类中的普通变量的初始化、构造代码块、构造方法结合而成的,clinit方法则是编译器将类中的静态变量的初始化、静态代码块结合而成的方法。并且clinit方法只执行一次,有jvm保证该方法的线程安全问题。
触发类加载的条件
- 直接使用new关键字时
- 使用一个类的static字段(且为被final修饰)或方法
- 通过反射调用时
- 类的子类被初始化时,当前类没有被加载则触发类加载
- main函数所在的类
双亲委派机制
class的加载依赖类加载器,类加载器接收到类加载请求时会先将加载请求叫给父级的类加载器,父加载器先尝试加载,无法加载时才会回到子类加载器去加载。
双亲委派机制的好处是,确保了核心类不会被用户自定义的类覆盖
破坏双亲委派
- 通过自定义类加载器并重写loadClass方法,自定义加载逻辑就可以破坏双亲委派机制
- 还有一种方法是使用线程上下文类加载器
Tomcat的类加载机制
tomcat的类加载机制与虚拟机的类加载机制正好相反,tomcat中类加载器接到加载请求时会先看自己能不能加载如果可以就自己直接加载,如果不能加载才交给父加载器。