Java 虚拟机(JVM)是Java程序的运行环境,它负责将 Java 源代码编译成字节码,并在运行时执行这些字节码。JVM 主要负责管理程序的内存、执行字节码、进行垃圾回收等任务。下面是 JVM 的内存区域及其功能的简要介绍:
JVM 内存区域主要分为线程共享区域(Java堆、方法区)、线程私有区域(程序计数器、虚拟机栈、本地方法栈)。
程序计数器也叫PC寄存器。是一块较小的内存空间。 在 JVM 规范中,每个线程都有自己的程序计数器,独立存储,互不影响,也就是说程序计数器是线程私有的。 如果当前线程执行的是一个 Java 方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是本地(Native)方法,则是空(Undefined)。 此内存区域是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域。
Java 虚拟机栈也是线程私有的,每个线程在创建时都会创建一个虚拟机栈,生命周期与线程相同。 虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)压入虚拟机栈,方法执行完毕栈帧出栈。 栈帧中存储着局部变量表、操作数栈、动态链接、方法出口等信息。
在 JVM 规范中对虚拟机栈规定了两类异常:
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常; 如果 Java 虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常。 可以通过参数-Xss 设定Java虚拟机栈空间大小。
本地方法栈与虚拟机栈类似,也是每个线程都会创建一个。区别是,虚拟机栈为虚拟机执行 Java 方法服务,本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
JVM 规范中并未对本地方法栈的实现做强制规定,具体虚拟机可以根据需要自由实现它。甚至在 Oracle Hotspot JVM 中将本地方法栈和虚拟机栈合二为一。 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出 StackOverflowError 和 OutOfMemoryError 异常。
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。它是Java内存管理的核心区域,用来存放 Java 对象实例,几乎所有的 Java 对象实例都被直接分配在堆上。 但是随着即时编译技术的进步和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化手段将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
从 JDK1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
Java堆是垃圾收集器管理的主要区域,因此也被称为GC堆。从垃圾回收的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的, 所以 Java堆还可以细分为:新生代(Eden区、From Survivor区 和 To Survivor区)和老年代。 无论如何划分,Java 中存储的都是对象的实例,这一点上是不变的,而将 Java堆细分的目的只是为了更好的回收内存,或者更快的分配内存。
如果在Java堆中没有足够的内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将抛出 OutOfMemoryError 异常。 通过参数-Xms 和 -Xmx 设定初始堆大小和最大堆大小。
方法区也是所有线程共享的一块内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 由于早期 HotSpot JVM 使用永久代实现方法区,很多人习惯将方法区称为永久代(Permanent Generation)。 方法区是Java虚拟机规范中的定义,是一种规范,而永久代则是一种是实现,一个是标准一个是实现, 其他的虚拟机(比如 BEA JRockit、IBM J9等)实现并没有永久代这一说法。
由于永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、类型的卸载)的效果比较难以令人满意, 我们通常使用 -XX:PermSize 和 -XX:MaxPermSize 设置永久代的大小, 32位机器默认的永久代大小为64M,64位的机器则为85M。 一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误(OOM)。
在 JDK6 的时候 HotSpot 团队就有放弃永久代逐步改为本地内存(Native Memory)来实现方法区的计划了, 到了 JDK7 已经把原本放在永久代的字符串常量池、静态变量等移到堆上分配, 在 JDK8 中彻底移除了永久代,将 JDK7 中永久代剩余的内容(主要是类的元数据)移到元空间(Metaspace)中。 移除永久代是为融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,不需要配置永久代。
元空间与永久代最大的区别在于:元空间并不在 Java虚拟机中,而是使用本地内存(Native Memory)。因此,默认情况下,元空间大小仅受本地内存限制。
类的元数据放入本地内存,字符串常量池和类的静态变量放入 Java堆中,这样加载多少类的元数据就不再由 MaxPermSize 控制,而由系统的实际可用空间来控制。通过参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 设定元空间初始值和最大值。
Java 类加载器(Class Loader)是 Java 运行时环境的一部分,负责将类的字节码加载到内存中,并将其转换为 java.lang.Class
的实例。Java 类加载器主要分为以下几种:
jre/lib/ext
目录下。ClassLoader
类,编写自定义的类加载器。Java 类加载器采用了双亲委派机制(Parent Delegation Model)。该机制的基本原则是:当一个类加载器收到类加载请求时,它首先不会自己去尝试加载,而是将请求委派给父类加载器去完成。每个类加载器都是如此,只有在父类加载器无法完成加载时,子类加载器才会尝试自己去加载。
类加载的过程包括以下步骤:
java.lang.Class
对象。类加载器采用懒加载的策略,即只有在需要使用某个类时才会加载该类。这样可以提高系统的启动速度,并减小资源消耗。在类加载器的工作过程中,双亲委派机制保证了类的一致性和防止类的重复加载。
Java 虚拟机中使用了多种垃圾回收算法和相应的垃圾回收器。以下是常见的垃圾回收算法和垃圾回收器,以及它们适用的场景:
1. 标记-清除算法(Mark and Sweep):
2. 复制算法(Copying):
3. 标记-整理算法(Mark and Compact):
4. 分代垃圾回收理论:
5. G1(Garbage-First)算法:
适用场景:
在实际应用中,选择合适的垃圾回收器需要综合考虑应用的特性、硬件环境、性能要求等因素。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。