一、java内存区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(JavaSE7版)》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域。
1.程序计数器(线程私有)
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时 就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
每条线程都有一个独立的程序计数器,各个线程之间互不影响,独立储存。
如果线程正在执行的是一个java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;
如果线程正在执行的是一个Native方法,计数器值为空(undefined)。
此内存区域是唯一一个在Java规范中没有规定任何OutOfMemoryError(OOM)情况的区域
2.Java虚拟机栈(线程私有)
虚拟机栈(Java Virtual Machine Stacks)描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。
通常大家所说的虚拟机堆内存和栈内存中,栈内存一般是指这里的局部变量表部分。
局部变量表存放了编译期可知的各种基本数据类型、对象引用(不是对象本身,可能是指向对象的指针或代表对象的句柄或其他的与此对象相关的位置,涉及到下文的“对象的访问定位”)和returnAddress类型。
3.本地方法栈(线程私有)
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,区别不过是虚拟机栈为Java方法(字节码)所服务,而本地方法栈则为虚拟机使用的Native方法服务。Java虚拟机规范没有对本地方法栈使用的语言、方式和数据结构有强制规定。因此虚拟机可以自由实现它,甚至有的虚拟机直接把本地方法栈和虚拟机栈合二为一。
4.Java堆(线程共享)
Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。唯一的目的就是存放对象实例。所有的对象实例和数组都要在堆上分配内存。
Java堆是垃圾收集器管理的主要区域。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,类似于磁盘空间。在实现时既可以是固定大小,也可以是可扩展的,当前的主流虚拟机都是按照可扩展实现的。如果在堆中没有内存完成实例化分配,并且堆也无法扩展时,会抛出OOM异常。
5.方法区(线程共享)
方法区(Method Area)用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
虚拟机规范把方法区描述为了堆的一个逻辑部分,但有个别名Non-Heap。
6.运行时常量池(线程共享)
运行时常量池(Runtime Constant Pool) 是方法区的一部分。
class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中保存。
Java虚拟机对Class文件中的每一部分的格式都有严格的规定。每一个字节用于存储哪种数据都必须符合规范才会被虚拟机认可、装载和执行,但是对于运行常量池,Java虚拟机规范没有做任何细节的要求,不同的虚拟机可以按照自己的需求进行实现。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定要只有编译的时候产生,运行期间也可能将新的常量放入池中,利用的多的便是String类的intern()方法。
在无法再申请到内存是会排除OOM异常。
二、HotSpot虚拟机对象探秘
HotSpot虚拟机在Java堆中对象分配、布局和访问过程的探讨。
1.对象的创建
Java是面向对象的语言,在程序的执行过程中随时都有可能会有对象被创建,从语言层面上讲,创建对象(例如克隆,反序列化)通常仅仅是new关键字。
过程:
加载类:当虚拟机遇到一个new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且通过检查这个符号引代表的类是否已经被加载、解析和初始化过。如果没有则执行相应的类加载过程。(此处不讨论这部分的具体内容,详情参考第七章)
分配内存:在类加载通过后,虚拟机要为新生对象分配内存,对象所需要的内存在类加载后就可以确定,为对象分配空间就是从Java堆中划分出来。此时常见有两种方法:(1)指针碰撞:若堆中内存是绝对规整的,已经使用的在一边,没有使用的在一边,中间有个分割指针,则只需要把分割指针向没有使用的内存移动所需要的固定大小(2)空闲列表:Java堆内存不是规整的,已使用的未使用的是相互交错的,虚拟机就要维护一个列表,列表保存哪些是已经使用的,哪些是未被使用的,分配的时候只需要从未被使用的内存中分配固定的大小给新对象。
选择哪种方法由堆内存是否规整所决定,而是否规整取决于采用的垃圾收集器是否带有压缩整理功能。
可以很明显的看出,这种分配空间的方法不是线程安全的,可能在对A对象分配时指针还没来得及修改,对象B就采用了原来的指针,解决这个问题有两种方案:
(1)对分配空间的动作进行同步化处理:实际上虚拟机采用CAS配上失败重试的方式保持更新操作的原子性(要么全部做完,要么一点也不做)。
(2)把内存分配的动作按照线程划分在不同的空间上进行,即每个线程在Java堆中预先分配一块内存,成为本地线程分配缓冲TLAB。哪个线程要分配内存就在那个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时才需要同步锁定。
内存空间初始化:内存分配结束后,虚拟机将分配的内存空间都初始化为零值(不包括对象头,分配的内存空间的具体内容下一部分会解释),如果使用TLAB来完成,这一工作可以提前到TLAB分配时进行,这一操作保证了对象的实例字段在Java中不赋初始值就可以直接使用,即零值。
对象头设置:接下来,虚拟机要对对象进行必要的设置,例如:是哪个类的实例?如何找到类的元数据信息?哈希码?对象的GC分代年龄等。这些信息存放在对象的对象头(Object Header)中。
初始化:从虚拟机视角来看,一个新的对象已经产生,但是从Java程序来看,这才刚刚开始,因为内存的数据内容都是零值,执行new之后,会执行<init>方法,按照程序员的要求来进行初始化。
2.对象的内存布局
分为三部分:
(1)对象头
①存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。32位虚拟机中为32bit,64位虚拟机中为64bit,官方称之为"Mark Word"。但是需要的存储的运行时数据太多,其实已经超过了32bit或者64bit,但是对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的效率Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态来复用自己的存储空间。例如在32位的HotSpot虚拟机中,如果对象处于未被锁的状态下,那么就不储存和锁有关的信息,25bit存储hashcode,4bit存储GC,2bit存储锁标志位、1bit固定为0。
②类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的示例。并不是所有的虚拟机实现都必须通过数据上保留类型指针(和对象的访问定定位有关,下面会进行解释)。如果对象是一个Java数组,那在对象头还必须有一块来记录数组长度的数据,因为从数组的元数据中无法确定数组的大小。
(2)实例数据
对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容,无论是父类继承下来的还是子类中定义的,都要记录下来。这部分的存储顺序会受到虚拟机分配策略参数(FieldAllocationStyle)和字段在Java源码中定义的顺序有关。HotSpot虚拟机默认的分配策略为: longs/doubles、ints、shorts/chars、bytes/booleans、oop(Ordinary Object Pointers),从策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个的前提下,在父类中定义的变量会出现在子类之前。如果CompactFields参数值为true(默认就是true),那么子类之中较窄的变量也可能插入到父类变量的空隙之中。
(3)对齐填充
不是必然存在的,也没有特殊的含义,仅仅是占位符的作用。由于HotSpot VM 的自动内存管理必须要求对象的起始地址必须是8字节的整数倍,换句话说就是对象的内存大小必须是8字节的整数倍,如果不足,则填充到8的整数倍。
3.对象的访问定位
即栈内存中的引用如何访问堆内存中的对象。目前主流的方式有两种:
(1)使用句柄:Java堆中会划分一块区域作为句柄池,栈中存储的就是对象的句柄地址,而句柄中包含了对象实例数据指针(指向堆内存中)和类型数据指针(指向方法区)。
(2)直接指针:栈中存储的直接就是对象地址,而堆中对象的布局要包括对象示例数据和类型数据指针(指向方法区)。
使用句柄的优点: 引用中存储的稳定的句柄地址,在对象移动(垃圾收集时移动对象是非常普遍的行为),只会改变句柄的实例数据指针,而引用本身不会改变。
直接指针的优点: 速度快,节省了一次指针定位的时间开销。由于对象访问十分频繁,极少成多之后也是可观的执行成本。HotSpot虚拟机是使用第二种进行对象访问的。
三、实战:OOM异常
原书阐述了产生几种异常的方式和简单代码,具体参考原书内容。
以上这篇浅谈Java内存区域与对象创建过程就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
# java对象创建过程
# 基于jvm java内存区域的介绍
# 学习JVM之java内存区域与异常
# Java内存区域和内存模型讲解
# Java内存区域与内存溢出异常详解
# JAVA jvm系列--java内存区域
# 句柄
# 的是
# 都有
# 加载
# 堆中
# 这部
# 自己的
# 池中
# 都是
# 有两种
# 这一
# 未被
# 有个
# 子类
# 是从
# 量表
# 数据结构
# 给大家
# 仅仅是
# 在对
相关文章:
如何用5美元大硬盘VPS安全高效搭建个人网站?
建站主机数据库如何配置才能提升网站性能?
济南网站建设制作公司,室内设计网站一般都有哪些功能?
浙江网站制作公司有哪些,浙江栢塑信息技术有限公司定制网站做的怎么样?
如何使用Golang安装API文档生成工具_快速生成接口文档
电商网站制作公司有哪些,1688网是什么意思?
个人摄影网站制作流程,摄影爱好者都去什么网站?
大同网页,大同瑞慈医院官网?
昆明网站制作哪家好,昆明公租房申请网上登录入口?
外贸公司网站制作哪家好,maersk船公司官网?
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
网站制作多少钱一个,建一个论坛网站大约需要多少钱?
如何通过云梦建站系统实现SEO快速优化?
湖北网站制作公司有哪些,湖北清能集团官网?
英语简历制作免费网站推荐,如何将简历翻译成英文?
怎么制作网站设计模板图片,有电商商品详情页面的免费模板素材网站推荐吗?
c++怎么编写动态链接库dll_c++ __declspec(dllexport)导出与调用【方法】
如何在Golang中引入测试模块_Golang测试包导入与使用实践
如何访问已购建站主机并解决登录问题?
广平建站公司哪家专业可靠?如何选择?
建站之星导航菜单设置与功能模块配置全攻略
如何制作网站标识牌,动态网站如何制作(教程)?
如何通过西部建站助手安装IIS服务器?
佛山企业网站制作公司有哪些,沟通100网上服务官网?
中山网站推广排名,中山信息港登录入口?
公司门户网站制作流程,华为官网怎么做?
临沂网站制作公司有哪些,临沂第四中学官网?
Thinkphp 中 distinct 的用法解析
网站插件制作软件免费下载,网页视频怎么下到本地插件?
建站之星如何助力网站排名飙升?揭秘高效技巧
淘宝制作网站有哪些,淘宝网官网主页?
宠物网站制作html代码,有没有专门介绍宠物如何养的网站啊?
网页设计网站制作软件,microsoft office哪个可以创建网页?
网站制作公司排行榜,抖音怎样做个人官方网站
网站制作的步骤包括,正确网址格式怎么写?
建站ABC备案流程中有哪些关键注意事项?
如何快速生成可下载的建站源码工具?
建站之星客服服务时间及联系方式如何?
实例解析angularjs的filter过滤器
如何配置IIS站点权限与局域网访问?
购物网站制作费用多少,开办网上购物网站,需要办理哪些手续?
如何用好域名打造高点击率的自主建站?
黑客如何通过漏洞一步步攻陷网站服务器?
如何在自有机房高效搭建专业网站?
台州网站建设制作公司,浙江手机无犯罪记录证明怎么开?
宁波免费建站如何选择可靠模板与平台?
电脑免费海报制作网站推荐,招聘海报哪个网站多?
如何获取上海专业网站定制建站电话?
网站网页制作电话怎么打,怎样安装和使用钉钉软件免费打电话?
大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?
*请认真填写需求信息,我们会在24小时内与您取得联系。