Java虚拟机——Java内存区域
1、运行时区域
Java虚拟机在执行Java程序的时候会把它管理的内厝划分为若干个不同功能的数据区域,如图所示

- 首先是程序计数器,程序计数器可以理解为当前程序执行的字节码的行号指示器,计数器中的数据即是下一条将要执行的字节码指令的行号。因为Java虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在任意一个时刻,一个处理器(单核)或是一个核(多核)都只会执行一个线程中的指令,所以,每个线程都拥有自己的程序计数器,还有就是如果执行的是一个Java方法,那么计数器中记录的就是字节码的地址,如果执行的是Native方法,那么计数器的值则是空(UnderFined)
- 虚拟机栈,虚拟机栈描述的是Java方法执行的内存模型,每次方法在执行的时候都会创建一个栈帧,栈帧中存储了局部变量表、操作数栈、动态链接、方法出口等等信息。每一个方法的从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈出栈的过程。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,如果栈可以动态扩展,且扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、float、long、double)、对象引用(reference类型,它可能是一个指向对象起始地址的指针,也可能是一个代表对象的句柄或是其他于此对象相关的位置)和returnAddressl类型(指向了一条字节码指令的地址)
- 本地方法栈和虚拟机栈的作用是类似的,区别就是本地方法栈是为Native方法服务的,同样也会抛出StackOverflowError异常和OutOfMemoryError异常。
- Java堆,Java堆首先是所有的线程共享的一块内存区域,这个区域的唯一目的就是用于存放对象实例,几乎所有的对象实例都在这里分配内存。如果堆中没有足够的内存完成实例分配,并且对也无法再扩展,将会抛出OutOfMemoryError异常。
- 方法区,这也是所有的线程共享的一块内存区域,它用于存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据,当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常。
- 运行时常量池,这是方法区的一部分,这部分专门用于存放编译期产生的各种字面量和符号引用,这部分将在类被加载后进入方法区的常量池中存放,和方法区一样无法满足内存分配需求时,将会抛出OutOfMemoryError异常。
- 直接内存,这部分并不是虚拟机运行时数据区的一部分,但也被频繁使用。这部分出现的是因为Native函数自己创建的,就比如NIO类读取文件的时候,会直接分配堆外内存,这一部分内存就是直接内存,直接内存区域动态扩展时出现内存无法满足时会抛出OutOfMemoryError异常。
2、对象的创建
A a = new A();
通常创建一个对象实例是使用的new关键字,当虚拟机遇到new指令的时候,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化过,如果有,那么将先执行响应的类加载过程。
类加载检查通过后,虚拟机将这个新生对象在堆中分配内存,所需要的内存大小将在类加载完成后确定,假设Java堆中的内存是规整的,也就是说空闲的在一边,非空闲的在一边,当分配内存的时候只需要指向中间边界的指针向空闲那边移动,这种方式称作指针碰撞,如果是非规整的,空闲的和非空闲的互相交错着,那么将有一个列表记录着那部分是空闲的,这样分配的时候就需要从这个列表中找到合适的空闲区域,并更新表中的记录,这种方式叫作空闲列表。
内存分配好之后,虚拟机将对分配的内存空间都初始化为零值,初始化完成后,虚拟机将对对象进行必要的设置,例如对象是属于那个类的实例、怎么才能找到类的元数据信息,对象的哈希码、对象的GC分代年龄。等设置结束后,虚拟机就已经创建好了一个新的对象,接下来在执行初始化方法init方法,这样一个完整的对象就算完全生成了。
3、对象的内存布局
对象在内存中存储的布局分为3块区域:对象头、实例数据和对齐补充
- 对象 头包括两部分,第一部分用于存储对象自身运行时的数据,例如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。另外如果对象是一个Java数组,那么在对象头数组中还必须有存放记录数组长度的数据。
- 实例数据部分是对象真正存储的有效信息,就是程序中定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来
- 对齐补充这一部分 不一定存在也没有什么特殊的含义,仅仅起到占位符的作用,因为有的虚拟机的自动内存管理系统要求对象的起始地址必须是8字节整数倍,因此需要对象的大小也必须是8字节的整数倍。
4、对象的访问定位
使用对象需要找到对象在堆中的地址,通常定位一个对象的地址主流的方式有使用句柄和直接指针两种
- 使用句柄,Java堆中会专门划分出一块内存来作为句柄池,栈上的reference数据中存储的就是句柄的地址,而句柄中包含了对象实例数据与类型数据各自的地址信息。
- 直接指针将直接指向Java堆中的Java实例数据的地址,Java堆中的实例数据也将有一个指向对象类型数据的指针。

这两种方法各有各的好处,句柄方式中reference中存储的句柄地址是一直不变的,当对象被移动的时候(在垃圾处理的时候对象被移动是很普遍的行为)时只会改变句柄中的实例数据指针。
而直接指针的好处就是速度更快,当你要操作一个对象的时候,句柄方式需要先定位到句柄在定位到对象实例,直接指节省了一次指针定位的开销。
Java虚拟机——Java内存区域的更多相关文章
- 从Java虚拟机的内存区域、垃圾收集器及内存分配原则谈Java的内存回收机制
一.引言: 在Java中我们只需要轻轻地new一下,就可以为实例化一个类,并分配对应的内存空间,而后似乎我们也可以不用去管它,Java自带垃圾回收器,到了对象死亡的时候垃圾回收器就会将死亡对象的内存回 ...
- 1 - JVM随笔分类(java虚拟机的内存区域分配(一个不断记录和推翻以及再记录的一个过程))
java虚拟机的内存区域分配 在JVM运行时,类加载器ClassLoader在加载到类的字节码后,交由jvm的执行引擎处理, 执行过程中需要空间来存储数据(类似于Cpu及主存),此时的这段空间的分 ...
- Java虚拟机之内存区域
原创文章,转载请标明出处! 目录 一.背景 二.运行时内存区域概述 1.官方描述 2.中文翻译 3.内存区域简述 4.运行时数据区简图 5.运行时数据区详图 三.JVM线程 JVM数据区域与线程关系 ...
- Java虚拟机各内存区域的位置及功能的介绍
Java虚拟机运行时数据区: 相关区域介绍: 程序计数器: 功能:当前线程所执行字节码的行号指示器.若是Java方法记录指令地址,若为Native方法,则不记录 隔离性:线程隔离 Error:无 Ja ...
- 深入理解Java虚拟机02--Java内存区域与内存溢出异常
一.概述 我们在进行 Java 开发的时候,很少关心 Java 的内存分配等等,因为这些活都让 JVM 给我们做了.不仅自动给我们分配内存,还有自动的回收无需再占用的内存空间,以腾出内存供其他人使用. ...
- Java虚拟机------JVM内存区域
JVM内存区域运行时数据区域分为两种: JVM内存区域 运行时数据区域分为两种: 线程隔离的数据区: 程序计数器 Java虚拟机栈 本地方法栈 所有线程程共享的数据区: Java堆 方法区 JVM 内 ...
- 深入Java虚拟机之内存区域与内存溢出
一.内存区域 Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域.Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器.Java虚拟机栈.本地方 ...
- 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码
程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...
- 深入理解java虚拟机【内存溢出实例】
通过简单的小例子程序,演示java虚拟机各部分内存溢出情况: (1).java堆溢出: Java堆用于存储实例对象,只要不断创建对象,并且保证GC Roots到对象之间有引用的可达,避免垃圾收集器回收 ...
- Java虚拟机:内存模型详解
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 我们都知道,当虚拟机执行Java代码的时候,首先要把字节码文件加载到内存,那么这些类的信息都存放在内存中的哪个区域呢?当我们创建一个对象实 ...
随机推荐
- Spring Boot 最流行的 16 条实践解读,你值得收藏!
Spring Boot是最流行的用于开发微服务的Java框架.在本文中,我将与你分享自2016年以来我在专业开发中使用Spring Boot所采用的最佳实践.这些内容是基于我的个人经验和一些熟知的Sp ...
- 【JavaScript】深入理解call,以及与apply、bind的区别
一.call call有两个妙用 1.继承(我前面的文章有提到用call实现call继承,有兴趣可以看下.https://www.cnblogs.com/pengshengguang/p/105476 ...
- Android安卓书籍推荐《Android驱动开发与移植实战详解》下载
百度云下载地址:点我 Android凭借其开源性.优异的用户体验和极为方便的开发方式,赢得了广大用户和开发者的青睐,目前已经发展成为市场占有率很高的智能手机操作系统. <Android驱动开发与 ...
- APP系统架构设计初探
一,图片体验的优化. 在手机上显示图片,速度是一个非常重要的体验点,试想,如果您打开一个网站,发现里面的图片一直显示失败或者是x,稍微做得好一点的,可能是一个不消失的loading或者是菊花等等,但不 ...
- sql server编写通用脚本实现获取一年前日期的方法
问题: 在数据库编程开发中,有时需要获取一年前的日期,以便以此为时间的分界点,查询其前后对应的数据量.例如:1. 想查询截止到一年前当天0点之前的数据量,以及一年前当天0点开始到现在的数据量.2. 想 ...
- GitHub使用整理——关于上传Keil工程一些注意的点
git上传警告warning: LF will be replaced by CRLF 在上传keil工程时,会遇到warning: LF will be replaced by CRLF警告: wa ...
- 从7点到9点写的小程序(用了模块导入,python终端颜色显示,用了点局部和全局可变和不可变作用域,模块全是自定义)
未完待续的小程序 要是能做的好看为啥不做的好看 在同目录下生成程序 1.程序文件 run.py from login import login from register import registe ...
- C#3.0新增功能02 匿名类型
连载目录 [已更新最新开发文章,点击查看详细] 匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型. 类型名由编译器生成,并且不能在源代码级使用. 每 ...
- [leetcode] #213 House Robber II Medium (medium)
原题链接 比子母题House Robber多了一个条件:偷了0以后,第n-1间房子不能偷. 转换思路为求偷盗[0,n-1)之间,以及[1,n)之间的最大值. 用两个DP,分别保存偷不偷第0间房的情况. ...
- LiteDB源码解析系列(3)索引原理详解
在这一章,我们将了解LiteDB里面几个基本数据结构包括索引结构和数据块结构,我也会试着说明前辈数据之巅在博客中遇到的问题,最后对比mysql进一步深入了解LiteDB的索引原理. 1.LiteDB的 ...