【JVM】jvm内存区域分析

2 篇文章 0 订阅

jvm(java virtual machine)其实就是Java虚拟机的缩写

Java程序是交给jvm执行的,我们在谈Java内存区域划分实际上就是指JVM内存区域划分。

Java程序具体执行流程:

Java程序执行的过程,首先是把Java源代码文件(.java)会被Java编译器编译为字节码文件(.class)文件,然后有JVM中的类加载器加载各个类中的字节码文件,加载完成之后,交由JVM执行引擎。在整个程序执行过程中,jvm会用一段空间来存储执行期间所需要用到的数据和相关信息,这段空间一般被称为Runtime Data Area(运行时数据区),也就是我们常说的jvm内存(在Java中常说的内存管理就是针对这段空间进行管理的即分配和回收内存空间)。

JVM运行时数据区域

堆(Heap)

是线程共享的

堆的特点是 堆内存最大、堆是线程共享、堆的目的就是存放对象,几乎所有的对象实例都在此分配(随着优化技术的更新,某些数据也会被放在栈上等)

因为堆占用内存空间最大,堆也是Java垃圾回收的主要区域(重点对象),因此也称作“GC堆”(Garbage Collected Heap)

堆的GC操作采用分代收集算法

堆分了新生代和老年代

新生代又分为:Eden空间、From Survivor(S0)空间、To Survivor(S1)空间

Java虚拟机规范规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。也就是说堆的内存是一块块拼接起来的。要是增加堆空间时,往上“拼凑”即可(可扩展性),当堆中没有内存完成实例分配,并且堆也无法再扩展时,回抛出OutOfMemoryError异常。

  • 控制参数

     -Xms设置堆的最小空间大小。-Xmx设置堆的最大空间大小。-XX:NewSize设置新生代最小空间大小。-XX:MaxNewSize设置新生代最小空间大小。

方法区(Method Area)

线程共享

方法区与堆区有很多共性:线程共享、内存不连续、可扩展、可垃圾回收,与堆一样当无法扩展时会抛出OutOfMemoryError异常。

正因为如此相像,Java虚拟机规范把方法区描述为堆的一个逻辑部分,但目前实际上是与Java堆分开的(Non-Heap)

方法区个性化的是,它存储的是已经被虚拟机加载的类信息、常量、静态变量、

即时编译器编译后的代码等数据

方法区的内存回收主要目标是针对常量池的回收和堆类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是回收确实是有必要的。

  • 控制参数

      -XX:PermSize 设置最小空间 -XX:MaxPermSize 设置最大空间。

虚拟机栈(JVM Stack)

是线程私有

虚拟机栈是线程私有的、生命周期与线程相同。它描述的是Java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量、操作数栈、动态链接、方法出口等信息。每个方法从调用直至完成的过程,都对应着一个栈帧从入栈到出栈的过程。每当一个方法执行完成时,该栈帧就会弹出栈帧的元素作为这个方法的返回值,并且清楚这个栈帧,Java栈的栈顶的栈帧就是当前正在执行的活动栈,也就是当前正在执行的方法。就像是组成动画的一帧一帧的图片,方法的调用过程也是由栈帧切换产生结果。

很多人会把Java内存分为堆内存(Heap)和栈内存(Stack),这种划分的流行只能说明大多数开发人员最关注、与对象内存分配关系最亲密的内存区域是这两块,其中所指的栈就是JVM栈,或者说是JVM栈中的局部变量表部分。实际上Java内存区域的划分比这个要复杂。

  • 控制参数

      -Xss控制每个线程栈的大小。

  • 局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。包括8种基本类型(int,short,byte,char,double,float,long,boolean)、对象引用(reference类型)和returnAddress类型(指向一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间(slot),其余的数据类型只占用1个。  如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈动态扩展无法由申请到足够的内存时会抛出OutOfMemoryError异常
  • 操作数栈(Operand Stack)也称作操作栈,是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部变量表或者对象实例的字段种复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。
  • 动态链接:Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态链接(Dynamic Linking)。
  • 方法返回:无论方法是否正常完成,都需要返回到方法被调用的位置,程序才能继续进行。

程序计数器(Program Counter Register)

是线程私有的

程序计数器(Program Counter Register)是一块较小的内存空间,他可以看作是当前线程所执行的字节码的行号指令器。在虚拟机概念模型里(概念模型,各种虚拟机可能会通过一些更高效的方式实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令:分支、跳转、循环、异常处理、线程恢复等基础操作都会依赖计数器完成。每个线程都有独立的程序计数器,用来在线程切换后能恢复到正确的执行位置,各条线程之间的计数器互不影响,独立存储。此块内存区域是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机的多线程是通过线程轮流切换分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。

因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,称这类内存区域为“线程私有”的内存。

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果是正在执行的是Native方法,这个计数器值则为空(Undefined).

方法本地栈(Native Method Stack)

本地方法栈(Native Method Stacks)与虚拟机栈作用相似,也会抛出StackOverflowError和OutOfMemoryError异常。

区别在于虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈是为虚拟机使用Native方法服务。

  • 控制参数

       在Sun JDK中本地方法栈和方法栈是同一个,因此也可以用-Xss控制每个线程的大小。

jvm内存结构&对应参数设置

preview

Java编译器第一次编译,会把源文件编译成字节码文件;第二次将字节码文件里的字节码指令编译成机器指令(会缓冲在方法区里)

所以JIT编译器主要负责:程序编译的性能 

Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构。 

 

自定义类是有系统类加载器加载的。

jvm的生命周期:

虚拟机的启动:Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是虚拟机的具体实现指定的。 

虚拟机的执行:一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序;程序开始执行时他才运行,程序结束时他就停止;执行一个所谓的Java程序的时候,真真正正在执行的是一个叫Java虚拟机的进程。

虚拟机的推出:程序正常执行结束;程序在执行过程中遇到了异常或者错误而异常终止;由于操作系统出现错误而导致Java虚拟机进程终止;某线程调用Runtime或System类的exit方法,或者Runtime类的halt方法,并且Java安全管理器也允许这次exit或者halt操作;除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载Java虚拟机时,Java虚拟机的退出情况。

Sun Classic VM:

Exact VM:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值