JVM开山篇之内存区域

目录

  • 前言
  • Java虚拟机运行时数据区域
    • 程序计数器
    • Java虚拟机栈
    • 本地方法栈
    • Java堆
    • 方法区
        • 运行时常量池
    • 对象访问

前言

从事 Java开发3年了,没怎么看过java底层的一些东西,比如虚拟机之类的。趁着周末,正好来瞧瞧,各位看官也可以一起探讨探讨。

对于Java程序猿来说,在虚拟机的自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,而且不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是因为Java程序猿把内存控制的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误将会成为一项异常艰难的工作。

这次学习笔者主要介绍下Java虚拟机的内存区域,及区域的作用、服务对象和其中可能产生的问题,这将是程序猿翻越虚拟机这堵墙的第一步,可以说是非常重要了。

Java虚拟机运行时数据区域

话不多说,直接上图:
JVM开山篇之内存区域_第1张图片
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,上图就是对应的几个运行时数据区域。

程序计数器

首先来看程序计数器,它较小的的内存空间,它是当前线程所执行的字节码的行号指示器。

在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成

Java虚拟机的多线程,是通过线程轮流切换并分配处理器执行时间的方式来实现的。

在任何一个确定的时刻,一个处理器只会执行一条线程中的指令。

线程切换后若是要恢复到正确的执行位置,那么线程需要有个独立的程序计数器,而且每个线程都有自己独有的,并且互不影响。

它是线程私有的内存,此内存区域,是虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

它是线程私有的,生命周期和线程相同。每个方法被执行时都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。一个方法被调用至完成,对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

经常有人把Java内存分为栈和堆,这种方法比较粗糙,这里面所指的栈就是虚拟机栈,或者说是虚拟机栈的局部变量表,在方法运行时局部变量表大小不会改变 。局部变量表存放了编译期可知的各种基本数据类型、对象引用(reference类型)和returnAddress类型(指向一条字节码指令的地址)。

可扩展,通过-Xss参数设定。

Java虚拟机规范中,对该区域规定了两种异常情况:
1、线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常。
2、若虚拟机能动态扩展,并且当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

本地方法栈

它与Java虚拟机栈的作用非常相似,抛出的异常也一样。区别是:
虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地栈是为虚拟机的Native方法服务。

Java堆

堆是内存中最大的一块区域,被线程共享,在虚拟机启动是创建,唯一的目的就是存放对象实例,几乎所有的对象实例都在这分配。

堆是垃圾收集器管理的主要区域,也被称作“GC堆”。从内存回收的角度看,还能被分为新生代和老年代,再细一点的有Eden空间、From Survivor空间、To Survivor空间等。目前先不说更细的分类。

Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。可扩展,通过-Xmx和-Xms控制。若在堆中无内存分配实例对象,并且堆也无法扩展,将抛出OutOfMemoryError异常。

方法区

它也是线程共享的区域,用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。垃圾收集行为在这个区域是比较少出现的,这个区域的内存回收主要是针对常量池的回收和对类型的卸载,并且回收成绩比较难以令人满意。

当方法区无法满足内存的分配时,将抛出OutOfMemoryError异常。

运行时常量池

它是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等信息外,还有常量池,用于存放编译期生成的各种字面量和符号引用,这部分类容将在类加载后存放到方法区的运行时常量中。

它具备动态性,在运行期间也能将新的常量放入其中,程序猿用的比较多的就是String的intern()方法。同样。当运行时常量池无法满足内存的分配时,将抛出OutOfMemoryError异常。

对象访问

主流的访问方式有两种:使用句柄和直接指针。
1、句柄方式
Java堆中会划分一块内存作为句柄,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据和类型数据各自的具体地址。如图:

JVM开山篇之内存区域_第2张图片
2、指针访问对象
Java堆对象的布局中就必须考虑如何放置访问数据类型的相关信息,reference中直接存储的就是对象的地址。如图:
JVM开山篇之内存区域_第3张图片
句柄方式的最大好处:就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
指针访问的最大好处:速度更快,它节省了一次指针定位的时间开销,由于对象的访问非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。Sun HotSpot 使用的就是这种方式。

通过本次的学习,知道了虚拟机的内存是如何划分,哪部分区域会导致内存溢出。什么样的代码和操作能导致内存溢出,在这就不多说了,还是要在底下多实践实践就能更明白了,写代码还是要多实践。

你可能感兴趣的:(JVM)