理解 JVM 必看-----超详细

JVM 运行流程

javac 命令将 java 文件编译成 class 文件,jvm 通过类加载器(ClassLoader)将 class 文件从磁盘中加载到运行时数据区,命令解释器 执行引擎 将字节码翻译成底层操作指令交给 CPU 执行,这个过程需要调用其他语言的接口(C++)本地库接口

内存区域划分

虚拟机栈(线程私有)

每个方法在执行的同时创建一个栈帧(Stack Frame),存储局部变量表、操作栈等

记录方法之间的调用关系,不记录方法的具体指令

本地方法栈(线程私有)

跟虚拟机栈类似,给本地方法使用,Java1.8 之后跟 虚拟机栈合并了

栈容量由参数 Xss(栈大小)设置,

  • 当栈帧深度超过限度是会报错,StackOverFlow异常
  • 当内存超过限制会报 OMM异常 OOM(out of memory)

堆(线程共享)

  • 占用内存最大的区域

堆的大小可以设置,Xms(最小启动内存)memory start、Xmx(最大运行内存)memory max

超出限制会报异常 OOM(out of memory)

方法区(线程共享)

  • 存放类信息,继承关系,实现哪些接口
  • 被final修饰的常量(成员属性),静态变量
  • 方法的内部指令、访问权限,返回值,参数

JDK7 使用永久代实现方法区,JDK8 使用 元空间

对于 HotSpot 来说JDK8 的元空间内存属于本地内存,并且将字符串常量池移动到了堆上

程序计数器(线程私有)

  • 占用内存最小

存放执行到了哪条指令(地址)

类加载机制

图来源于《深入理解Java虚拟机》

加载

在方法区中生成类对象

连接

验证: 验证格式释放符合规范要求

准备: 为类中的静态变量分配内存,并初始化值,例如 有这样一个成员变量 private static int num = 111,初始化的值是 0,不是 111

解析:初始化常量,将常量池内的符号引用替换为直接引用

初始化

初始化静态变量,执行静态代码块

双亲委派模型

对于Java虚拟机来说只有两中加载器

  • 启动类加载器(Bootstrap ClassLoader),使用C++实现,是虚拟机自身的一部分
  • 其他所有的类加载器,使用Java实现,并且全部继承自抽象类 java.lang.ClassLoader

对于Java开发者来说有三层加载器

  • 启动类加载器(BootstrapClassLoader)加载标准库中的类(String ArrayList)
  • 扩展类加载器(ExtensionClassLoader)加载扩展的类
  • 应用程序类加载器(ApplicationClassLoader)加载自己写的类,第三方库中的类
  • 自定义类加载器

前三层类加载器是JVM 自带的,这三种类加载器的搜索目录不同,约定了父子关系,依次向上询问是否已加载

一、加载自己写的类

先从ApplicationClassLoader开始,再到 ExtensionClassLoader,再到 BootstrapClassLoader,到了顶层就开始从这一层查找,因为是自己写的类,当前层的类加载器管理的目录中没有这个类,所以就返回到下一层ExtensionClassLoader,继续找,发现还是找不到,继续返回下一层,找到了就加载,找不到就会报 ClassNotFoundException 异常

二、加载标准库中的类

先从ApplicationClassLoader开始,再到 ExtensionClassLoader,再到 BootstrapClassLoader,到了顶层就开始从这一层查找,找到了就加载

双亲委派模型的优点

  • 防止程序员自己写的类将标准库中的类覆盖了,例如,写了一个完全限定名是 java.lang.String 的类,那么加载的时候,到了 BootstrapClassLoader 时会加载标准库中的String类,这样就避免了标准库的类被覆盖

垃圾回收机制(GC)

对于虚拟机栈、本地方法栈、程序计数器这个三部分而言,当线程结束或者方法结束他们的生命也就结束了,对于方法区来说,里面存放的是类对象,对于GC 不迫切,所以,我们垃圾回收的关注点在

垃圾回收就是对象的回收


死亡对象的判断算法

1、引用技术算法

Java 未使用这种方法,Python 使用的就是这种算法

每当有一个引用指向这个对象,这个对象的引用计数器就+1,当计数器为 0 时,就代表是死亡对象了

缺陷:无法解决 循环引用 的问题,当多个线程对同一个引用计数器修改时,需要考虑线程安全问题

2、可达性分析

以一些特殊的变量(对象)为起点,称为 GC Roots,只有当对象可以从起点出发访问到的,我们才称为可
达,而那些不可达的对象就会被回收

可以作为GC Roots 的对象

1、虚拟机栈中的引用的对象

2、方法区中静态属性引用的对象

3、方法区中常量引用的对象

4、本地方法栈中 JNI(Native 方法)引用的对象

关于可达,就拿二叉树来说,只要得到根节点就能访问到这颗树的每个节点


垃圾回收算法

1、标记-清除算法

先标记出哪些对象需要回收,在标记完成后统一进行标记对象的回收,直接在原地回收,对于还存活的对象不做处理,所以会带来内存碎片,效率也不高

2、复制算法

将内存空间分成大小相等的两部分,一次只使用一部分,当需要进行垃圾回收的时候,会将此块区域还存活的对象复制到另一块上,最后把原本那块直接全部清理掉

优点:算法实现简单,运行高效,并且不会带来内存碎片的问题

3、标记-整理算法

整理前:

假如2,4 是需要回收的对象,则把3,5复制到1的后面,这样就不会产生内存碎片

缺点:效率不高

4、分代算法

将内存分区域,不同区域使用上述算法中的某种算法

将 Java 堆分为新生代 和 老年代,新生代分为 Eden(伊甸区)、Survivor From、Survivor To,其中这三种空间的内存容量比值为 Eden:Survivor From:Survivor To = 8:1:1

每经历一轮 GC,存活的对象的年龄就加1,多大的年龄会进入老年区,不同的JVM有不同的参数,所以我们不必关注这点,而是关注策略本身即可

  • 新生成的对象放在 Eden 里面
  • 一般来说 Eden 里的对象活不过一轮 GC,所以适用于 复制算法
  • 经过一轮GC的对象就会被放在 Survivor To 区,在经过一轮 GC,Eden 和 Survivor To 中存活的对象就会被搬到 Survivor From 中,所以都有些对象就会反复在 Survivor From 和 Survivor To 中,最后经过若干次的GC,Survivor 中的达到一定年龄的对象就会进入 老年代
  • 老年代中的对象一般存活率比较高,GC 的频率也会降低,所以不适合复制算法,而是选择标记-整理算法

注意:如果有一个很大的对象,这个对象会直接存在于老年代,因为很大的对象不适合复制算法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值