第二章——Java内存区域与内存溢出异常

2019-03-03  本文已影响0人  南麂

概述

对Java程序员来说,在虚拟机自动管理内存的情况下,不需要为每一个new出来的对象执行delete/free的操作,不容易出现内存泄漏和内存溢出问题。

运行时数据区域

Java虚拟机在执行java程序的过程中会把它管理的内存分成多个内存区域,每个内存区域的用途以及创建销毁时间都不同。

程序计数器

每个线程执行代码的时候都会有一个独立的程序计数器存储当前线程执行到了哪一行字节码。如果正在执行的是java方法时,存储的值为正在执行的虚拟机字节码指令的地址,如果是native方法时,这个计数器的值为空。jvm在执行多线程程序的时候,也是通过这个计数器去定位切换来的线程执行到了哪一行代码。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令

Java虚拟机栈

Java虚拟机栈也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型:每一个方法执行时,会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法执行完毕后出栈。
别人常说到的堆内存、栈内存,栈内存就是虚拟机栈,或者说是虚拟机栈的局部变量表部分。
局部变量表存储了编译期已知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,表示对象位置的数据)和returnAddress(指向了一条字节码指令的地址)。
在代码执行的过程中,如果线程请求的栈深度大于虚拟机栈,将抛出StackOverflowError异常。如果虚拟机栈可以动态拓展,但拓展的时候无法申请到足够的内存,将抛出OutOfMemoryError异常。
tips:对于方法值传递引用传递的理解:基本类型在压栈的时候会对数据进行拷贝,因此在方法内修改了基本数据类型的参数无法影响到外部。

本地方法栈

本地方法栈的功能与虚拟机栈类似,但本地方法栈为native方法服务。

Java堆

Java堆是虚拟机所管理的内存中最大的一块,它被所有的线程共享。该区的目的是存放对象的实例,几乎所有的对象实例都在这里分配内存,是虚拟机的垃圾回收器管理的主要区域。
Java堆的大小可以通过-Xmx和-Xms控制,如果堆中没有可用内存区分配新的实例了并且无法扩展了,那就会抛出一个OutOfMemoryError。

方法区

方法区也是被所有线程共享的内存区块,它主要存放类信息、常量、静态变量、即时编译器(JIT)编译后的代码等数据。虚拟机对方法区的限制非常宽松,可以选择不实现垃圾收集(个人理解:第一点是因为该区所占用的数据少,第二点是很难去控制哪个类型的定义或者静态变量不需要使用了。)与堆同理,当方法区无法满足内存分配的需求时,将抛出OutOfMemoryError异常。

运行时常量池

运行时常量池是方法区的一部分,它主要用于存放编译期间生成的各种字面量和符号引用。

直接内存

直接内存不属于虚拟机运行时区域,但是现在也经常被人用到。如NIO类就可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,减少了java堆和Native堆的来回复制,提高性能。

对象的创建

  1. 类检查:虚拟机遇到一条new指令时,首先会检查这个指令后的参数是否能在常量池中找到一个类的符号引用,并且检查这个符号代表的类是否已经被加载、解析和初始化过。如果没有,那必须先执行类的初始化过程。
  2. 分配内存:检查完毕后,虚拟机就要为新生的对象分配内存,对象所需的内存在类加载完毕之后就可以知道。为对象分配内存分有根据java堆内存是否规整分为两种方式:

Java堆是否规整由采用的垃圾收集器是否带有压缩整理功能决定。但堆内存分配是一个非常频繁的行为,会出现并发的情况,因此需要考虑使用同步的功能,虚拟机采用的是CAS配上重试的方法保证更新操作的原子性。

  1. 初始化内存空间:内存分配的完成后,虚拟机需要将分配的内存空间都初始化为零(不包括对象头)。接下来设置对象的对象头:对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
  2. 执行构造函数初始化实例:执行<init>方法

对象的内存布局

在HotSpot虚拟机中,对象在内存中可以分为3块区域:对象头、实例数据和对齐填充。

对象头
偏向锁、轻量级锁的状态转换以及对象Mark Word的关系.png 轻量级锁加锁流程.png
实例数据

实例数据无论是父类中定义的还是子类定义的内容都会记录起来。

对象的访问定位

现在虚拟机主流的对象数据访问方式有2种:

使用句柄

Java堆种会划出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据和类型数据各自的具体地址。

好处:reference中存储的是稳定的句柄地址,在对象被移动(垃圾回收导致对象移动)只改变句柄中的实例指针而reference本身不需要改变。

通过句柄访问对象.png
直接指针(Sun HotSpot使用)

reference存储的直接就是对象地址。

好处:速度快,节省了一次指针定位的时间开销。

通过直接指针访问对象.png
上一篇下一篇

猜你喜欢

热点阅读