JVM知识点汇总(1)

目录

一. Java 类加载过程

二. 描述一下 JVM 加载 Class 文件的原理机制

三 Java 内存分配

四. GC 是什么? 为什么要有 GC ?

五. 简述 Java 垃圾回收机制

六. 如何判断一个对象是否存活(或者 GC 对象的判定方法)


一. Java 类加载过程

Java 类加载需要经历以下 7 个过程

1. 加载

加载是类加载的第一个过程, 在这个阶段, 将完成以下三件事情:

  • 通过一个类的全限定名获取该类的二进制流
  • 将该二进制流中的静态存储结构转化为方法去运行时数据结构
  • 在内存中生成该类的 Class 对象, 作为该类的数据访问入口

2. 验证

验证的目的是为了确保 Class 文件的字节流中的信息不会危害到虚拟机. 在该阶段主要完成以下四种验证:

  • 文件格式验证: 验证字节流是否符合 Class 文件的规范, 如主次版本号是否在当前的虚拟机范围内, 常量池中的常量是否有不被支持的类型.
  • 元数据验证: 对字节码描述的信息进行语义分析, 如这个类是否有父类, 是否集成了不被继承的类等
  • 字节码验证: 是整个验证过程中最复杂的一个阶段, 通过验证数据流和控制流的分析, 确定程序语义是否正确, 主要针对方法体的验证. 如: 方法中的类型转换是否正确, 跳转指令是否正确等.
  • 符号引用验证: 这个动作在后面的解析过程中发生, 主要是为了确保解析动作能正确执行.

3. 准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值, 这些内存都将在方法区中进行分配. 准备阶段不分配累的实例变量内存, 实例变量将会在对象实例化时随着对象一起分配在 Java 堆中

public static int value = 456;  在准备阶段 value 初始值为 0. 在初始化阶段才会变为 456

4. 解析

该阶段主要完成符号引用到直接引用的转换动作. 解析动作并不一定在初始化动作完成之前, 也有可可能在初始化之后

5. 初始化

初始化时类加载的最后一步, 前面的类加载过程, 除了在加载阶段用户应用程序可以通过自定义类加载器参与外, 其余动作完全由虚拟机主导和控制. 到了初始化阶段, 才真正开始执行类中定义的 Java 程序代码

6. 使用

7. 卸载

二. 描述一下 JVM 加载 Class 文件的原理机制

Java 语言是一种具有动态性的解释型语言, 类(Class) 只有被加载到 JVM 后才能运行. 当运行指定程序时, JVM 会将编译生成的 .class 文件按照需求和一定的规则加载到内存中, 并组织成为一个完整的 Java 应用程序. 这个加载过程是由类加载器完成, 具体来说, 就是由 ClassLoader 和它的子类实现的. 类加载器本身也是一个类, 其本质是把类文件从硬盘读取到内存中

类的加载方式分为隐式加载和显示加载. 隐式加载值得是程序在使用 new 等方式创建对象时, 会隐式得调用类的加载器把对应的类加载到 JVM 中. 显示加载指的是通过直接调用 class.forName( 方法) 方法来把所需的类加载到 JVM 中

任何一个工程项目都是由许多类组成的, 当程序启动时, 只把需要的类加载到 JVM 中, 其他类只有被使用到的时候才会被加载, 采用这种方法一方面可以加快速度, 另一方面可以节约程序运行时对内存的开销. 此外, 在 Java 程序语言中, 每个类或接口都对应一个 .class 文件, 这些文件可以被看成是一个个可以被动态加载的单元, 因此只有部分类被修改时, 只需要重新编译变化的类即可, 而不需要重新编译所有文件, 因此加快了编译速度.

在 Java 语言中, 类的额加载是动态的, 它并不会一次性将所有的类全部加载后再运行, 而是保证程序运行的基础类完全加载到 JVM 中, 至于其他类, 则在需要的时候才加载

三 Java 内存分配

  • 寄存器: 我们无法控制
  • 静态域: static 定义的静态成员
  • 常量池: 编译时被确定并保存在 .class 文件中的常量值(final) 和一些文本修饰的符号引用(类和接口的全限定名, 字段的名称和描述符, 方法、名称和描述符)
  • 非 RAM 存储: 硬盘等永久存储空间
  • 堆内存: new 创建的对象和数组, 由 Java 虚拟机自动垃圾回回收器管理, 存取速度慢
  • 栈内存: 基本类型的变量和对象的引用变量(堆内存空间的访问地址), 速度快, 可以共享, 但是大小与生存期必须确定, 缺乏灵活性

Java 堆的结构:

JVM 堆的运行是数据区, 所有类的实例和数组都是堆上分配的内存. 它是 JVM 启动时被创建. 对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收

堆内存是由存活和死亡的对象组成的. 存活的对象是应用可以访问的, 不会被垃圾回收. 死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象. 一直到垃圾收集器把这些对象回收掉之前, 它们会一直占据堆内存空间.

四. GC 是什么? 为什么要有 GC ?

GC 是垃圾收集器的意思(GabageCollection), 内存处理是编程人员容易出现问题的地方, 忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃, Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的, Java 语言没有提供释放已分配内存的显示操作方法

五. 简述 Java 垃圾回收机制

在 Java 中, 程序员是不需要显示的去释放一个对象的内存的, 而是有虚拟机自动执行. 在 JVM中, 有一个垃圾回收线程, 它是低优先级的, 在正常情况下是不会执行的, 只有在虚拟机空闲或者当前对内存不足时, 才会触发执行, 扫描哪些没有被引用的对象, 并将它们添加到要回收的集合中, 进行回收.

六. 如何判断一个对象是否存活(或者 GC 对象的判定方法)

1. 引用计数法

所谓引用计数法就是给每一个对象设置一个引用计数器, 每当有一个地方引用这个对象时, 就将计数器加一, 引用失效时, 计数器就减一. 当一个对象的引用计数器为零时, 说明此对象没有被引用, 也就是 "死对象", 将会被回收.

引用计数法有一个缺陷就是无法解决循环引用问题, 也就是说当对象 A 引用对象 B, 对象 B 又引用对象 A, 那么此时 A、B对象的引用计数器都不为零, 也就是无法完成垃圾回收, 所以主流的虚拟机都没有采用这种算法.

2. 可达性算法(引用链法)

该算法的思想是: 从一个被称为 GC Roots 的对象开始向下搜索, 如果一个对象到 GC Roots 没有任何引用链相连时, 则说明此对象不可用

在 Java 中可以作为 GC Roots 的对象有以下几种:

  • 虚拟机栈中引用的对象
  • 方法区类静态属性引用的对象
  • 方法区常量池引用的对象
  • 本地方法栈 JNI 引用的对象

 当一个对象不可达 GC Roots 时, 这个对象并不会被立马回收. 而是处于一个死缓的阶段, 若要被真正的回收需要经历两次标记

如果对象在可达性分析中没有与 GC Roots 的引用链, 那么此时就被第一次标记并且进行一次筛选, 筛选的条件是是否有必要执行 finalize() 方法. 当对象没有覆盖 finalize() 方法或者已被虚拟机调用过, 那么就认为是没必要的. 如果该对象有必要执行 finalize() 方法, 拿满这个对象将会放在一个称为 F-Queue 的对队列中, 虚拟机会触发一个 finalize() 线程去执行, 此线程是低优先级的, 并且虚拟机不会承诺一直等待它运行完, 这是因为如果 finalize() 执行缓慢或者发生了死锁, 那么就会造成 F-Queue 队列一直等待, 造成了内存回收系统的崩溃. GC 对处于 F-Queue 中的对象进行第二次被标记, 这是该对象将移除 "即将回收" 集合, 等待回收.

你可能感兴趣的:(java面试题,jvm,java,开发语言)