这里写目录标题
JVM
JVM核心
Java能够摆脱平台的原因就是Java源代码经过编译器变成字节码,字节码经过JVM解释成不同的机器码。而不同的操作系统有不同的JVM。
JVM包含两个组件和两个子系统
- 类加载子系统:将class文件加载到运行时数据取
- 执行引擎:由jit和垃圾回收器组成
- 本地接口:与本地库进行交互
- 运行时数据区:JVM的核心。包含方法区,程序计数器,堆,本地方法栈,虚拟机栈
运行时数据区
-
程序计数器:当前线程所执行的字节码的行号指示器
-
Java虚拟机栈:会创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。
-
本地方法栈:主要区别是Java虚拟机栈执行的是Java方法服务,而本地方法栈执行Native方法服务
-
Java堆:垃圾回收的主战场,几乎所有的对象实例都是在这里分配的。Java堆又分为老年代(2/3,大对象直接进入老年代)和新生代(1/3)对象优先在 Eden 区分配。
-
方法区:于存储类信息、常量、静态常量和即时编译后的代码等数据
运行时常量池是方法区的一部分(用于存放编译期生成的各种字面常量和符号引用)
对象创建的过程:
- 虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。
- 类加载通过后,接下来分配内存。分配内存有两种,一种是指针碰撞另一种是空闲列表。
- 因为对象的创建在虚拟机种并发执行的会有危险。可以采用CAS+失败重试或者每个线程在Java堆中预先分配一块内存
除了程序计数器外都有可能发生outofmemory
内存异常有两种情况,
- 一种是内存泄漏:就是申请了内存,但是没有释放,导致内存空间浪费。通俗说法就是有人占着茅坑不拉屎。
- 另一种是内存溢出:是申请内存时,JVM没有足够的内存空间。通俗说法就是去蹲坑发现坑位满了。
方法调用:
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
JVM垃圾回收
判断对象是否存活:
- 引用计数法:有一个bug就是循环引用
- 可达性分析:从根节点向下延申,对象没有被引用,则判断有无finall关键字,无则直接回收有则进行标记,标记两次后进行回收
垃圾回收算法
-
标记清除:效率高但是有碎片化空间
-
标记复制:可有空间变少但是可用内存变小了
-
标记整理:结合的折中方法,将存活对象放在一端
-
分代收集:新生代用复制,老年代用清除或者整理。永久代基本不清理
Minor GC新生代、Major GC老年代
由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。它的收集频率较低,耗时较长。
垃圾收集器
- 新生代:Serial、PraNew、Parallel Scavenge(动态调节及及进行高吞吐量,高效利用 CPU)
- 老年代:Serial Old、Parallel Old、CMS(尽可能少的减少停顿时间,有碎片)
- 最强的G1(后台维护一个 优先列表):整体上是标记整理,局部是复制
JVM的类加载
类被编译成字节码文件(具有语言无关性)加载到JVM,并对数据进行验证,准备,解析,初始化后,最终形成可以被虚拟机直接使用的Java类型就是类加载机制。准备,解析,初始化都是在程序运行期间完成的。这种策略虽然会增加一些额外的开销,但是增加了灵活性。
类加载的七个过程:
- 加载:获取此类的字节码、静态存储结构转化为方法区的运行时数据结构、生成对象
- 验证:文件格式验证,字节码验证,符号引用验证
- 准备:为类变量分配内存(被 static 修饰的变量)并设置类变量初始值(数据类型的零值)的阶段。这些变量所使用的内存都将在方法区进行分配
- 解析:将常量池内的符号引用(一组符号来描述所引用的目标,符号可以上任何形式的字面量,只要使用时能无歧义地定位到目标即可。)替换为直接引用(直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄)的过程
- 初始化:执行类中定义的 Java 程序代码。初始阶段是执行类构造器 () 方法的过程。
使用
卸载
类加载器:
- 启动类加载器(c++):加载Java的核心类库
- 扩展类加载器:加载Java的扩展类库
- 应用程序类加载器:Java应用的类一般都是在这加载的
- 用户自定义加载器:用户自己定义的加载器,继承自ClassLoader
双亲委派模式:
当一个类加载器收到一个类加载请求的时候,他首先不会自己先去加载,而是交给父类加载,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个类加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。双亲委派模式主要是保证加载的唯一性,防止类重名而造成的歧义