JVM内存结构划分实例解析

这篇文章主要介绍了JVM内存结构划分实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

数据区域划分

运行时内存区域划分:程序计数器、虚拟机栈、本地方法栈、堆、方法区

程序计数器

  • 线程私有
  • 通过寄存器实现
  • 不会存在运行溢出

当前线程所执行的行号指示器,记住下一条JVM指令的执行地址

虚拟机栈

  • 垃圾回收不涉及栈内存
  • 栈内存是线程私有的,可以理解为线程运行需要的内存空间
  • 栈由栈帧组成,每个栈帧代表一个方法执行时需要的内存(参数,局部变量,返回地址)
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

栈内存分配过大只能支撑一定的递归调用,并不会影响运行速度,还可能减少线程数量(因为物理内存是一定的)

本地方法栈

为运行本地方法时分配的内存(HotSpot把虚拟机栈和本地方法栈合二为一了)


  • 有垃圾回收机制
  • 线程共享,需要考虑线程安全问题
  • 存储的都是对象的实例(通过new关键字创建的对象)
  • 从内存分配的角度来说:堆中可以划分出多个线程私有的分配缓冲区(TLAB),以提升对象分配时的效率
  • Java堆可以处于物理上不连续的内存空间,但在逻辑上应该视为连续的(但是对于比如数组这种大对象,可能会要求连续的内存空间)

方法区

  • 线程共享区
  • 存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存
  • 在虚拟机启动时被创建,逻辑上属于堆的一部分(不同JVM实现的方式不同)
    • JDK1.6使用永久代(PerGen)作为方法区的实现
    • JDK1.8使用元空间(Metaspace)对方法区进行实现(包含Class ClassLoader 常量池三个部分,放在直接内存中)StringTable放在堆中(有助于垃圾回收管理)

使用场景:如Spring Mybatis使用的动态加载

运行时常量池

运行时常量池是方法区的一部分

二进制字节码内容:类基本信息\常量池表\类方法定义,包含了虚拟机指令

其中,常量池表中存放编译期间生成的各种字面量(比如各种基本数据类型)与符号引用(比如,类名\方法名\参数类型),这部分内容将在类加载后存放到方法区的运行时常量池中,并把符号地址变为真实地址

StringTable

类似于hashTable结构,不能自动扩容

常量池中的字符串只是符号,第一次使用时才变为对象

利用串池机制,避免重复创建字符对象

案例

  • 字符串拼接的原理是编译期优化
  • 字符串拼接原理是StringBuilder(JDK1.8)
  • 使用intern()方法,主动将串池中还没有的字符串对象放入串池
// StringTable [ "a", "b" ,"ab" ] hashtable 结构,不能扩容
public class Demo1_22 {
  // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
  // ldc #2 会把 a 符号变为 "a" 字符串对象
  // ldc #3 会把 b 符号变为 "b" 字符串对象
  // ldc #4 会把 ab 符号变为 "ab" 字符串对象

  public static void main(String[] args) {
    String s1 = "a"; // 懒惰的
    String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")
    String s5 = "a" + "b"; // javac 在编译期间的优化,结果已经在编译期确定为ab

    System.out.println(s3 == s5);
  }
}

JDK1.7以后,利用intern()方法,会将字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回;而JDK1.6调用intern()方法,是将对象拷贝一份到串池中,指向堆中的对象本身引用并不不改变

public class Demo1_23 {

  // ["ab", "a", "b"]
  public static void main(String[] args) {
    demo1();
    demo2();
  }

  static void demo1() {
    // 串池中事前没有"ab",intern()之后,s返回的是串池中的对象
    String s = new String("a") + new String("b");

    String s1 = s.intern();

    System.out.println(s == "ab"); // true
    System.out.println(s1 == "ab");   //true
  }

  static void demo2() {
    // 串池中事前已有"ab",s返回的仍是堆中的对象
    String x = "ab";
    String s = new String("a") + new String("b");

    // 堆 new String("a")  new String("b") new String("ab")
    String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回

    System.out.println( s2 == x);  // true
    System.out.println( s == x );  //false
  }
}

位置

JDK1.6时,StringTable放在元空间内,属于永久代的位置,但是StringTable占用内存容易触发full gc耗时较久;JDK1.7以后将StringTable放在堆内存中,随着内存占用增大首先触发minor gc,耗时较短.

直接内存

使用Native函数直接分配堆外内存,然后通过Java堆里的DirectByteBuffer对象作为引用对这块内存的引用进行操作.
原理说明:

使用Unsafe对象完成直接内存的分配和回收,回收时需要主动调用freeMemory方法

ByteBuffer的实现类内部使用了Cleaner(虚引用)来监测ByteBuffer(BB)对象,一旦BB对象被垃圾回收,会有ReferenceHandler线程通过Cleaner方法调用freeMemory来释放内存

创建新对象说明

HotSpot虚拟机在Java堆中对象分配、布局和访问的过程

对象的创建

new字节码指令

虚拟机遇到new字节码指令时,首先检查能否在常量池中定位到一个类的符号引用,并检查该符号引用的来是否已被加载、解析和初始化。如果没有,则执行相应的类加载过程。
类加载检查后,虚拟机为新生对象分配内存

内存分配

对象所需的内存大小在类加载过程中可以确定,在Java虚拟机中为对象划分内存时有两种方式:指针碰撞、空闲列表
指针碰撞: 利用一个指针作为已用内存和未用内存的分界点的指示器,内存分配就仅仅是指针的移动。优点在于不会造成内存碎片化,但是速度较慢

空闲列表:虚拟机维护一个内存使用记录表,使用时,从空闲的内存区域直接划分一块足够大的空间给对象实例。

内存分配的线程安全问题

划分可用空间后仍要考虑并发情况下对内存的使用,有两种方式解决内存冲突的问题:CAS配上失败重试、TLAB本地线程分配缓冲
TLAB:把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配了一小块内存空间

对象的内存布局

对象在堆内存中的布局可以划分为三个部分:对象头、实例数据、对齐填充

对象头

对象头中包含两类信息:Mark Word、类型指针

Mark Word

存储对象自身运行时数据,考虑到虚拟机的空间效率,被设计成一个动态定义的数据结构,即根据对象的状态复用自己的存储空间(数据长度在32位和64位虚拟机上分别为32个比特和64个比特)

类型指针

对象中指向它类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例(不是所有虚拟机都必须在对象数据上保留类型指针)此外,如果对象是一个数组,对象头中还必须拥有一块记录数据长度的数据

实例数据

即程序代码里定义的各种类型的字段内容,包括从父类继承的或子类中定义的字段。各类数据存储是按照一定顺序的(long/double、ints...),而宽度相同的字段总是被分配到一起存放,所以父类中定义的变量可能会出现在子类之前。

对齐填充

占位符,无特殊意义

HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整倍数,若有些对象的对象头和示例数据内存设计不是8的倍数,则需要利用占位符来进行填充。

对象的访问定位

Java程序通过reference数据操作对上的具体对象,主流的访问方式有两种:句柄、直接指针

句柄

Java堆中可能划分出一块内存作为句柄池。reference中存储对象的句柄地址,句柄中包含对象的实例数据和类型数据的具体地址信息。

直接指针

Java堆中对象的布局需要考虑如何放置类型数据的相关信息(如访问信息)。reference中存储的直接就是对象地址,如果只访问对象本身,就不要多一次间接访问的开销

优缺点

使用句柄访问, reference数据只需关乎句柄地址,当对象被回收或移动后只需改变句柄中的实例数据指针,而reference本身不用修改
使用直接指针省去了一次指针定位的时间开销,速度更快,由于Java中对象的访问相当频繁,所以效果可观。
HotSpot使用直接指针的方式

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 解析Linux系统中JVM内存2GB上限的详解

    我们通常使用的JVM都是32位的(64位的JVM会损失10-20%的性能,通常不建议使用),而32位程序的寻址空间应该是4GB才对,为什么Linux上的JVM内存只能使用2GB呢? 经过和JDK研发组的人员沟通,终于弄清楚了一些相关的原因.这个问题存在于早期的一些Linux版本中,特别是内核2.5以前的版本,2.6以后的版本就基本上没有这个问题了.原来这些Linux版本对进程有个对内存2GB的限制,是一个地址连续的内存块大小的上限,而JVM的堆空间(heap size)需要连续的地址空间,因此,

  • 详解JVM 运行时内存使用情况监控

    java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是由类加载器一步步解析, 执行与生成与内存区域中的; 并且jvm有自己的垃圾回收器对内存区域管理, 回收; 但是我们已经可以通过一些工具来在程序运行时查看对应的jvm内存使用情况, 帮助更好的分析与优化我们的代码; 注: 查看系统里java进程信息 // 查看当前机器上所有运行的java进程名称与pid(进程编号) jps -l // 显示指定的jvm进程所有的属性设置和配置参数 jinfo pid 1 . jmap : 内存占用情

  • JVM内存结构相关知识解析

    最近在看< JAVA并发编程实践 >这本书,里面涉及到了 Java 内存模型,通过 Java 内存模型顺理成章的来到的 JVM 内存结构,关于 JVM 内存结构的认知还停留在上大学那会的课堂上,一直没有系统的学习这一块的知识,所以这一次我把< 深入理解Java虚拟机JVM高级特性与最佳实践 >.< Java虚拟机规范 Java SE 8版 >这两本书中关于 JVM 内存结构的部分都看了一遍,算是对 JVM 内存结构有了新的认识.JVM 内存结构是指:Java 虚拟机定义

  • Java内存模型与JVM运行时数据区的区别详解

    首先,这两者是完全不同的概念,绝对不能混为一谈. 1.什么是Java内存模型? Java内存模型是Java语言在多线程并发情况下对于共享变量读写(实际是共享变量对应的内存操作)的规范,主要是为了解决多线程可见性.原子性的问题,解决共享变量的多线程操作冲突问题. 多线程编程的普遍问题是: 所见非所得 无法肉眼检测程序的准确性 不同的运行平台表现不同 错误很难复现 故JVM规范规定了Java虚拟机对多线程内存操作的一些规则,主要集中体现在volatile和synchronized这两个关键字. vo

  • JVM内存模型知识点总结

    内存模型如下图所示 堆 堆是Java虚拟机所管理的内存最大一块.堆是所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域唯一的目的就是存放对象实例.所有的对象实例都在这里分配内存 Java堆是垃圾收集器管理的主要区域.从内存回收的角度来看,由于现在的垃圾收集器采用的是分代收集算法.所以,java堆又分为新生代和老年代.从内存分配的角度来说,线程共享的java对中可能划分出多个线程私有的fenp缓冲区(Thread Local Allocation Buffer). 可以通过 -Xms.-X

  • jvm内存溢出解决方法(jvm内存溢出怎么解决)

    java.lang.OutOfMemoryError: PermGen space 发现很多人把问题归因于: spring,hibernate,tomcat,因为他们动态产生类,导致JVM中的permanent heap溢出 .然后解决方法众说纷纭,有人说升级 tomcat版本到最新甚至干脆不用tomcat.还有人怀疑spring的问题,在spring论坛上讨论很激烈,因为spring在AOP时使用CBLIB会动态产生很多类. 但问题是为什么这些王牌的开源会出现同一个问题呢,那么是不是更基础的原

  • 老生常谈JVM的内存溢出说明及参数调整

    对于JVM的内存写过的文章已经有点多了,而且有点烂了,不过说那么多大多数在解决OOM的情况,于此,本文就只阐述这个内容,携带一些分析和理解和部分扩展内容,也就是JVM宕机中的一些问题,OK,下面说下OOM的常见情况: 第一类内存溢出,也是大家认为最多,第一反应认为是的内存溢出,就是堆栈溢出: 那什么样的情况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了: java.lang.OutOfMemoryError: ......java heap space..... 也就是当你看到hea

  • 分别在Linux和Windows下设置JVM内存的简单方法

    Linux服务器: 在/usr/local/apache-tomcat-5.5.23/bin 目录下的catalina.sh 添加:JAVA_OPTS='-Xms512m -Xmx1024m' 或者 JAVA_OPTS="-server -Xms800m -Xmx800m -XX:MaxNewSize=256m" 或者 CATALINA_OPTS="-server -Xms256m -Xmx300m" Windows服务器: 在/apache-tomcat-5.5.

  • JVM内存结构划分实例解析

    这篇文章主要介绍了JVM内存结构划分实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 数据区域划分 运行时内存区域划分:程序计数器.虚拟机栈.本地方法栈.堆.方法区 程序计数器 线程私有 通过寄存器实现 不会存在运行溢出 当前线程所执行的行号指示器,记住下一条JVM指令的执行地址 虚拟机栈 垃圾回收不涉及栈内存 栈内存是线程私有的,可以理解为线程运行需要的内存空间 栈由栈帧组成,每个栈帧代表一个方法执行时需要的内存(参数,局部变量,返回地

  • 深入理解Java虚拟机 JVM 内存结构

    目录 前言 JVM是什么 JVM内存结构概览 运行时数据区 程序计数器 Java虚拟机栈 本地方法栈 方法区 运行时常量池 Java堆 直接内存 前言 JVM是Java中比较难理解和掌握的一部分,也是面试中被问的比较多的,掌握好JVM底层原理有助于我们在开发中写出效率更高的代码,可以让我们面对OutOfMemoryError时不再一脸懵逼,可以用掌握的JVM知识去查找分析问题.去进行JVM的调优.去让我们的应用程序可以支持更高的并发量等......总之一句话,学好JVM很重要! JVM是什么 J

  • JVM入门之JVM内存结构内容详解

    一.java代码编译执行过程 源码编译:通过Java源码编译器将Java代码编译成JVM字节码(.class文件) 类加载:通过ClassLoader及其子类来完成JVM的类加载 类执行:字节码被装入内存,进入JVM虚拟机,被解释器解释执行   注:Java平台由Java虚拟机和Java应用程序接口搭建,Java语言则是进入这个平台的通道,   用Java语言编写并编译的程序可以运行在这个平台上 二.JVM简介 1.java程序经过一次编译之后,将java代码编译为字节码也就是class文件,然

  • JVM内存区域划分相关原理详解

    学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内存又是如何划分的呢? 由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分.在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程: 如上图所示,首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加

  • C语言中结构体实例解析

    目录 一.结构体定义 二.实例演示 结构体作为函数参数 结构体指针 三.typedef struct 和 struct的区别 1.声明不同 2.访问成员变量不同 3.重新定义不同 总结 一.结构体定义 C语言结构体由一系列相同或者不同类型的数据构成的集合,结构体类型就是以struct关键字定义的数据类型. 结构体的格式如下: struct 结构名称 { 结构体所包含的数据成员,包括变量数组等 } 结构变量 ;//结构变量可以指定一个或多个 举例: struct Student { char na

  • JVM内存结构:程序计数器、虚拟机栈、本地方法栈

    目录 一.JVM 入门介绍 JVM 定义 JVM 优势 JVM JRE JDK的比较 学习步骤 二.内存结构 整体架构 1.程序计数器(寄存器) 1.1 作用 1.2 特点 2.虚拟机栈 2.1 定义 2.2 演示 2.3 面试问题辨析 2.4 内存溢出 2.5 线程运行诊断 3.本地方法栈 4.总结 一.JVM 入门介绍 JVM 定义 Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境) JVM 优势 一次编写,到处运行 自动内存管理,垃圾回收机制

  • C#枚举类型与结构类型实例解析

    本文以C#实例讲解了枚举类型与结构类型的用法,程序主要是通过个人电话本演示枚举类型与结构类型的用法,具体代码如下所示: using System; class ID { //定义枚举类型 public enum Sex { male, female };//注意别忘了这里的分号 //定义电话本的结构类型 public struct TelBook { public string name; public Sex sex;//性别类型为枚举类型 public string number; } //

  • 深入解析JVM之内存结构及字符串常量池(推荐)

    前言 Java作为一种平台无关性的语言,其主要依靠于Java虚拟机--JVM,我们写好的代码会被编译成class文件,再由JVM进行加载.解析.执行,而JVM有统一的规范,所以我们不需要像C++那样需要程序员自己关注平台,大大方便了我们的开发.另外,能够运行在JVM上的并只有Java,只要能够编译生成合乎规范的class文件的语言都是可以跑在JVM上的.而作为一名Java开发,JVM是我们必须要学习了解的基础,也是通向高级及更高层次的必修课:但JVM的体系非常庞大,且术语非常多,所以初学者对此非

  • 一篇文章带你了解JVM内存模型

    目录 1. JVM介绍 1.1 什么是JVM? 1.2 JVM的优点 1.2.1 一次编写,到处运行. 1.2.2 自动内存管理,垃圾回收机制. 1.2.3 数组下标越界检查 1.2.4 多态 1.3 JVM.JRE.JDK之间的关系 1.3.1 JVM的简介 1.3.2 JRE的简介 1.3.3 JDK的简介 1.4 JVM的常见实现 1.5 JVM的内存结构图 1.5.1方法区.堆 1.5.2虚拟机栈.程序计数器.本地方法栈 1.5.3执行引擎 1.5.4 GC(垃圾回收机制) 1.5.5本

随机推荐