1. jvm
java虚拟机, 运行在操作系统层面, 可以隔绝操作系统对java应用程序的影响, 运行在jvm上具备可移植性
jvm提供了java语言运行环境, 用来执行jvm指令
jdk提供了java工具集, 有一系列针对jvm指令的工具
1.1. 类加载器
编译: 编译器(jdk提供的javac也是在编译)将java代码编译成.class文件
词法分析->语法分析->语义分析,最终将java文件编译成.class文件
类的加载:
①类在第一次被调换时,会被类加载器进行加载
②加载类会先加载类导入的包
③类加载器加载类时, 会先使用父类加载器进行加载
④父类加载失败使用子类进行加载(双亲委派)
⑤类加载器加载类, 会将类读取到jvm中(加载), 解释器翻译成机器码
⑥类执行初始化
加载: 类加载器加载.class文件进入jvm内存(加载后, 程序执行和编译文件已经无关, 即使删除.class文件也不影响应用程序的执行)
链接:
验证: 验证代码是否正确
准备: 创建静态变量等,为初始化做准备
初始化: 执行静态等, 类的初始化
初始化:
验证->准备->解析, 加载即是将class文件读入到jvm的运行时数据区, 并且准备好静态执行环境
类加载器:
①启动类加载器(Bootstrap): 加载$JAVA_HOME中jre/lib/rt.jar里所有的class,
②拓展类加载器(Extension): 加载$JAVA_HOME中jre/lib/*.jar里所有的class,启动类加载器和拓展类加载器一起加载了java类库下的所有的类
③应用类加载器(AppClassLoader): 加载java编译器编译后的所有类(自定义类, 在./out/路径下)
④自定义类加载器: 自定义
类加载器采用双亲委派原则加载, 加载时会逐级先使用父类类加载器加载, 父类加载失败会采用子类加载
原因: 类加载时, 会先加载该类import的文件, 该import的文件可能被父类加载器加载过了
1.2. 运行时数据区
jvm管理的所有内存
包括本地方法栈, 虚拟机栈, 堆, 方法区, 程序计数器, 方法区, 常量池
1.3. 执行引擎
执行jvm代码, 调度运行时数据区空间分配
本地方法存在本地方法库, 执行交给本地方法执行引擎, 本地执行引擎执行结束交给执行引擎
①加载完成后, 执行引擎在方法区创建空间, 存放静态, 常量, 类信息, 方法信息, 若实例了类则会在堆的新生代Eden中分配空间创建实例
②执行引擎在栈中创建空间, 栈中分配栈帧, 栈帧执行方法, 本地变量表保存对堆的引用, 操作数栈中指向jvm指令, 在动态链接中保存对调用的方法的引用
③调用方法生成新的栈帧, 进行压栈操作, 逐步执行完方法调用的栈帧, 然后执行方法,
④执行引擎根据栈帧的正常返回或异常返回做出响应
2. 程序计数器
绑定线程, 相当于指针, 指向栈中下一行执行的地址值
每一个线程都存在一个程序计数器
3. 栈
包括java虚拟机栈和本地方法栈
应用程序执行再栈帧, 其中有着对象的引用
执行引擎执行方法时, 栈会给该方法分配一个内存空间(栈帧)
栈帧:
**本地变量表: **存储对堆中类的实例的引用
操作数栈: jvm的操作指令(栈中具体执行jvm指令的地方)
动态链接: 调用别的方法, 对别的方法的引用
正常方法执行: 方法正常执行完成
中断方法执行: 方法异常
当生成栈帧填满栈会栈内存溢出
4. 堆
存放对象实例
4.1. 年轻代
Eden(伊甸园): 新创建的对象都会在Eden创建内存空间, 当Eden内存达到阈值会进行MinorGC, Eden占比年轻代80%
survivor(幸存者区): 包括from区和 to区, 垃圾回收使用MinorGC执行垃圾回收算法使用复制法, 两个survivor区都占比年轻代(10%)
4.2. 老年代
年轻代进行一定次数(默认15次)MinorGC后进入老年代, 或者对象比Survivor内存大, 在一次MinorGC后也会直接进入老年代
老年代满了之后会进行FullGC, 当fullGC之后再在老年代中存储比剩余空间还大的实例时, 会OOM
4.3. 永久代(jdk8以前)
存放方法区和共享常量池, jdk1.8移除永久代, 方法区移动到元空间, 永久代在堆内, 元空间在堆外
5. 方法区
存放类的信息, 静态信息, 方法信息, 即时编译器编译后的代码缓存
即时编译器编译后的代码缓存超出方法区内存也会导致OOM
元空间:
-
元空间在堆外
-
默认直接操作物理内存
-
方法区放进了元空间, 常量池放进了堆中
6. 运行时常量池
存放常量, 在方法区中
7. GC算法介绍
7.1. 垃圾判断
引用计数法: 判断对象的引用次数来决定是否回收对象, 目前没有垃圾收集器采用这种
可达性算法: 这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
在Java语言中,可以作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI(Native方法)的引用对象
真正标记以为对象为可回收状态至少要标记两次。
第一次标记:不在 GC Roots 链中,标记为可回收对象。
第二次标记:判断当前对象是否实现了finalize() 方法,如果没有实现则直接判定这个对象可以回收,如果实现了就会先放入一个队列中。并由虚拟机建立一个低优先级的程序去执行它,随后就会进行第二次小规模标记,在这次被标记的对象就会真正被回收了!
7.2. 垃圾回收算法
复制法 : 将内存平均分为两部分, from区存储, 存满后将存活的对象复制到to区, 然后清空from区, 循环往复(默认15次),
标记清除法: 存储满了后, 第一次遍历对应该回收的对象进行标记, 第二次遍历对标记对象进行清除
标记压缩法: 基于标记清除法, 清除后进行压缩, 将所有存活的对象都向一端移动, 以便空出连续空间
8. GC
进行GC时, 其他所有线程都会停止运行, 只有GC线程可以正常执行.
筛选垃圾使用的是可达性判断算法,
可达性算法通过GC-ROOT作为根, 来分析执行链, 若执行链没有对象的引用则判断为垃圾, 进行清除
minorGC: 发生在Survivor区, 使用复制算法, from区复制所有存活对象到to区, 然后清除from区, 完成一次GC, 然后from和to区调换,
majorGC: 发生在老年代, 使用标记清除法和标记整理法进行GC, 每一次MajorGC几乎都会伴随着一次MinorGC
对象经过MinorGC一定次数(默认15次)后会被移送老年代, 或者对象比Survivor内存大经过一次GC也会送入老年代
fullGC: 一次MinorGC+MajorGC, 组合清理, 很慢, 机会每一次majorGC都会是FullGC, 应尽量避免FullGC
, 使用标记清除法和标记整理法进行GC, 每一次MajorGC几乎都会伴随着一次MinorGC
对象经过MinorGC一定次数(默认15次)后会被移送老年代, 或者对象比Survivor内存大经过一次GC也会送入老年代
fullGC: 一次MinorGC+MajorGC, 组合清理, 很慢, 机会每一次majorGC都会是FullGC, 应尽量避免FullGC