java内存模型缩写_Java内存模型详解

借用一句话:Java与C++之间有一堵内存动态分配和垃圾收集技术围成的高墙,墙外面的人想进来,墙里面的人却想出去。

一.我们为什么要了解JAVA内存

因为虚拟机帮我们JAVA程序员管理着内存,我们在new Object()申请了内存创建对象之后,便不需要再去delete/free来释放内存。也因此不容易出现内存泄漏和内存溢出的问题,看起来一切都很美好。

但是,如果一个程序员不了解虚拟机是怎么管理内存的,那么在排查内存相关的错误是便会成为一个巨大的难题。

二.内存区域有哪些

787ae65e17a7b8a3ea17fe5b5ca8e78d.png

内存区域分为两种,一种随着虚拟机的进程启动而存在。另一种则依赖用户进程的启动和结束而建立和销毁。

1.程序计数器

一块较小的线程私有的内存空间,可以看作是当前线程的所执行的字节码的行号指示器。

如果线程正在执行的是一个JAVA方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,那么计数器值为空(Undefined)。

该内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError(OOM)情况的区域。

2.虚拟机栈

线程私有的,每个Java方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

3.本地方法栈

线程私有,同虚拟机栈,为native方法服务。在HotSpot虚拟机中,直接把虚拟机栈和本地方法栈合二为一。

4.堆

线程共享的区域。存放实例的区域,几乎所有的对象实例都在这里分配内存。同时,因为空间固定,而用户可能需要不断生成实例,故该区域还是垃圾收集的主要区域。垃圾收集将在后面提到。

Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

5.方法区

线程共享的区域,存储已被虚拟机加载的类信息、常量、静态变量等数据。

很多人称之为“永生代”,因为HotSpot使用永生代来实现方法区。Java规范中对方法区的限制十分宽松,可以选择不实现垃圾收集。

6.运行时常量池

方法区的一部分,用于存放编译器生成的各种字面量和符号引用,在类加载完成后进入方法区的运行时常量池中存放。

关于这快区域,有一个需要注意的地方。代码如下:

48304ba5e6f9fe08f3fa1abda7d326ab.png

public class t18 {

public static void main(String[] args){

Integer a1 = 128 ;

Integer a2 = 128 ;

System.out.println(a1==a2);

Integer b1 = 127;

Integer b2 = 127;

Integer b3 = 1 + b1;

Integer b4 = a1 -1 ;

System.out.println(b1==b2);

System.out.println(b3==a1);

System.out.println(b4 == b1);

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

上面代码的运行结果为 false ,true ,false ,true 。这是很多人第一次见到时都无法理解的,因为这里涉及到了常量池的知识。JVM会把一些int,String等数据进行在常量池中缓存,但是重点在于,对于int型数据,只会缓存 -128~127 范围内的数据。因此:

a1、a2超过了127,在堆中分配内存,两者指向不同对象,返回false;

b1、b2都指向常量池中的127,故b1、b2指向地址相同,返回true;

第3、4个同理,Integer b3 = 1+b1  ----> Integer b3 =Integer.valueOf(1+b1)。

7.直接内存

JDK1.4后加入了NIO (new I/O)类,引入了基于通道与缓冲区的IO方式,可以使用native函数库分配机器内存,如电脑8g内存,JVM可以使用电脑的剩余内存,只需要在java堆中存储DirectByteBuffer对象作为内存的引用进行操作。这样在某些场景中提高性能。

三.在new一个对象时发生了什么

当虚拟机遇到一条new 指令时,首先回去检查能否在常量池中定位,并检查这个类是否已经被加载、解析、初始化过,如果没有,那么必须先执行类的加载过程。

类加载完成后,接下来将会为对象分配内存,即把一块确定大小的内存从java堆中划分出来。如果java堆是连续且规整的,已分配过的内存放在一边,空闲的在另一边。中间的指针作为分界点的指示器,那么分配内存就是将指针向空闲的方向移动所需要的距离,(使用Serial、PalNew等带规整过程的垃圾收集器);如果java堆是不规整的,那么虚拟机就必须维护一个记录,分配内存的同时需要更新记录,(如使用CMS这种基于标记-清除算法的收集器)。

将分配到的内存空间赋予初值,如整形变量置0,bool型置false。保证了对象字段在代码中可以不付初值就可以直接使用。然而在实际编写代码中,建议采用赋初值的形式,保持一个良好的代码习惯。另外

String s ;

System.out.println(s); //未初始化,编译器报错

该代码会报错,而不是输出null,切记切记。

初始化对象的对象头数据,每个java对象都有对象头(Object Header),里面记录了对象是哪个类的实例、如何找到类的元数据信息、哈希码、GC年龄、偏向锁等信息。

执行init方法,把对象按照程序员的一员进行初始化,这样,一个可用的对象才完成new操作。

四.一个对象在内存中有哪些部分

以HotSpot虚拟机为例,对象在内存中存储的区域可以分为三个部分

1.对象头(Object Header)

对象头包括两部分,一部分用于存储对象自身的运行时数据,官方称之为“Mark Word”,包括:HashCode、GC年龄、锁状态、线程持有锁、偏向锁线程id、偏向时间戳等。占一个字长(32bit或64bit,取决于虚拟机)。

另一部分是类型指针,对象指向的类元数据指针,通过这个来确定该对象是哪个类的实例。另外如果一个对象是一个数组,那么还有一块用于记录数组长度的数据。

2.对象数据

即实例中存储的,程序员设计的应该存储的数据。

3.对齐填充

不是必须的,仅仅起着占位的作用,HotSpot内存管理规定对象的起始地址必须是8字节的整数倍,换句话说对象的大小必须是9字节的整数倍,因此,当实例大小没有对齐时,需要通过对齐填充来补全。

五.如何访问定位对象

创建对象是为了使用对象,java虚拟机使用栈上的reference数据来操作堆上的具体对象,目前的访问方式主流有两种:

1.使用句柄访问

Java堆中会划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据各自的地址信息。

即访问时refenrence(存句柄地址) --> 句柄池(堆中,存对象地址) --> 具体对象(堆或方法区中)。

2.使用直接指针访问

直接访问,reference(存对象地址)-->具体对象(堆中或方法区中),一次跳转。HotSpot虚拟机使用的就是这种方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值