JVM
JVM工作流程
运行时数据区
- 方法区:被jvm加载的类的信息,常量,静态变量,即时编译后的代码等数据
- 堆区:存放对象实例,堆可被细分为新生代和老年代
- 虚拟机栈:方法执行的局部变量,方法出口信息等
垃圾回收机制
什么是垃圾?
没有任何引用的一个对象或者是循环引用但没有栈空间的引用指向任何一个对象。
怎么定位垃圾?
- 引用计数算法:给对象增加一个引用计数器,每当有地方引用这个对象时,计数器加一,当引用失效时,计数器减一,小于0证明没有引用,则释放这个对象的内存。缺点是不能解决对象之间循环引用的问题,所以jvm不用这个算法
- 根可达算法:从rootset遍历对象。
- rootset:线程栈变量,静态变量,常量池,JNI指针
垃圾回收算法
-
标记-清理算法:
- 标记阶段:从rootset开始搜索对象,只要能找到的就对它标记,没有被标记的被认为是垃圾对象
- 清理阶段:释放那些没有被标记过的对象的内存
- 缺点:内存碎片化问题:释放的内存不连续,导致后面无法配一段很大的内存
-
复制算法:
- 目的:为了解决内存碎片化的问题
- 步骤:
- 复制算法把堆分成了两个相等的空间tospace和fromspace分别用指针指着,对象内存分配只用tospace。
- 当tospace空间不够用时会触发GC,这个时候两个空间的指针交换,tospace变成fromspace,fromspace变成tospace。
- 对此时的fromspace从rootset开始遍历对对象进行标记拷贝到tospace。
- 最后对fromspace整体释放内存
- 这样就能使得tospace空间是连续的,解决了内存碎皮的问题
- 缺点:空间利用率低,只有tospace可以利用,所以内存利用率只有50%。
-
标记-压缩算法
- 是标记清理算法和复制算法的结合
- 标记阶段:同标记-清理算法的标记阶段
- 压缩阶段:压缩是内存压缩,把那些存活的对象放在一起,GC后的释放的内存就是连续的了,解决了内存碎片化的问题
- 缺点:为了实现把存活的对象放在一起,需要遍历堆两次,牺牲了性能
-
分代收集算法:将堆分为新生代和老年代。
- 新生代:对象存活率低,每次垃圾回收后都只有少量对象存活,所以此时用复制算法。
- 老年代:对象存活率高,大量存活的对象,此时用标记-清除(标记-整理)算法。
类加载机制
java类加载器分类
- 启动类加载器:BootstrapClassLoader,它是所有类加载器的父加载器。
- 除了类加载器之外其他的类加载器:
- ExtClassLoader:负责加载目录%JRE_HOME%/lib/ext目录中或-Djava.ext.dirs中参数指定的路径中的jar包和class文件
- Application ClassLoader 应用类加载器,也称为系统类加载器(System ClassLoader)负责加载当前应用classpath下的所有类
- 自定义类加载器
类加载器的加载顺序是怎样的呢?
答:java用的是双亲委派机制,这种模型要求除了顶层的BootStrapClassLoader外,其余的类加载器都应当有自己的父类加载器,如果一个类加载器收到了类加载请求,首先会把这个请求委派给父类加载器加载,只有父类加载器无法完成类加载请求时,子类加载器才会尝试自己去加载。
顺序: 盗图
类加载器加载过程
类从被加载到虚拟机内存中开始,到卸载出内存它的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载七个阶段。
而加载过程是其中的加载、验证、准备、解析、初始化
- 加载:把类信息在堆里生成一个java.lang.Class对象
- 验证:文件格式验证,元数据验证(验证这个类符不符合java语言要求),字节码验证(保证访法体中的类型转换有效,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但不能把一个父类对象赋值给子类数据类型),符号引用验证
- 准备:开始为类变量在方法区分配内存,并设置变量初始值
- 解析:虚拟机将常量池内的符号引用替换为直接引用(比如一个类的字段如果不是直接引用就得往它实现的接口中查找字段,然后在类的父类中查找字段,直到找到直接引用)
- 初始化:执行类构造器()方法的过程