一.类加载过程
- 加载:把.class文件找到,读取文件内容
- 验证:根据jvm虚拟机规范检查.class文件的格式是否符合要求
- 准备:给类对象分配内存空间(内存初始化全0)
- 解析:针对字符串常量进行初始化,把符号引用转化为直接引用
- 初始化:真正的针对类对象里面的内容进行初始化,加载父类,执行静态代码块的代码
其中,字符串常量需要一块内存空间,用来存字符的实际内容,另外还有一个引用,保存这个内存空间起始地址;
类加载之前,字符串常量是在.class文件中,而此时的引用并非这个字符串常量的真正的地址,而指的是这个常量在文件中的”偏移量”;类加载之后,才真正把这个字符串常量放到内存中,此时才有内存地址,此时的引用才能转换成直接引用;
java程序运行后,只有用到了的类才会被加载(懒汉模式);1构造类的实例;2调用这个类的静态方法/使用静态属性;3加载子类,就会先加载其父类。一旦加载过后,后续再使用就不必再加载
二.双亲委派模型
双亲委派模型描述的就是类加载过程找到.class文件的基本过程;
jvm默认提供了三个类加载器
- BootStrapClassLoader 负责加载标准库中的类
- ExtensionClassLoader 负责加载jvm扩展库中的类
- ApplicationClassLoader 负责加载用户提供的第三方库/用户项目代码中的类
上述的三个类存在“父子关系”,1是2的父类,2是3的父类
类加载的工作配合过程:
首先就先从ApplicationClassLoader开始,但是其并非真正加载,而是交给父亲,这时进行到ExtensionClassLoader,也不是真正加载,也是交给父亲,进行到BootStrapClassLoader,这个类加载器也想先给父亲,可父亲为null,就由自己加载,搜索标准库中的类去加载,若没找到,就交由子类去加载,最终又返回到ApplicationClassLoader,若还没找到,就会抛出类找不到的异常;
实现上述加载逻辑顺序的目的是:保证标准库中的类优先加载,然后是扩展库,最后是用户类,避免用户在自己的代码中写的类名和标准库的名字重合,这样一来jvm加载的还是标准库中的类;另一方面,类加载器用户也是可以自己实现的,用户自定义的类加载器,也可以加入到上述流程中,就可以和现有加载器配合使用;当然自已写的加载器,可以去遵守双亲委派模型,也可以不遵守;
“双亲”委派模型是机翻的,其英文为parent,实为父亲委派模型;
三。垃圾回收机制GC
垃圾回收指的就是把不用的内存自动释放了;C/C++中内存回收需要程序员手动释放,若不释放,这块内存空间就会持续存在,直到进程结束。也可能会导致内存泄漏,服务器不停的运转的,可用内存越用越少,后续可能出现严重问题;
java go python php js大部分语言用GC来实现垃圾回收;
GC好处自然是省心,程序员的代码简单,不易出错;坏处是需要消耗额外的系统资源和性能;
C++是追求性能的极致的,GC是违背初心的,还会引起STW问题(stop the world);
STW: 若内存中的垃圾过多,触发了GC操作,开销很大,也有可能涉及到锁操作,导致业务代码无法正常执行,形成卡顿,极端的情况下,几十毫秒到上百毫秒;
GC主要针对堆进行释放的,以“对象”为基本单位进行回收,整个对象不再使用时会被回收;
GC实际的工作过程:1,找到垃圾/判定垃圾 2,释放对象
1.找到垃圾
1)引用计数(非java做法)
给每个对象分配一个计数器,每创建一个引用指向给对象,计数器+1,销毁引用就-1;
简单有效,但java不用,因为:
a:内存空间浪费多,每个对象都要分配一个计数器,若按4字节算,对象一旦多了,占用的额外空间就很多,特别是一个对象本身就4字节,加入计数器,体积大一倍;
b:存在循环引用的问题
2)可达性分析(java做法)
java中的对象都是通过引用来指向并访问的,整个java中的对象时通过一种链式/树形结构穿起来的
可达性分析,就是从组织所有对象树的根结点出发,遍历树,所有能被访问到的对象标记成“可达”
jvm自己拿着一个所有对象的名单,通过上述遍历,把可达的标记出来,不可达的就进行回收了
可达性分析相对于引用计数来说会慢一点,但并不需要一直执行,隔段时间分析一遍就好了
2.清理垃圾
1)标记清除
简单粗暴,直接把不用的内存给释放掉
缺点是内存碎片问题,被释放的空间是零散的,不是连续的;
2)复制算法
解决了内存碎片化问题,把整个内存分成两半,把不是垃圾的对象复制到另一半,然后把一空间释放掉;
缺点是:1空间利用率低,2若是垃圾少,有效对象多,复制成本大
3)标记整理
保证了空间利用率,把后面有用的对象往前面搬运
缺点效率不高,搬运的空间大,开销大
上述的三种为基本策略,做一个复合策略“分代回收”
刚new出来的对象,年龄是0,放在伊甸区,熬过一轮GC的对象就要放到幸存区,幸存区的对象也要周期性接受GC考验,两个幸存区通过复制算法来回拷贝,幸存区中一段周期考验后就进入老年代了,老年代都是年纪大的对象,生命周期普遍更长,GC扫描的频率变低了,如果老年代的对象成垃圾了,就使用标记整理的方式进行释放;