Java 面试 JVM 基础:运行时内存、类加载机制

本文详细介绍了Java JVM的运行时内存结构,包括线程私有区域(如虚拟机栈、本地方法栈、线程计数器)和公有区域(如堆、方法区、运行时常量池)。同时,探讨了Java对象的创建过程,包括类加载检查、内存分配、初始化等步骤。此外,文章还涵盖了类加载机制,包括加载、连接、初始化等阶段,以及双亲委派模型的工作原理,确保类加载的有序性和安全性。
摘要由CSDN通过智能技术生成

1. JVM 运行时内存

1.1 线程私有

① 线程计数器

  • 可以看作是当前线程所执行的字节码的行号指示器
  • 字节码解释器通过这个计数器的值来获取下一条需要执行的指令
  • 是唯一一个不会出现 OOM 的内存区域
  • 随着线程创建而创建、随着线程销毁而销毁

② 虚拟机栈

  • 是由一个个栈帧组成:每个栈帧里面都有局部变量表、操作数栈、动态链接、方法出口信息
  • 生命周期和线程一样
  • 局部变量表:存放了各种编译期可知的数据类型(基本数据类型)和对象引用
  • 可能会出现两种错误:SOF 和 OOM
    • SOF:若虚拟机栈内存不允许动态扩展,线程请求深度超过栈的最大深度
    • OOM:若堆中没有空闲内存、而且垃圾回收器也无法提供更多内存

③ 本地方法栈

  • 和虚拟机栈类似,只不过是服务于本地方法

1.2 公有区域

① 堆

  • 虚拟机中锁管理内存中最大的一块,“几乎” 所有的对象实例 、数组都在堆中分配
  • 从 JDK1.7 就已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用,那么对象可以直接再栈上分配内存
  • Java 堆是垃圾收集器管理的主要区域,也被称为 GC 堆,从垃圾回收的角度可以分为新生代和老年代,新生代再细 Eden、Form Survivor、To Survivor 默认比例是 8:1:1
  • 比较容易出现 OOM

② 方法区 元空间

  • 也被称为永久代,永久代就是 HotSpot 虚拟机对应规范中方法区的一种实现
  • 在 1.8 的时候已经被移除了对应的是 metaspace,使用的是直接内存
  • 常用参数:
    • -XX:MetaspaceSize=N 设置 MetaSpace 初始大小
    • -XX:MaxMetaspaceSize=N 设置 MetaSpace 的最大大小
  • 替换为元空间使用的是直接内存,限制就变成了系统内存,没有指定大小的话会根据应用需求动态调整大小
  • 元空间存放的是类的元数据,这样加载多少类的元数据不由 MaxPermSize 控制了,OOM 概率会更小

③ 运行时常量池

  • 是方法区的一部分
  • 1.8 之后字符串常量池还在堆,运行时常量池还在方法区(metaspace)
  • 无法申请到内存会 OOM

④ 直接内存

  • 不是运行时内存的一部分,但这部分内存也被频繁使用,也可能会 OOM

2. Java 对象的创建过程

  1. 类加载检查:检查这个指令的参数是否能在常量池定位到这个类的符号引用,是否已经被加载、解析和初始化过。如果没有,执行相应的类加载过程。
  2. 分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来
    • 指针碰撞: 使用内存规整的情况,用过的内存在一边,没用过的在另一边,中间有一个分界指针,向着没用过的内存方向将改指针移动对象内存大小
    • 空闲列表:内存不规整的情况,虚拟机会维护一个列表,列表会记录那些内存块是可用的,在分配时,找到足够大的内存块给对象,更新列表
  3. 初始化默认值
  4. 设置对象头
  5. 执行 init 方法

2.1 类加载机制

  • 可以分为:加载、连接、初始化、[使用、卸载],连接又可以分为验证、准备、解析
  • 加载:
    • 通过全类名获取定义此类的二进制字节流
    • 将字节流所代表的静态存储结构转化为方法去的运行时数据结构
    • 在堆中生成一个代表这个类的 class 对象,作为方法区中这些数据的访问入口
  • 验证:
    • 文件格式验证:验证 .class 文件是否符合规范,版本号、魔数等
    • 元数据验证:对字节码描述的信息进行语义分析,是否有父类,是否有冲突
    • 字节码验证:主要是通过数据流和控制流分析语法是否合法,符合逻辑。在这个阶段会对数据类型做验证
    • 符号引用验证:发生在虚拟机将符号引用转化为直接引用的时候,对类自身以外的信息进行验证
    • 是重要但非必需的,可以使用 -Xverfity:none 关闭大部分验证
  • 准备:
    • 为类变量分配内存并设置初始值,类变量 (static) 会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到 java 堆中
    • 这里的初始值是指默认值
  • 解析:
    • 符号引用
    • 直接引用
  • 初始化:
    • 类加载的最后一步,在这个阶段代码才开始正式执行
    • 如果没有被加载和连接,先加载并连接
    • 加入父类没有被初始化,优先初始化父类
    • 类中如果有初始化语句,会以此执行这些初始化语句

2.2 类加载器

  • BootstrapClassLoader:启动器类加载器,最顶层的加载类,由 C++ 实现,负责加载 %JAVA_HOME%lib 目录下的 jar 包和类被 -Xbootclasspath 参数指定路径中的所有类
  • ExtensionClassLoader:扩展类加载器,主要负责加载 %JRE_HOME%lib/ext 目录下的 jar 包和类,或被 java.ext.dirs 系统变量指定的路径下的 jar 包
  • AppClassLoader:应用程序类型加载器,面向用户的加载器,负责加载 classpath 下的所有 jar 包和类
  • 自定义类加载器:继承 ClassLoader

2.3 双亲委派模型

  • 每一个类都有对应它的类加载器,系统中的 ClassLoader 在协同时会默认使用双亲委派模型,在类加载的时候先判断是否被加载过,被加载过的直接返回
  • 会先把请求委派改父类加载器的 loadClass() 处理,当父类加载器为 null 的时候,会启动 BootstrapClassloader 作为父加载器
  • 自底向上检查是否被加载,自顶向下尝试加载类
  • 双亲委派模型保证了 Java 程序的稳定,可以避免类的重复加载,也保证了 Java 核心的 API 不被篡改
  • 不想使用双亲委派的话可以自己实现一个类加载器,重载 loadClass()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值