《JVM》java 内存区域

Java 内存区域图

一、程序计数器

  • 线程私有
执行java方法时,记录的是虚拟机字节码指令地址。执行native方法时,记录值是undefined

此内存区域是JVM唯一一个没有OutOfMemoryError区域

二、虚拟机栈

  • 线程私有

用栈帧来存储Java方法所须数据(局部变量表、操作数栈、动态连接、方法出口等)编译时就已经完成分配,确定大小

1、局部变量表(Local Variables)

存储方法的形参,方法中定义的非静态变量,基本数据类型直接存储其值,引用类型则存储其引用地址。局部变量表在编译阶段就确定大小,在运行期间大小不会改变

2、操作数栈

程序中的所有计算过程都是在借助于操作数栈来完成的。

3、动态链接

指向当前方法所属的类的运行时常量池的引用。

4、方法出口

当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

三、本地方法栈

本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。

四、Java堆

  • 线程共享

Java垃圾收集器管理的主要区域,Java中的堆是用来存储对象本身的以及数组

五、方法区

  • 线程共享
存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是==常量池==,用来存储编译期间生成的字面量和符号引用。

1、class常量池(静态常量池)

主要存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)。

  • 字面量:例如文本字符串、fina修饰的常量。
  • 符号引用:例如类和接口的全限定名、字段的名称和描述符、方法的名称和描述符

2、运行时常量池

  • 方法区的一部分,当类加载到内存中后,JVM就会将class常量池中的内容存放
    运行时常量池中;运行时常量池里面存储的主要是编译期间生成的字面量、符号引用等等。
  • 类加载在链接环节的解析过程,会符号引用转换成直接引用(静态链接)。此处得到的直接引用也是放到运行时常量池中的。
  • 运行期间可以动态放入新的常量,例如String.intern()。

符号引用

符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用和虚拟机的布局无关。个人理解为:在编译的时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,多以就用符号引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。

直接引用

直接引用和虚拟机的布局是相关的,不同的虚拟机对于相同的符号引用所翻译出来的直接引用一般是不同的。如果有了直接引用,那么直接引用的目标一定被加载到了内存中。

详见:周志明《深入理解java虚拟机》里 7.3.4节的内容

3、字符串常量池

字符串常量池,也可以理解成运行时常量池分出来的一部分。类加载到内存的时候,字符串会存到字符串常量池里面。利用池的概念,避免大量频繁创建字符串。
graph LR
A[jdk1.6及之前]-->B[jdk1.7]
B-->C[jdk1.8]
  • Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池

  • Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里

  • Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里

六、直接内存(堆外内存)

直接内存(堆外内存)指的是Java应用程序通过直接方式从操作系统中申请内存。这个差别与之前的堆、栈、方法区,那些内存都是经过了虚拟化。所以严格来说,这里是指直接内存。

1、直接内存有哪些

  • 使用了 Java 的 Unsafe 类,做了一些本地内存的操作;
  • Netty 的直接内存(Direct Memory),底层会调用操作系统的 malloc 函数。
  • JNI 或者 JNA 程序,直接操纵了本地内存,比如一些加密库;
    JNI 是 Java Native Interface 的缩写,通过使用 Java 本地接口书写程序,可以确保代码在不同的平台上方便移植。
    JNA(Java Native Access )提供一组 Java 工具类用于在运行期间动态访问系统本地库(native library:如 Window 的 dll)而不需要编写任何 Native/JNI 代码。 开发人员只要在一个 java 接口中描述目标 native library 的函数与结构,JNA 将自动实现 Java 接口到 native function 的映射。 JNA 是建立在 JNI 技术基础之上的一个 Java 类库,它使您可以方便地使用 java 直接访问动态链接库中的函数。 原来使用 JNI,你必须手工用 C 写一个动态链接库,在 C 语言中映射 Java 的数据类型。 JNA 中,它提供了一个动态的 C 语言编写的转发器,可以自动实现 Java 和 C 的数据

2、优点

  1. 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作。
  2. 加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后再发送,而堆外内存相当于省略掉了这个工作。
  3. 可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现。
  4. 可以扩展至更大的内存空间。比如超过1TB甚至比主存还大的空间。

3、缺点

  1. 堆外内存难以控制,如果内存泄漏,那么很难排查
  2. 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象比较适合。

六、对象的创建

对象的创建一定程度上就是按需分配内存空间。

  • 指针碰撞(内存空间规整)Serial、ParNew、
  • 空闲列表(已使用内存空间和空闲内存相互交错)CMS
“指针碰撞”存在一个并发问题,在程序运行过程中,对象的创建是很频繁的,因此存在多线程并发问题。

解决方案:

  • CAS和失败重试的机制,保证更新的原子性
  • 本地线程分配缓冲TLAB(Thread Local Allocation Buffer),为每一个线程分配独立的内存空间,,当且仅当为线程分配新的内存空间时才需要同步锁去分配内存空间 -XX:+/-UseTLAB

七:对象的内存布局

在HotSpot虚拟机中,对象在内存中分成三部分组成,分别是对象头、实例数据、对其填充。

1、对象头

对象头可以分成两部分:Mark Word+类型指针+数组长度(只有对象是数组时)

Mark Word:哈希码、GC分代年龄、锁状态标识、偏向线程ID、偏向时间戳等
类型指针:对象指向类元数据的指针(如果是数组,还需记录数组长度。注:不是所有虚拟机实现都必须在对象头数据上保留类型指针)

Mark Word

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值