以上内容不是原创,如有侵权,请告知。
首先明白一点,JVM本质上也是一个进程运行在操作系统上。
C程序的存储空间布局
经典的存储空间布局正文段:这是存放CPU执行的机器指令。通常代码区是共享的,即其它执行程序可以调用它。
初始化数据段(数据段):存放已经初始化的全局变量、已经初始化的静态变量(包括全局和局部的)。
未初始化数据段:存放未初始化的全局变量和未初始化的静态变量。
栈:由编译器自动释放,存放函数的参数值、局部变量等。
堆:用于动态分配内存,位于BSS和栈中间的地址区域。
《UNIX环境高级编程》
Java虚拟机的内存结构
Java虚拟机的内存结构程序计数器
- 线程私有的
- 当前线程所执行的字节码的行号指示器
java虚拟机栈
- 线程私有的
- 每个方法执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等。
- 局部变量表包括基本数据类型、对象引用(存的是对象的地址)
本地方法栈
- native方法调用
java堆
- 所有线程共享的
- 存放实例对象
- 垃圾回收收集器的主要区域(GC堆)
- 新生代和老生代()
- 可以处于物理上不连续的内存空间
方法区
- 线程共享的
- 用于存储已被虚拟机加载的类信息、常量、静态常量、及时编译器编译后的代码等数据。
- 永生代
运行时常量池
- 方法区的一部分
- 用于存放编译期后生成的各种字面量的符号引用
来源:《深入理解Java虚拟机》
关系
关系上图特别强调了JVM进程模型的代码区和数据区指的是JVM自身的,而非Java程序的。普通进程栈区,在JVM一般仅仅用做线程栈。JVM的堆区和普通进程的差别是最大的,下面具体详细说明:
首先是永久代。永久代本质上是Java程序的代码区和数据区。Java程序中类(class),会被加载到整个区域的不同数据结构中去,包括常量池、域、方法数据、方法体、构造函数、以及类中的专用方法、实例初始化、接口初始化等。这个区域对于操作系统来说,是堆的一个部分;而对于Java程序来说,这是容纳程序本身及静态资源的空间,使得JVM能够解释执行Java程序。
其次是新生代和老年代。新生代和老年代才是Java程序真正使用的堆空间,主要用于内存对象的存储;但是其管理方式和普通进程有本质的区别。
普通进程在运行时给内存对象分配空间时,比如C++执行new操作时,会触发一次分配内存空间的系统调用,由操作系统的线程根据对象的大小分配好空间后返回;同时,程序释放对象时,比如C++执行delete操作时,也会触发一次系统调用,通知操作系统对象所占用的空间已经可以回收。
JVM对内存的使用和一般进程不同。JVM向操作系统申请一整段内存区域(具体大小可以在JVM参数调节)作为Java程序的堆(分为新生代和老年代);当Java程序申请内存空间,比如执行new操作,JVM将在这段空间中按所需大小分配给Java程序,并且Java程序不负责通知JVM何时可以释放这个对象的空间,垃圾对象内存空间的回收由JVM进行。
JVM的内存管理方式的优点是显而易见的,包括:第一,减少系统调用的次数,JVM在给Java程序分配内存空间时不需要操作系统干预,仅仅在Java堆大小变化时需要向操作系统申请内存或通知回收,而普通程序每次内存空间的分配回收都需要系统调用参与;第二,减少内存泄漏,普通程序没有(或者没有及时)通知操作系统内存空间的释放是内存泄漏的重要原因之一,而由JVM统一管理,可以避免程序员带来的内存泄漏问题。
最后是未使用区,未使用区是分配新内存空间的预备区域。对于普通进程来说,这个区域被可用于堆和栈空间的申请及释放,每次堆内存分配都会使用这个区域,因此大小变动频繁;对于JVM进程来说,调整堆大小及线程栈时会使用该区域,而堆大小一般较少调整,因此大小相对稳定。操作系统会动态调整这个区域的大小,并且这个区域通常并没有被分配实际的物理内存,只是允许进程在这个区域申请堆或栈空间。
以上内容不是原创,如有侵权,请告知。
网友评论