JVM内存区域划分

1.什么是jvm?
(1)jvm是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的。
(2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。
(3)JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。


2.jdk、jre、jvm是什么关系?
(1)JRE(Java Runtime Environment),也就是java平台。所有的java程序都要在JRE环境下才能运行。
(2)JDK(Java Development Kit),是开发者用来编译、调试程序用的开发包。JDK也是JAVA程序需要在JRE上运行。
(3)JVM(Java Virtual Machine),是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。
Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。


3.JVM原理
(1)jvm是java的核心和基础,在java编译器和os平台之间的虚拟处理器,可在上面执行字节码程序。
(2)java编译器只要面向jvm,生成jvm能理解的字节码文件。java源文件经编译成字节码程序,通过jvm将每条指令翻译成不同的机器码
,通过特定平台运行




4. JVM执行程序的过程
1) 加载.class文件 
2) 管理并分配内存 
3) 执行垃圾收集
JRE(java运行时环境)由JVM构造的java程序的运行环,也是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,
因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。
JVM在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,
提供一个完整的Java运行环境,因此也就虚拟计算机。


操作系统装入JVM是通过jdk中Java.exe来完成,
通过下面4步来完成JVM环境:
1) 创建JVM装载环境和配置 
2) 装载JVM.dll 
3) 初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例
4) 调用JNIEnv实例装载并处理class类。


5. JVM的生命周期
1) JVM实例对应了一个独立运行的java程序它是进程级别 
a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void 
main(String[] args)函数的class都可以作为JVM实例运行的起点 
b) 运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程 
c) 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出 
2) JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的




Java程序的运行是通过Java虚拟机来实现的。通过类加载器将class字节码文件加载进JVM,然后根据预定的规则执行。Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些内存区域被统一叫做运行时数据区。Java运行时数据区大致可以划分为5个部分。如下图所示。在这里要特别指出,我们现在说的JVM内存划分是概念模型。具体到每个JVM的具体实现可能会有所不同。具体JVM的实现我只会提到HotSpot虚拟机的实现细节。




程序计数器
程序计数器是一块较小的内存空间,它可以看成是当前线程所执行的字节码的行号指示器。程序计数器记录线程当前要执行的下一条字节码指令的地址。由于Java是多线程的,所以为了多线程之间的切换与恢复,每一个线程都需要单独的程序计数器,各线程之间互不影响。这类内存区域被称为“线程私有”的内存区域。 
由于程序计数器只存储一个字节码指令地址,故此内存区域没有规定任何OutOfMemoryError情况。


虚拟机栈
Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 
一个栈帧就代表了一个方法执行的内存模型,虚拟机栈中存储的就是当前执行的所有方法的栈帧(包括正在执行的和等待执行的)。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。我们平时所说的“局部变量存储在栈中”就是指方法中的局部变量存储在代表该方法的栈帧的局部变量表中。而方法的执行正是从局部变量表中获取数据,放至操作数栈上,然后在操作数栈上进行运算,再将运算结果放入局部变量表中,最后将操作数栈顶的数据返回给方法的调用者的过程。(关于栈帧和基于栈的方法执行,我会在之后写两篇文章专门介绍。敬请期待☺) 
虚拟机栈可能出现两种异常:由线程请求的栈深度过大超出虚拟机所允许的深度而引起的StackOverflowError异常;以及由虚拟机栈无法提供足够的内存而引起的OutOfMemoryError异常。


本地方法栈
本地方法栈与虚拟机栈类似,他们的区别在于:本地方法栈用于执行本地方法(Native方法);虚拟机栈用于执行普通的Java方法。在HotSpot虚拟机中,就将本地方法栈与虚拟机栈做在了一起。 
本地方法栈可能抛出的异常同虚拟机栈一样。



Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例:所有的对象实例以及数组都要在堆上分配(The heap is the runtime data area from which memory for all class instances and arrays is allocated)。但Class对象比较特殊,它虽然是对象,但是存放在方法区里。在下面的方法区一节会介绍。Java堆是垃圾收集器(GC)管理的主要区域。现在的收集器基本都采用分代收集算法:新生代和老年代。而对于不同的”代“采用的垃圾回收算法也不一样。一般新生代使用复制算法;老年代使用标记整理算法。对于不同的”代“,一般使用不同的垃圾收集器,新生代垃圾收集器和老年代垃圾收集器配合工作。(关于垃圾收集算法、垃圾收集器以及堆中具体的分代等知识,我之后会专门写几篇博客来介绍。再次敬请期待☺) 
Java堆可以是物理上不连续的内存空间,只要逻辑上连续即可。Java堆可能抛出OutOfMemoryError异常。


方法区
方法区与Java堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 所有的字节码被加载之后,字节码中的信息:类信息、类中的方法信息、常量信息、类中的静态变量等都会存放在方法区。正如其名字一样:方法区中存放的就是类和方法的所有信息。此外,如果一个类被加载了,就会在方法区生成一个代表该类的Class对象(唯一一种不在堆上生成的对象实例)该对象将作为程序访问方法区中该类的信息的外部接口。有了该对象的存在,才有了反射的实现。 
在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。但是在之后的HotSpot虚拟机实现中,逐渐开始将方法区从永久代移除。Java7中已经将运行时常量池从永久代移除,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间。


运行时常量池
运行时常量池是方法区的一部分,关于运行时常量池的介绍,请参考我的另一篇博文:String放入运行时常量池的时机与String.intern()方法解惑。我还是花了些时间在理解运行时常量池上的。
直接内存


JDK1.4中引用了NIO,并引用了Channel与Buffer,可以使用Native函数库直接分配堆外内存,并通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。 
如上文介绍的:Java8以及之后的版本中方法区已经从原来的JVM运行时数据区中被开辟到了一个称作元空间的直接内存区域。


参考: 
《深入理解Java虚拟机-JVM高级特性与最佳实践》第二版 周志明著
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值