JVM 虚拟机架构(图片来源: 浅析Java虚拟机结构与机制)


JVM 内存区域

JVM会将Java进程所管理的内存划分为若干不同的数据区域. 这些区域有各自的用途、创建/销毁时间:


(图片来源: JAVA的内存模型及结构)


一. 线程私有区域

线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束而创建/销毁(在Hotspot VM内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死).

1. Program Counter Register(程序计数器):

一块较小的内存空间, 作用是当前线程所执行字节码的行号指示器(类似于传统CPU模型中的PC), PC在每次指令执行后自增, 维护下一个将要执行指令的地址. 在JVM模型中, 字节码解释器就是通过改变PC值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖PC完成(仅限于Java方法, Native方法该计数器值为undefined). 
不同于OS以进程为单位调度, JVM中的并发是通过线程切换并分配时间片执行来实现的. 在任何一个时刻, 一个处理器内核只会执行一条线程中的指令. 因此, 为了线程切换后能恢复到正确的执行位置, 每条线程都需要有一个独立的程序计数器, 这类内存被称为“线程私有”内存.


2. Java Stack(虚拟机栈)

虚拟机栈描述的是Java方法执行的内存模型: 每个方法被执行时会创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息. 每个方法被调用至返回的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程(VM提供了-Xss来指定线程的最大栈空间, 该参数也直接决定了函数调用的最大深度).

  • 局部变量表(对应我们常说的‘堆栈’中的‘栈’)存放了编译期可知的各种基本数据类型(如boolean、int、double等) 、对象引用(reference : 不等同于对象本身, 可能是一个指向对象起始地址的指针, 也可能指向一个代表对象的句柄或其他与此对象相关的位置, 见下: HotSpot对象定位方式) 和 returnAddress类型(指向一条字节码指令的地址). 其中longdouble占用2个局部变量空间(Slot), 其余只占用1个. 如下Java方法代码可以使用javap命令或javassist等字节码工具读到:
public String test(int a, long b, float c, double d, Date date, List<String> list) {
StringBuilder sb = new StringBuilder().append(a).append(b).append(c).append(d).append(date); for (String str : list) {
sb.append(str);
} return sb.toString();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注: javap/javassist读到的其实是静态数据, 而局部变量表内存储的却是运行时动态加载的动态数据, 但因为局部变量表所需的内存空间在编译期间即可完成分配, 当进入一个方法时, 这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间大小不会改变, 因此可以在概念上认定这两部分内容存储的数据格式相同.


3. Native Method Stack(本地方法栈)

Java Stack作用类似, 区别是Java Stack为执行Java方法服务, 而本地方法栈则为Native方法服务, 如果一个VM实现使用C-linkage模型来支持Native调用, 那么该栈将会是一个C栈(详见: JVM学习笔记-本地方法栈(Native Method Stacks)), 但HotSpot VM直接就把本地方法栈和虚拟机栈合二为一.


二. 线程共享区域

随虚拟机的启动/关闭而创建/销毁.


1. Heap(Java堆)

几乎所有对象实例和数组都要在堆上分配(栈上分配、标量替换除外), 因此是VM管理的最大一块内存, 也是垃圾收集器的主要活动区域. 由于现代VM采用分代收集算法, 因此Java堆从GC的角度还可以细分为: 新生代(Eden区From Survivor区To Survivor区)和老年代; 而从内存分配的角度来看, 线程共享的Java堆还还可以划分出多个线程私有的分配缓冲区(TLAB). 而进一步划分的目的是为了更好地回收内存和更快地分配内存.


2. Method Area(方法区)

即我们常说的永久代(Permanent Generation), 用于存储被JVM加载的类信息常量静态变量即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收类型的卸载, 因此收益一般很小)

不过在1.7的HotSpot已经将原本放在永久代的字符串常量池移出: 

而在1.8中, 永久区已经被彻底移除, 取而代之的是元数据区Metaspace(这一点在查看GC日志和使用jstat -gcutil查看GC情况时可以观察到),与永久代不同, 如果不指定Metaspace大小, 如果方法区持续增长, VM会默认耗尽所有系统内存.

  • 运行时常量池 
    方法区的一部分. Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项常量池(Constant Pool Table)用于存放编译期生成的各种字面量和符号引用, 这部分内容会存放到方法区的运行时常量池中(如前面从test方法中读到的signature信息). 但Java语言并不要求常量一定只能在编译期产生, 即并非预置入Class文件中常量池的内容才能进入方法区运行时常量池, 运行期间也可能将新的常量放入池中, 如Stringintern()方法.

三. 直接内存

直接内存并不是JVM运行时数据区的一部分, 但也会被频繁的使用: 在JDK 1.4引入的NIO提供了基于Channel与Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存, 然后使用DirectByteBuffer对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在Java堆和Native堆中来回复制数据, 因此在一些场景中可以显著提高性能. 
显然, 本机直接内存的分配不会受到Java堆大小的限制(即不会遵守-Xms、-Xmx等设置), 但既然是内存, 则肯定还是会受到本机总内存大小及处理器寻址空间的限制, 因此动态扩展时也会出现OutOfMemoryError异常.

JVM内存管理基础的更多相关文章

  1. JVM内存管理基础概念

    .内存的不同形态 物理内存 虚拟内存 .内存的使用形式 内核空间 用户空间 .java虚拟机运行时数据划分 PC寄存器:保存当前程序运行时的内存地址. Java栈:总是和线程关联,每个线程拥有一个ja ...

  2. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  3. JVM内存管理及垃圾回收

    一.JVM内存的构 Java虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法.总体分为下面几个部分: 程序计数器(Progra ...

  4. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  5. java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

    概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...

  6. JVM内存管理及垃圾回收【转】

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  7. 你应该这样理解JVM内存管理

    在进行Java程序设计时,一般不涉及内存的分配和内存回收的相关代码,此处引用一句话: Java和C++之间存在一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外的人想进去,墙里面的人想出来 ,个人从这 ...

  8. JVM内存管理及GC机制

    一.概述 Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露 ...

  9. java jvm内存管理/gc策略/参数设置

    1. JVM内存管理:深入垃圾收集器与内存分配策略 http://www.iteye.com/topic/802638 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想 ...

随机推荐

  1. RAC2——11g Grid Infrastructure的新机制

    版权声明:本文为博主原创文章,未经博主允许不得转载. 可以看到,最初CRS(Cluster Ready Services)名词的起源就是因为10.1中作为集群软件的原因.后来经历了Clusterwar ...

  2. Nginx隐藏主机信息,proxy_hide_header 与fastcgi_hide_header

    Nginx中proxy_hide_header 与fastcgi_hide_header都可以隐藏主机头信息,两者在具体使用时还是有着一定的区别的.刚好业务使用的nginx反向代理在显示响应头时将后端 ...

  3. 基于DRL和TORCS的自动驾驶仿真系统——之环境配置

    基于DRL和TORCS的自动驾驶仿真系统 --之环境配置 玩TORCS和DRL差不多有一整年了,开始的摸爬滚打都是不断碰壁过来的,近来在参与CMU的DRL10703课程学习和翻译志愿者工作,也将自己以 ...

  4. FineUI Grid中WindowField根据列数据决定是否Enalble

    前台页面Grid控件中设置OnPreRowDataBound属性,windowFile控件设置ID protected void Grid1_PreRowDataBound(object sender ...

  5. 打开Visual Studio 2012的解决方案 连接 Dynamics CRM 2011 的Connect to Dynamics CRM Server 在其工具下没有显示

    一.使用TFS 代码管理,发现Visual Studio 2012 菜单栏 工具下的Connect to Dynamics CRM Server 没有显示. 平常打开VS下的工具都会出现Connect ...

  6. java编码-多重(乱码)

    一.1,UTF编码 - 2,ISO解码 - 3,UTF编码 - 4,ISO解码 String ISO = "ISO-8859-1"; String UTF = "UTF- ...

  7. linux 守护进程(daemon process)代码-详细注释

    1. 进程组 组长不能创建新的 会话. 其它进程可以创建新的会话,创建后既成为会话首领,同时失去控制终端. 2. 会话首领可以重新打开控制终端 1 #include <stdio.h> 2 ...

  8. ios获取安装的app

    http://www.iphonedevsdk.com/forum/iphone-sdk-development/22289-possible-retrieve-these-information.h ...

  9. linux 搭建 redis

    找了很多教程总有一个适合我 http://www.cnblogs.com/_popc/p/3684835.html 当然我的目录是/opt/redis   文章中的目录是/usr/local/redi ...

  10. 字符串strip相关函数

    s.strip(rm) 删除s字符串中开头.结尾处,位于 rm删除序列的所有字符,但只要遇到非rm序列中的字符就停止s.lstrip(rm) 删除s字符串中开头处,位于 rm删除序列的所有字符,,但只 ...