运行时数据区域

其中右侧三个一起的部分是每个线程一份,左侧两个是所有线程共享的。

程序计数器(Program Counter Register)

英文名称叫Program Counter Register。如果翻译为程序寄存器更加合理。

因为这块内存区域很小,功能也类似于寄存器。所以还是翻译寄存器比较靠谱。

在虚拟机的概念模型中(仅仅是概念模型,虚拟机可以通过一些更高效的方式来实现),字节码解释器会通过改变程序计数器的值来选取下一条需要执行的字节码指令。

因为CPU是要轮转的,在切换回来之后,Java能够找到下一条要执行的指令。

每一个线程会有一个独立的程序计数器。

线程执行Nativan方法时,计数器记录为空(Undefined)

唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域

线程私有,生命周期跟线程一样。

Java虚拟机栈(Java Virtual Machine Stacks)

线程私有,生命周期跟线程一样。

用于描述Java方法执行的内存模型:

一个栈帧代表一个方法的执行内存模型:入栈和出栈分别代表方法被调用和方法执行完成。

一个栈帧包含:局部变量,操作数栈,动态链接,方法出口等。

重点说局部变量:存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(Object reference)和字节码指令地址(returnAddress类型)。

其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。

因为所有类型占用的内存大小都是可知的,所以局部变量的内存空间是在编译器完成分配的,运行期不会改变局部变量表的大小。

如果线程请求的栈深度(个人理解:方法调用的深度)大于虚拟机所允许的深度,将抛出StackOverflowError异常;

如果虚拟机栈可以动态扩展(个人理解:虽然局部变量表的空间在编译器就固定了,但是其他几个可能会动态扩展),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

本地方法栈(Native Method Stacks)

本地方法栈(Native Method Stacks)与Java虚拟机栈所发挥的作用非常类似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

同样可能会抛出StackOverflowError和OutOfMemoryError异常。

Java堆(Java Heap)

Java堆(java heap)是Java虚拟机所管理的内存中最大的一块

它是被所有线程共享的一块内存区域,在虚拟机启动时创建

几乎所有的对象实例都存放在这里。之所以说几乎,是因为:在java虚拟机规范中描述:所有的对象实例都要在堆上分配。但是由于JIT编译器的发展和逃逸分析技术的逐渐成熟,栈上分配,标量替换优化技术将会导致"所有的对象实例都要在堆上分配"不那么绝对了。

Java堆是垃圾收集管理的主要战场。根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的 磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。(通过-Xmx和-Xms控制)

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区(Method Area)

方法区存储的是类的一些基本信息,如类的常量池、静态变量,即时编译器编译后的代码等。【标准说法是:类及其父类的全限定名(java.lang.Object没有父类)、类的类型(Class or Interface)、访问修饰符(public, abstract, final)、实现的接口的全限定名的列表、常量池、字段信息、方法信息、静态变量、ClassLoader引用、Class引用】

可以这样理解,如果定义了一个类,其中有一个静态变量,那么在new这个类的时候,静态变量会放在方法区,实例的其它部分会放在Java堆;如果又new了这个类,那么方法区的静态变量不变,在Java堆里面会有一块新的内存放实例。

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

运行时常量池(Runtime Constant Pool)

运行时常量池(Runtime Constant Pool),它是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用(如编译器生成的static final常量字面量,方法表中的方法名索引 描述符索引 符号引用等),这部分内容将在类加载后存放到常量池中。

运行时常量是相对于常量来说的,它具备一个重要特征是:动态性。当然,值相同的动态常量与我们通常说的常量只是来源不同,但是都是储存在池内同一块内存区域。Java语言并不要求常量一定只能在编译期产生,运行期间也可能产生新的常量,这些常量被放在运行时常量池中。这里所说的常量包括:基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和String(也可以通过String.intern()方法可以强制将String放入常量池)

我的理解:运行时动态扩展常量池可以把一些不经常变化的数据缓存起来,和String的缓存池一样,不知道String的缓存是不是存放在这里。

直接内存(Direct Memory)

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分。

在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括RAM及SWAP区或者分页文件)的大小及处理器寻址空间的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

对象访问

对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。

如下面这句代码:

Object objectRef = new Object();

假设这句代码出现在方法体中,"Object objectRef" 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。而"new Object()"这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。

reference类型在java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。

句柄访问方式:java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

指针访问方式:reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。

这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。

java堆溢出

实验:

实验代码:

  1. public class OutOfMemoryTest {
  2.  
  3.    static class OOMObject{
  4.  
  5.    }
  6.    public static void main(String[] args) {
  7.       // TODO Auto-generated method stub
  8.       List list = new ArrayList();
  9.       while(true)
  10.       {
  11.          list.add(new OOMObject());
  12.       }
  13.    }
  14.  
  15. }

设置JVM参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

错误信息:

java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid568.hprof ...

Heap dump file created [27885730 bytes in 0.090 secs]

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at java.util.Arrays.copyOf(Unknown Source)

at java.util.Arrays.copyOf(Unknown Source)

at java.util.ArrayList.grow(Unknown Source)

at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)

at java.util.ArrayList.ensureCapacityInternal(Unknown Source)

at java.util.ArrayList.add(Unknown Source)

at jvm.OutOfMemoryTest.main(OutOfMemoryTest.java:16)

使用MemoryAnalyzer查看dump:

OOM(OutOfMemory)是最长江的内存溢出。

java.lang.OutOfMemoryError: Java heap space代表堆内存溢出。

这种情况,一般要确认是内存泄漏(Memory Leak)还是内存溢出(Memory OverFlow)。

内存泄漏:对象的使用导致没有被GC。

内存溢出:内存不足。需要调整JVM参数。

虚拟机栈和本地方法溢出

在Hotspot中不区分虚拟机栈和本地方法栈,所以调整他们两个的参数都是-Xss。

经过我的实验,我发现,当由于方法嵌套循环调用导致的溢出,只会跟-Xss的配置有关,没有线程请求的栈深度(个人理解:方法调用的深度)大于虚拟机所允许的深度这种说法。至少我将-Xss调整到100M也没有发现。

测试代码:

  1. package jvm;
  2.  
  3. public class StackOverFlowTest {
  4.  
  5.    private long stackLenth = 0;
  6.    public static void main(String[] args) {
  7.       // TODO Auto-generated method stub
  8.       StackOverFlowTest s = new StackOverFlowTest();
  9.       try {
  10.          s.testStack();
  11.       } catch (Throwable e) {
  12.          System.out.println(s.stackLenth);
  13.          e.printStackTrace();
  14.       }
  15.  
  16.    }
  17.    private void testStack()
  18.    {
  19.       long l = 999999999999999L;
  20.       stackLenth ++;
  21.       testStack();
  22.    }
  23.  
  24. }

在10M的时候,报错信息:

java.lang.StackOverflowError

at jvm.StackOverFlowTest.testStack(StackOverFlowTest.java:21)

在100M的时候,报错信息:

java.lang.StackOverflowError

at jvm.StackOverFlowTest.testStack(StackOverFlowTest.java:21)

ps:即使我把内存调整到2G,stackLenth还在增加。证明不是因为达到了"线程请求的栈深度"而导致的StackOverflowError。

作者的结论:

在单线程模式下,不论是栈帧太长,还是虚拟机栈容量太小,都只能抛出StackOverflowError。

在多线程模式下,通过不断地建立线程到时可以抛出OutOfMemoryError异常。

运行时内存池异常和方法区内存异常

运行时常量池存储在方法区中。所以他们的异常都是一样的。方法去的jvm参数如:-XX:PermSize=10M -XX:MaxPermSize=10M 注意,和上面不一样,这里是等号。

方法区的内存溢出报错为:java.lang.OutOfMemoryError: PermGen space

深入理解JAVA虚拟机 自动内存管理机制的更多相关文章

  1. [深入理解Java虚拟机]<自动内存管理>

    Overview 走近Java:介绍Java发展史 第二部分:自动内存管理机制 程序员把内存控制的权利交给了Java虚拟机,从而可以在编码时享受自动内存管理.但另一方面一旦出现内存泄漏和溢出等问题,就 ...

  2. 深入java虚拟机学习 -- 内存管理机制

    前面说过了类的加载机制,里面讲到了类的初始化中时用到了一部分内存管理的知识,这里让我们来看下Java虚拟机是如何管理内存的. 先让我们来看张图 有些文章中对线程隔离区还称之为线程独占区,其实是一个意思 ...

  3. JAVA之自动内存管理机制

    一.内存分配 1.JVM体系结构 2.运行时数据区域 3.内存分配二.内存回收 1.垃圾收集算法 2.垃圾收集器三.相关参考一.内存分配JVM体系结构 在了解自动内存管理的内存分配之前,我们先看下JV ...

  4. java虚拟机(一)——内存管理机制与OOM异常

    一  java内存区域与内存溢出异常(OOM) 1)运行时数据区域划分        1.程序计数器(Program Conuter Register) 程序计数器是一块较小的内存空间,它是当前线程执 ...

  5. 深入理解JVM(一) -- 自动内存管理机制

    Java运行时数据区域分为:程序计数器,虚拟机栈,本地方法栈,Java堆,方法区,运行时常量池,直接内存,结构如下: 1.程序计数器: 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示 ...

  6. 深入理解java虚拟机,内存管理部分

    1,对象回收前会调用finalize()方法,尝试自救,只能调用一次 2,上面横向对比c++的析构函数,但是java有良好的内存管理,而且try/catch做得比较好 3,回收一个常量,1,对象的实例 ...

  7. 深入理解Java虚拟机(自动内存管理机制)

    文章首发于公众号:BaronTalk 书籍真的是常读常新,古人说「书读百遍其义自见」还是很有道理的.周志明老师的这本<深入理解 Java 虚拟机>我细读了不下三遍,每一次阅读都有新的收获, ...

  8. 【深入理解Java虚拟机】自动内存管理机制——垃圾回收机制

      Java与C++之间有一堵有内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来.C/C++程序员既拥有每一个对象的所有权,同时也担负着每一个对象生 ...

  9. 【深入理解Java虚拟机】自动内存管理机制——内存区域划分

      Java与C++之间有一堵有内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来.C/C++程序员既拥有每一个对象的所有权,同时也担负着每一个对象生 ...

随机推荐

  1. 创建一个包含TC的Alpine镜像

    镜像的创建 更换镜像至ustc(为了测试时的速度) 安装musl-dev make gcc linux-headers bison flex以使TC可以编译 拷贝进TC的源代码 进入源代码文件夹进行编 ...

  2. 自定义View饼状图的绘制

    package com.loaderman.customviewdemo; import android.content.Context; import android.graphics.Canvas ...

  3. python 引流

    Python给抖音自动点赞和评论,实现自动化运营! 都说抖音有毒,一刷就停不下来了.看来抖音这款产品紧紧抓住了人们内心深处的某些需求.当然今天不是来探讨抖音这款产品的啊.今天我们来学习如何用 Pyth ...

  4. 利用Python进行异常值分析实例代码

    利用Python进行异常值分析实例代码 异常值是指样本中的个别值,也称为离群点,其数值明显偏离其余的观测值.常用检测方法3σ原则和箱型图.其中,3σ原则只适用服从正态分布的数据.在3σ原则下,异常值被 ...

  5. Pytorch笔记 (3) 科学计算2

    一.组织张量的元素 (1)重排张量元素 本节介绍在不改变 张量元素个数 和 各元素的值的情况下改变张量的大小 torch.Tensor类的成员方法 reshape() 参数是多个int类型的值. 如果 ...

  6. Egret入门学习日记 --- 第二篇 (书籍的选择 && 书籍目录 && 书中 3.3 节 内容)

    第二篇 (书籍的选择 && 书籍目录 && 书中 3.3 节 内容) 既然选好了Egret,那我就要想想怎么学了. 开始第一步,先加个Q群先,这不,拿到了一本<E ...

  7. Jmeter 04 Jmeter变量的使用

    在使用jmeter进行接口测试时,我们难免会遇到需要从上下文中获取测试数据的情况,这个时候就需要引入变量了. 定义变量 添加->配置元件->用户自定义的变量 添加->配置元件-> ...

  8. spring boot-11.全局捕获异常

    1.在Spring boot 中如果发生错误,浏览器访问会默认跳转到Whitelabel Error Page 这个错误页面,如果是客户端访问的话返回JSON格式的错误数据,说明spring boot ...

  9. 【提高组NOIP2008】双栈排序 (twostack.pas/c/cpp)

    [题目描述] Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一个元素压入栈S1 操作b 如果栈 ...

  10. springboot2.0国际化

    springboot2.0配合thymeleaf实现页面国际化 1. 引入thymeleaf <?xml version="1.0" encoding="UTF-8 ...