1. 概述

  对于从事c和c++程序开发的开发人员来说,在内存管理领域,他们既拥有最高权力的”皇帝“又是从事最基础工作的”劳动人民“---既拥有每个对象的”所有权“,又担负着每个对象开始到终结的维护责任。java把内存控制的权利交给了java虚拟机,一旦出现内存泄露和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查将会是一项异常艰难的工作。

2. 运行时数据区域

  

  

  (1) 程序计数器

    程序计数器是一块较小的内存空间,他可以看作是当前线程所执行的字节码的行号指示器。由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器不影响,独立存储,我们称这类内存区域为”线程私有“的内存。

    如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

  (2) java虚拟机栈

    与程序计数器一样,java虚拟机栈也是线程私有的,每个线程创建的同时都会创建VM栈,他的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧用于存储局部变量表(八种基本类型)、操作数帧、指向当前方法所属类的运行时常量池的引用、动态链接、返回结果等信息,非基本数据类型的对象在JVM栈上仅存放一个指向堆上的地址。

    当线程执行一个方法时,就会随之创建一个对应的栈帧,压入栈。方法执行完成后,就会将方法出栈。线程当前执行的方法所对应的栈帧位于VM栈的顶部。

    局部变量表存放了编译期可知的各种基本数据类型、对象引用。局部变量表所需的内存空间在编译期完全分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

    对于这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

  (3) 本地方法栈

    虚拟机栈为虚拟机执行java方法(即字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError异常和OutOfMemoryError异常。

  (4) java堆

    java堆是java虚拟机所管理的内存中最大的一块,存储对象实例以及数组值的区域。java堆是被所有线程共享的一块内存区域,因此在其上进行对象内存的分配均需要进行加锁。java堆是垃圾收集器管理的主要区域。java堆细分为:新生代 和老年代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由Froom Space和To Space组成。java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

  

  特点:

    a. java堆是被所有线程共享的一块内存区域,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销,是比较大的。

    b. 新生代:新建的对象都是用新生代分配内存的。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivior区,当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的并且还存活的对象,将被复制到老年代。

    c. 老年代:在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。

    d. 永久代:实现方法区,主要存放所有已加载的类信息,方法信息,常量池等。永久代堆垃圾回收没有显著影响。

  (5) 方法区

·    方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、final类型的常量、静态变量、属性、方法即时编译器编译后的代码等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

  (6) 运行时常量池

    运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池。当常量池无法再申请到内存时就会抛出OutOfMemoryError异常。

3. 对象的创建

  虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。之后虚拟机将为新生对象分配内存。对象所需内存的大小在来加载完成后便可以确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。

  对象在内存存储的布局可以分为3块区域:对象头、实例数据和对齐填充。

    (1) 对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希吗、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    (2) 对象实例是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。

    (3) 对齐填充:仅仅起到占位符的作用

  对象的访问定位:java程序需要通过栈上的referrnce数据来操作堆上的具体对象。主流的访问方式有使用句柄和直接指针两种。

    (1) 使用句柄访问的话,那么java堆中将会划出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与数据类型各自的具体地址信息。

    (2) 使用直接指针访问,那么java堆对象的布局中就必须考虑如何放置访问类型的相关信息,而reference中存储的直接就是对象的地址。

  

    

    

JVM内存布局的更多相关文章

  1. JVM(二)JVM内存布局

    这几天我再次阅读了<深入理解Java虚拟机>之第二章"Java内存区域与内存溢出异常",同时也参考了一些网上的资料,现在把自己的一些认识和体会记录一下.  (本文为博主 ...

  2. JVM内存布局及GC知识回顾

    注:本文篇幅较长,且需要有一定的java基础,建议各位看官,备好瓜子.饮料.小板凳,摆个让自己舒服的姿势,慢慢细看^_^, 文中所有素材,均来自互联网,本人只是详细梳理了一遍,形成此文. 一.JVM运 ...

  3. [转帖]详解JVM内存布局及GC原理,值得收藏

    概述 https://www.toutiao.com/i6731345429574713868/ java发展历史上出现过很多垃圾回收器,各有各的适应场景,不仅仅是开发,作为运维也需要对这方面有一定的 ...

  4. JVM内存布局及GC知识

    一.JVM运行时内存布局 按java 8虚拟机规范的原始表达:(jvm)Run-Time Data Areas, 暂时翻译为"jvm运行时内存布局". 从概念上大致分为6个(逻辑) ...

  5. 深入理解Java虚拟机之JVM内存布局篇

    内存布局**** ​ JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的稳定高效运行.不同的JVM对于内存的划分方式和管理机制存在部分差异.结合JVM虚拟机规范,一起来 ...

  6. JVM 内存布局

    JVM 内存布局规定了 Java 在运行过程中内存申请.分配.管理的策略,保证了 JVM 的高效稳定运行. 线程是否共享 Heap (堆区) 堆是 OOM 故障最主要的发生区域.它是内存区域中最大的一 ...

  7. JVM内存布局(又叫Java运行时数据区)

    JVM 堆中的数据是共享的,是占用内存最大的一块区域. 可以执行字节码的模块叫作执行引擎. 执行引擎在线程切换时怎么恢复?依靠的就是程序计数器. JVM 的内存划分与多线程是息息相关的.像我们程序中运 ...

  8. JVM内存问题分析

    JVM运行时数据区: 1.方法区:类信息(类名,访问修饰符.字段描述.方法 描述等).常量.静态变量.即时编译后的class文件等.在GC时用永久代来实现方法区 2.运行时常量池:是方法区的一部分,存 ...

  9. 2万字长文包教包会 JVM 内存结构 保姆级学习笔记

    写这篇的主要原因呢,就是为了能在简历上写个"熟悉JVM底层结构",另一个原因就是能让读我文章的大家也写上这句话,真是个助人为乐的帅小伙....嗯,不单单只是面向面试学习哈,更重要的 ...

随机推荐

  1. [开源]开放域实体抽取泛用工具 NetCore2.1

    开放域实体抽取泛用工具 https://github.com/magicdict/FDDC 更新时间 2018年7月16日 By 带着兔子去旅行 开发这个工具的起源是天池大数据竞赛,FDDC2018金 ...

  2. Docker Mongo数据库开启用户认证

    一.启动mongo容器的几种方式 #简化版 docker run --name mongo1 -p 21117:27017 -d mongo --noprealloc --smallfiles #自定 ...

  3. 生成命令行接口--google开源的fire使用体验【python-fire】

    在python中,命令行接口常用的argparse 和click,但是相对于python-fire 来说灵活度太缺了,fire可以直接将python中的函数,以命令行显示. 简单的介绍几个例子: #! ...

  4. Mathematica查看内部定义

    << GeneralUtilities`; PrintDefinitions[IntegerReverse]

  5. 【原创 Hadoop&Spark 动手实践 6】Spark 编程实例与案例演示

     [原创 Hadoop&Spark 动手实践 6]Spark 编程实例与案例演示 Spark 编程实例和简易电影分析系统的编写 目标: 1. 掌握理论:了解Spark编程的理论基础 2. 搭建 ...

  6. 【Spark深入学习 -16】官网学习SparkSQL

    ----本节内容-------1.概览        1.1 Spark SQL        1.2 DatSets和DataFrame2.动手干活        2.1 契入点:SparkSess ...

  7. python(62):保留两位小数

    转载:https://blog.csdn.net/jiandanjinxin/article/details/77752297 在C/C++语言对于整形数执行除法会进行地板除(舍去小数部分). 例如 ...

  8. 异类查询要求为连接设置ANSI_NULLS和ANSI_WARNINGS选项

    在查询分析器中,先输入两句    set   ansi_nulls   on    set   ansi_warnings   on    执行然后再    Create   Proc   存储过程  ...

  9. 最简单的设计模式——单例模式的演进和推荐写法(Java 版)

    前言 如下是之前总结的 C++ 版的:软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的. 概念不多说,没意思,我自己总结就是: ...

  10. C++ 非常量引用无效

    /* 非常量引用无效 */ #include <iostream> using namespace std; /* C++标准的规定:非常量的引用不能指向临时对象: 为了防止给常量或临时变 ...