栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

深入理解JVM --- JVM内存管理,Java对象创建、内存布局、访问定位

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深入理解JVM --- JVM内存管理,Java对象创建、内存布局、访问定位

文章目录

一、JVM内存管理

1.1 程序计数器1.2 虚拟机栈1.3 本地方法栈1.4 Java堆1.5 方法区1.6 运行时常量池1.7 直接内存 二、Java对象探秘

2.1 对象的创建2.2 对象内存布局2.3 对象的访问定位
复习一遍JVM,加深印象

一、JVM内存管理

如图,每条线程独有的区域为:虚拟机栈,本地方法栈,程序计数器

方法区和堆是线程共有的,方法区在JDK不同版本实现不同

1.6版本:方法区是堆的永生代来实现的,方法区中有一个笼统的常量池1.7版本:对方法区的常量池进行了细分,把字符串常量池 移到了堆中,分出的运行时常量池 仍在方法区内1.8版本:取消了永生代实现方法区的方式,用元空间来实现方法区 1.1 程序计数器

程序计数器是每个线程私有的

程序计数器的作用:

    程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。程序计数器能保证线程切换后能恢复到正确的执行位置。

程序计数器是JVM内存区域里唯一一个不会OOM的区域

1.2 虚拟机栈

虚拟机栈也是每个线程私有的

每个方法被执行的时候,虚拟机栈就会同步创建一个栈帧 用来存放局部变量表、操作数栈、动态连接、方法出口等信息,方法执行完毕就弹出这个栈帧,从调用方法到方法结束就对应一个栈帧的入栈和出栈

局部变量表:

局部变量表存放的是

Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)对象引用类型(reference类型,引用指针)returnAddress类型 指向下一个字节码指令地址

栈相关的内存错误:

SOF(StackOverflowError):线程请求的栈深度大于虚拟机所允许的深度

OOM(OutOfMemoryError):当栈容量允许动态拓展,在拓展时无法申请到足够的内存抛出OOM异常

注意:Hotspot虚拟机默认不允许动态拓展,所以HotSpot虚拟机上是不会由于虚拟机栈无法扩展而导致OutOfMemoryError异常——只要线程申请栈空间成功了就不会有OOM,但是如果申请时就失败,仍然是会出现OOM异常的

栈帧

1.3 本地方法栈

本地方法栈和虚拟机栈作用类似,区别是本地方法栈是为 Native方法 服务的,虚拟机栈是为虚拟机执行 Java方法(也就是字节码) 服务的

与虚拟机栈类似,本地方法栈也会栈深度溢出SOF,栈拓展失败OOM

1.4 Java堆

Java堆是虚拟机管理的内存中最大的一块,并且是所有线程共享的区域

堆的唯一目的就是存放对象实例 ,Java里几乎所有的对象实例都在这里分配内存(少数是即时编译技术的发展,逃逸分析技术中栈上分配、标量替换使得对象实例并不绝对是在堆上分配)

堆内存分区:

新生代

伊甸园区Eden:所有new出来的对象首先存放在此处幸存者区Survivor:0区,1区又被称为to区和from区,经过一次轻GC(Minor GC)后,将伊甸园区剩下的存入幸存者区。幸存者两个区会不断交换位置。 老年代 :假如经过很多次(默认15次)轻GC仍然没被清掉的会到老年区,若老年区满了,则会进行一次重GC(Major GC),又叫Full GC,将老年区的清理一遍,剩下的进入永久存储区永久代 :永久区是方法区的实现,在jdk8以后永久区取消,变成元空间(metaspace)。而元空间和永久区的区别是元空间不在虚拟机中,而是使用本地内存。

堆是可拓展的,可以通过通过参数-Xmx和-Xms设定

-Xmx:设置最大分配内存

-Xms:设置初始分配内存

如果堆中没有内存完成实例分配,并且堆也无法再扩展时,就会抛出OOM异常

1.5 方法区

方法区是线程共享的,在1.8之前方法区是用堆的永生代实现的,但有一个别名:非堆

方法区主要存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

1.6版本:方法区是堆的永生代来实现的,方法区中有一个笼统的常量池1.7版本:对方法区的常量池进行了细分,把字符串常量池 移到了堆中,分出的运行时常量池 仍在方法区内1.8版本:取消了永生代实现方法区的方式,用元空间来实现方法区

如果方法区无法满足新的内存分配需求时,将抛出OOM异常

1.6 运行时常量池

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

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table) 常量池表的内容将在类加载后存放到方法区的运行时常量池中

事实上,并非预先在Class文件常量池的内容才能进入运行时常量池,运行期间也能将新的常量放入池中。用的最多的就是String类的intern()方法

常量池受到方法区约束,当常量池无法再申请到内存时会抛出OOM异常

1.7 直接内存

Direct Memory直接内存并不是虚拟机规范中定义的内存区域,我的理解是计算机的物理内存。

例如:JDK1.4引入的NIO可以使用Native函数直接分配堆外内存,通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用来进行操作

一般服器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态拓展时出现OOM异常

二、Java对象探秘 2.1 对象的创建

当Java虚拟机遇到一条字节码new指令时,

    首先会检查常量池是否有这个类的引用 ,并检查这个引用代表的类是否被加载、解析和初始化过 。如果没有,则进行类加载过程。

    类加载检查通过之后,就是为新生对象分配内存

    内存分配完成之后还要进行初始化零值的操作

    接下来还要对对象进行必要的设置,例如是哪个类的实例,对象的GC分代信息等

    上面工作完成后对象创建才刚刚开始,构造函数还没执行,即Class文件中操作还没执行

    当new指令执行完成后,接着执行完方法初始化,这样一个真正的对象才算完全构造


内存分配的细节:对象分配空间的任务实际上等同于把一块确定大小的内存块从堆中划分出来

堆内存绝对规整 :

如果规整的话,分配内存仅仅就是把指针向空闲的方向挪动一段与对象大小相等的空间。这种方式称为指针碰撞法 堆内存并不是规整的 :

如果不规整,即已被使用的内存和空闲内存相互交错,那时候就没办法简单使用指针碰撞法了虚拟机此时必须维护一个列表记录哪块内存块可用,分配的时候将足够大的空间划分给对象实例。这种方式称为空闲列表法

对象在虚拟机中创建是非常频繁的,并发情况下并不是线程安全的 ,解决方案:

一种是采用CAS配上失败重试的方式保证更新操作的原子性另一种是把内存分配的动作按照线程划分在不同空间进行,即堆中预先分配一小块内存:本地线程分配缓冲TLAB 2.2 对象内存布局

对象堆内存布局可以划分为三个部分:

对象头(Mark Word):

对象头一部分存储hashcode、GC分代年龄、锁状态标志等信息;另一部分是类型指针,指向类型元数据

实例数据(Instance Data): 该部分对象是真正存储的有效信息

对齐填充(Padding): 为了保证对象是8字节的倍数,需要有自动补齐

2.3 对象的访问定位

分为两种方式:

使用句柄访问: 堆中划分出一块内存作为句柄池,reference变量指向句柄,通过句柄区访问对象地址

使用直接指针访问: 这种方式栈中reference变量存储的直接就是对象的地址

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/760391.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号