Java的内存区域

深入理解JVM–Java内存区域

Java虚拟机:
Sun Classic/Exact VM(第一款商用Java虚拟机)
Hotspot VM(目前使用范围最广泛的虚拟机)
Mobile/Embeded VM 主要使用于移动端和嵌入式市场…
Java运行时内存区域

在这里插入图片描述
线程私有:程序计数器、虚拟机栈、本地方法栈
程序计数器:程序流程控制的指示器(因为java虚拟机的多线程是通过多线程轮流切换、分配处理器执行时间执行的)。每个线程都有一个独立的程序计数器,用于保存各个线程的当前执行位置。
对于Java程序,程序技术器存放正在执行的虚拟机的字节码指令地址,对于本地方法,程序计数器应为空。
注意 :程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
虚拟机栈:生命周期和线程相同,存储局部变量表、操作数栈、动态连接、方法出口。每一个方法的调用都对应着一个栈帧在虚拟机中从入栈到出栈的过程。

  • 局部变量表 主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置),其所需要的内存空间在编译期间完成。
  • 操作数栈 主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。
  • 动态链接 主要服务一个方法需要调用其他方法的场景。Class 文件的常量池里保存有大量的符号引用比如方法引用的符号引用。当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用,这个过程也被称为 动态连接 。
  • 《JVM虚拟机规范》对虚拟机栈规定了两种异常,1)对线程请求深度大于虚拟机所允许的深度 抛出StackOverError异常 2)如果Java虚拟机栈容量可动态扩展则抛出OutOfMemoryError异常。
    本地方法栈:作用与虚拟机栈相似,虚拟机栈为Java的字节码程序服务,本地方法栈为Native方法服务。
    线程共享:堆、方法区、直接内存。
    Java堆:Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
    Java堆既可以被实现成固定大小的,也可以被时限为可扩展的(由-Xmx -Xms设定)
    HotSpot的堆结构
    在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:
    新生代内存(Young Generation)
    老生代(Old Generation)
    永久代(Permanent Generation)
    Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。
    JDK 8 版本之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存。

方法区:方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据,JDK8以前,更多人将方法去称呼为永久代,JDK8之后成为元空间,内存不足报OutofMemory异常。
为什么要将永久代改为元空间?

  • 1.整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
    2、元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了,而由系统的实际可用空间来控制,这样能加载的类就更多了。
    3、在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了

运行时常量区:Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的 常量池表(Constant Pool Table),内存不足报OutofMemory异常。
Java语言并不要求常量只能在编译期间才能产生,运行时同样可以,例如String的intern()方法。
String::intern()是一个本地方法,其作用是若果字符串常量池中含有相同的常量则直接返回该常量的引用,否则将String对象所包含的常量添加在常量池中,并且返回此String的引用。
直接内存 直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的,内存不足报OutofMemory异常。
在这里插入图片描述
JDK7将原本放在永久代的字符串常量池、静态变量等移至堆中,便于垃圾回收和管理。

对象的分配、布局和访问的全过程

对象的分配
1.类加载检查:遇到字节码New指令时,检查这个指令的参数是否能定位到一个类的符号引用,并检查是否被加载过,、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2. 分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

  • 指针碰撞(Bump the point) : 适用场合 :堆内存规整(即没有内存碎片)的情况下。====>标记-整理
    原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
    使用该分配方式的 GC 收集器:Serial, ParNew
  • 空闲列表 (Free List): 适用场合 : 堆内存不规整的情况下。 ===>标记-清除
    原理 :虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
    使用该分配方式的 GC 收集器:CMS

内存分配时的并发问题
CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
TLAB(本地线程分配缓冲): 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配,是否采用TLAB通过-XX:+/-UseTLAB参数设定。
3.初始化零值:这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4.设置对象头(Object Header)。例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。
5.执行方法,按照程序员意向进行初始化。

对象的内存布局

  • Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
  • 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
    对象的访问定位
    建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有:使用句柄、直接指针。
    句柄包含了对象实例数据与类型数据的各自具体的地址信息,指针存储的直接对象地址。
    这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值