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