今天的博客主题
Java虚拟机 ——》 Jvm内存模型
这个图大家肯定都不陌生。学Java的都见过...
JDK(Java Development ToolKit)
就是 Java 开发工具箱。JDK是整个 Java 的核心里边包含了 JRE。除了包含 JRE 之外还包含了一些java javac等一些命令工具类。除此之外,还包含了 Java 源生的API。
JDK有以下三种版本:
J2SE(standard edition)标准版 是我们通常用的一个版本
J2EE(enterpsise edtion)企业版,使用这种JDK开发J2EE应用程序
J2ME(micro edtion)主要用于移动设备、嵌入式设备上的 Java 应用程序开发
JRE(Java Runtime Enviromental)
是 Java 运行时环境,所谓的 Java 运行时环境,就是为了保证 Java 程序能够运行时,所必备的一些基础环境,也就是它只是保证 Java 程序运行的,不能用来开发,而 JDK 才是用来开发的,所有的Java程序都要在JRE下才能运行。包括JVM和JAVA核心类库和支持文件。
与JDK相比,它不包含开发工具编译器、调试器和其它工具。
JRE 包含了 JVM
JVM(Java Virtual Mechinal)
因为 JRE 是 Java 运行时环境。Java 运行底层就是依赖于 JVM,即 Java 虚拟机,Java 虚拟机用来加载类文件信息。Java之所以有跨平台的特性,就是因为有 JVM。
回顾下JVM主要是做什么:加载类文件,执行代码。
那加载的类文件那去了,程序怎么运行的?
下面我们来探索下 JVM 的内部。
上图是整个 Java 运行时系统。也可以看出 JVM 包含了几个部分
ClassLoader(类加载系统):用于加载类文件
字节码执行引擎:1)虚拟处理器。2)解释器。3)即时(JIT)编译器。
JVM内存模型:1)类(方法)区。2)程序计数器。3)堆。4)栈。5)本地方法栈。
类加载器之前也介绍了,下面主要介绍下JVM的内存模型的几大块东西。
Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。
其中一些数据区域是在Java虚拟机启动时创建的,只有在Java虚拟机退出时才会被销毁。其他数据区域是每个线程的。每线程数据区在创建线程时创建,在线程退出时销毁。
1)类(方法)区
Java虚拟机有一个在所有Java虚拟机线程之间共享的方法区域。方法区域类似于用于传统语言的编译代码的存储区域,或者类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法。
方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是堆的一部分,但简单的实现可能选择不垃圾收集或压缩它。此规范不强制指定方法区域的位置或用于管理已编译代码的策略。方法区域可以具有固定的大小,或者可以根据计算的需要进行扩展,并且如果不需要更大的方法区域,则可以收缩。方法区域的内存不需要是连续的。
Java虚拟机实现可提供程序员或用户对方法区域的初始大小的控制,以及在大小可变的方法区域的情况下,对最大和最小方法区域大小的控制。
需注意:
如果方法区域中的内存无法满足分配请求,Java虚拟机将抛出OutOfMemoryError。
1-1)运行时常量池
运行时常量池是类文件中常量池表的每类或每接口运行时表示形式。它包含几种常量,从编译时已知的数字文本到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于传统编程语言的符号表,尽管它包含比典型符号表更广泛的数据范围。
每个运行时常量池都是从Java虚拟机的方法区域分配的。类或接口的运行时常量池是在Java虚拟机创建类或接口时构造的。
需注意:
在创建类或接口时,如果运行时常量池的构造需要的内存超过Java虚拟机的方法区域中可用的内存,Java虚拟机将抛出OutOfMemoryError。
2)程序计数器
也叫PC寄存器。 它主要记录当前正在执行的Java虚拟机指令的地址。
Java虚拟机可以同时支持多个执行线程。每个Java虚拟机线程都有自己的程序计数器。
在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法。
如果该方法不是本机的,则程序计数器包含当前正在执行的Java虚拟机指令的地址。
如果线程当前执行的方法是本机的,那么Java虚拟机的程序计数器的值是未定义的。
Java虚拟机的程序计数器足够大,可以容纳特定平台上的返回地址或本机指针。
3)堆
Java虚拟机有一个在所有Java虚拟机线程之间共享的堆。堆是运行时数据区,从中为所有类实例和数组分配内存。
堆是在虚拟机启动时创建的。对象的堆存储由自动存储管理系统(称为垃圾收集器)回收;对象从不显式释放。Java虚拟机不采用特定类型的自动存储管理系统,存储管理技术可以根据实现者的系统需求来选择。堆可以具有固定的大小,或者可以根据计算的需要进行扩展,并且如果不需要更大的堆,则可以收缩堆。堆的内存不需要是连续的。
Java虚拟机实现可以为程序员或用户提供对堆的初始大小的控制,如果堆可以动态扩展或收缩,还可以提供对最大和最小堆大小的控制。
需注意:
如果计算需要的堆比自动存储管理系统提供的堆多,Java虚拟机会抛出OutOfMemoryError。
4)栈(堆栈)
每个Java虚拟机线程都有一个私有Java虚拟机堆栈,与线程同时创建。Java虚拟机堆栈存储帧。Java虚拟机堆栈类似于传统语言(如C)的堆栈:它保存局部变量和部分结果,并在方法调用和返回中发挥作用。因为Java虚拟机堆栈除了push和pop帧之外从不被直接操作,所以帧可以被堆分配。Java虚拟机堆栈的内存不需要是连续的。
在Java虚拟机规范的第一版中,Java虚拟机堆栈被称为Java堆栈。
该规范允许Java虚拟机堆栈具有固定的大小,或者根据计算的需要动态地扩展和收缩。如果Java虚拟机堆栈的大小是固定的,那么在创建每个Java虚拟机堆栈时,可以独立选择该堆栈的大小。
Java虚拟机实现可以提供程序员或用户对Java虚拟机栈的初始大小的控制,以及在动态扩展或收缩Java虚拟机栈的情况下,对最大和最小大小的控制。
需注意:
如果线程中的计算需要比允许的更大的Java虚拟机堆栈,Java虚拟机将抛出StackOverflowError。
如果可以动态扩展Java虚拟机堆栈,并且尝试了扩展,但是没有足够的内存来实现扩展,或者没有足够的内存来为新线程创建初始Java虚拟机堆栈,则Java虚拟机抛出OutOfMemoryError。
5)本地方法栈
Java虚拟机的实现可以使用传统堆栈(俗称“C堆栈”)来支持本机方法(用Java编程语言以外的语言编写的方法)。本机方法栈也可以由Java虚拟机指令集的解释器的实现来使用,该指令集采用C语言。不能加载本机方法并且本身不依赖于传统栈的Java虚拟机实现不需要提供本机方法栈。如果提供了本机方法堆栈,则通常在创建每个线程时为每个线程分配本机方法堆栈。
此规范允许本机方法堆栈具有固定的大小,或者根据计算的需要动态地扩展和收缩。如果本机方法堆栈的大小是固定的,则在创建每个本机方法堆栈时,可以独立选择该堆栈的大小。
Java虚拟机实现可以向程序员或用户提供对本机方法栈的初始大小的控制,以及在本机方法栈大小可变的情况下,对最大和最小方法栈大小的控制。
需注意:
如果线程中的计算需要比允许的更大的本机方法堆栈,Java虚拟机将抛出StackOverflowError。
如果可以动态扩展本机方法堆栈并尝试扩展本机方法堆栈,但内存不足,或者如果内存不足,无法为新线程创建初始本机方法堆栈,则Java虚拟机会抛出OutOfMemoryError。
对于上面这些一通介绍,是不是觉得生硬难以理解很官方?没错这就是官方翻译来了,改成自己的大白话,通俗易懂
1)类方法区
2)程序计数器
3)堆
4)栈
5)本地方法栈
堆
-Xms2048M -Xmx2048M
新生代
-Xmn1024M
方法区
// 设定元空间初始空间大小,默认21M,达到该值就 full GC 进行类型卸载
‐XX:MetaspaceSize=256M
// 设定元空间最大值,默认-1,无限制,或受限于本地内存大小
‐XX:MaxMetaspaceSize=256M
栈
// 通常只有几百K,决定了函数调用的深度,每个线程独有的栈空间
// 不能大了,大了就适得其反了。递归会导致栈溢出。
-Xss512K
那JVM内存参数大小该如何设置呢?
只能说没有固定标准,需要根据实际项目情况分析,然后在设置。
就是尽可能让对象都在新生代里分配和回收,别让太多对象频繁的进入老年代,避免频繁的对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。