Java程序员一般不需要太关注内存,因为操作内存的权力都交给了Java虚拟机,但是Java程序员必须需要了解JVM是如何使用内存的,否则一旦内存出现泄漏或事溢出的话,就会一筹莫展不知道从哪去入手排查问题。

一、JVM内存模型

JVM在运行时会把它管理的内存划分成若干个不同区域,每个区域有各自不同的用处,以及不同的创建和销毁的时间,有的随着JVM进程启动而存在,而有的需要随着用户线程的启动和结束而创建和销毁,主要内存区域划分如下图示:

主要分成两大类,线程共享的区域和线程独享的区域。

线程共享区域

1.堆

堆内存是JVM管理的最大的内存区域,堆内存被所有线程共享,随着虚拟机的启动而创建,存放的全部是对象实例,几乎所有的对象实例都是在堆内存中被分配内存。随着JIT编译器的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术会导致对象不一定会在堆中分配内存了。

堆内存是垃圾回收主要的区域,而主流的垃圾回收都是采用的分代收集算法,所以堆内存也常常会被分为新生代和老年代,新生代又可以被分成Eden区、From Survivor区和To Survivor区。堆内存虽然可以划分成很多区,但仅仅是逻辑上的区分,实际存储的都是对象实例。而且堆内存可以在物理内存空间上不连续,只需要逻辑上连续即可。一般JVM优化主要也是针对堆内存来进行优化。而堆内存的扩展主要是通过-Xms和-Xmx参赛来进行配置,当堆内存不足以再创建新对象时就会抛出OutOfMemoryError异常

2.方法区

方法区和堆内存一样也是所有线程共享的,主要存储已经被虚拟机加载的类信息、静态变量、常量、即时编译器编译后的代码等信息。由于方法区存储的一般都是不变的数据,所以垃圾回收很少会在这个区域进行。方法区的垃圾回收主要是针对常量池和对类型的卸载。这个区域如何内存不足也会抛出OutOfMemoryError异常。

线程独享区域

1.虚拟机栈

虚拟机栈属于线程私有,随着线程的创建而创建,随着线程的销毁而销毁。而每执行一个Java方法,都会在虚拟机栈中创建一个栈帧 (Stack Frame),栈帧中又包含局部变量表、操作栈、动态链接和方法出口等信息。每一个Java方法被调用直到方法执行完成的过程,对应着一个栈帧在虚拟机栈中的入栈到出栈道过程。一般有一种说法是Java内存分为堆内存和栈内存,这种是比较笼统的,这里的栈内存实际指的仅仅是虚拟机栈中的一个栈帧的局部变量表区域。局部变量表中存储八种基本数据类型、对象引用(不同的虚拟机可能是指向对象起始地址的引用指针,也可能是指向一个对象的句柄或与对象相关的位置)

虚拟机栈内存区域每执行一个方法,有创建一个栈帧压栈,执行完后出栈,栈的特点是先进后出,后进先出,如果线程请求的栈深度大于虚拟机允许的深度,会抛StackOverFlowError(栈溢出);而如果虚拟机栈内存不足且扩展时也无法申请到足够的内存的话,则会抛OutOfMemoryError(内存溢出)

1.1、局部变量表:存储方法局部变量

1.2、操作数栈:出入栈进行操作数的计算

1.3、动态连接:动态寻址的过程,比如方法定义UserService userService; 那么实际运行的时候需要找到UserService接口具体是由哪个对象实现的

1.4、顾名思义就是方法的出口

下面以简单例子查看虚拟机栈的工作方式:

  public static Integer num = 10;

     public int add(int i){
int j = 5;
int k = i+j;
j++;
k = num + j;
return k;
}

定义一个简单的add方法,定义一个静态变量,代码编译之后结果如下:

  public int add(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=2
0: iconst_5
1: istore_2
2: iload_1
3: iload_2
4: iadd
5: istore_3
6: iinc 2, 1
9: getstatic #2 // Field num:Ljava/lang/Integer;
12: invokevirtual #3 // Method java/lang/Integer.intValue:()I
15: iload_2
16: iadd
17: istore_3
18: iload_3
19: ireturn
LineNumberTable:
line 7: 0
line 8: 2
line 9: 6
line 10: 9
line 11: 18 

(具体每一步的含义及局部变量表和操作数栈的变化情况在《JVM探秘6--图解虚拟机栈的局部变量表和操作数栈工作流程》详细图解)

2.程序计数器

程序计数器占有的内存较小,主要存储当前线程执行的字节码的行号,字节码执行器工作时需要根据程序计数器来选取下一条需要执行的字节码指令,由于JVM的多线程数通过线程轮流切换被分配CPU执行的,一个CPU同一时间只能处理一个线程的一个指令,为了在线程切换后能够恢复到正确的指令位置,就需要每个线程都有一个独立的程序计数器,各个线程之间的程序计数器互不影响。如果线程正在执行JAVA方法,则计数器记录正在执行的字节码指令的地址,如果正在执行的事本地方法,计数器值则为空。程序计数器由于存储的值只有字节码地址,没有扩展的需要,所以这个区域是不会出现OutOfMemoryError情况的。

3.本地方法栈

本地方法栈和虚拟机栈类似,不同的是虚拟机栈是为Java方法服务,而本地方法栈是为本地方法服务,而本地方法使用的语言不同的虚拟机是不一样的,也有的虚拟机会将本地方法栈和虚拟机栈合二为一。本地方法栈和虚拟机栈一样也会抛StackOverFlowError和OutOfMemoryError。

除了上面的JVM运行时内存划分,还有一类内存叫做直接内存,这类内存是不受JVM管控的,但是也会出现OutOfMemoryError异常。JDK1.4加入了NIO,引入了通道和缓冲区的I/O方式,可以使用本地方法直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作,可以避免java堆和native堆中来回复制数据而提高性能。直接内存不受JVM控制,但是会受到机器总内存的影响,如果JVM内存分配过大加上其他的内存大于了服务器的总内存,就会导致直接内存无法分配,同样也会出现OutOfMemoryError异常。

JVM探秘1--JVM内存运行时区域划分的更多相关文章

  1. JVM知识总结-运行时区域划分

    区域简介 JVM运行时区域有些随着虚拟机进程的启动而存在,有些依赖于用户线程的启动和结束而建立和销毁,大致分为以下几类:方法区,虚拟机栈,本地方法栈,堆,程序计数器,概念图如下(源于<深入理解J ...

  2. JVM学习笔记:Java运行时数据区域

    JVM执行Java程序的过程中,会使用到各种数据区域,这些区域有各自的用途.创建和销毁时间.根据<Java虚拟机规范>,JVM包括下列几个运行时数据区域,如下图所示: 其中红色部分是线程私 ...

  3. JVM运行时区域详解。

    我们知道的JVM内存区域有:堆和栈,这是一种泛的分法,也是按运行时区域的一种分法,堆是所有线程共享的一块区域,而栈是线程隔离的,每个线程互不共享. 线程不共享区域 每个线程的数据区域包括程序计数器.虚 ...

  4. JVM笔记【1】-- 运行时数据区

    目录 (一)java内存区域管理 1.1 程序计数器 1.2 虚拟机栈 1.3 本地方法栈 1.4 java堆 1.5 方法区 1.5.1 运行时常量池 (二)直接内存 (一)java内存区域管理 C ...

  5. Java 内存管理、JVM 工作原理与 Java 运行时系统

    Java 虚拟机规范中说明:所有的对象实例(all class instances)以及数组都要在堆上分配: the heap is the runtime data area from which ...

  6. JVM探秘:Java内存区域

    本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 概述 Java 虚拟机为程序员分担了很多内存管理的工作,不再像 C/C++ 那样容易出 ...

  7. JVM 专题十二:运行时数据区(七)对象的实例化内存布局与访问定位

    1. 对象的实例化 1.1 创建对象的方式 new 最常见的方式 变形1 : Xxx的静态方法 变形2 : XxBuilder/XxoxFactory的静态方法 Class的newInstance() ...

  8. JVM之基础概念(运行时数据区域、TLAB、逃逸分析、分层编译)

    运行时数据区域 JDK8 之前的内存布局 JDK8 之后的 JVM 内存布局 JDK8 之前,Hotspot 中方法区的实现是永久代(Perm),JDK8 开始使用元空间(Metaspace),以前永 ...

  9. 深入浅出JVM(一):运行时数据区域

    程序计数器 线程私有 指向了正在执行的虚拟机字节码指令的地址:如果是本地方法,数值为空 没有 OutOfMemoryError 错误的区域 Java虚拟机栈 线程私有: 生命周期与线程相同: 代表着 ...

随机推荐

  1. Ubuntu下pdf和图片互转

    前边文章可以将ppt转换为pdf  查看 使用unoconv将ppt转为pdf,再使用imagemagick将pdf转为图片 这次想将pdf和图片进行互转 当前目录下只有2.ppt 1.ppt转pdf ...

  2. Cesium高度解析

    var viewer = new Cesium.Viewer('cesiumContainer', { shadows : true }); //为true时,球体会有高程遮挡效果(在没有地形时候也会 ...

  3. 几个常见的Mysql索引问题

    1. 选择性较低的列是否适合加索引? 索引选择性等于列中不重复(distinct)的行数量(也叫基数),与记录总数的比值.范围在0-1之间.数值越大,索引越快. 例如主键是唯一的,不重复的,所以选择性 ...

  4. TypeSrcript如何引入第三方库 如果加d.ts以及async await如何使用 demo,只有代码,文字后续补充

    https://files.cnblogs.com/files/cappuccino/laya2.rar

  5. 宣布推出针对 Microsoft OneDrive 和 SharePoint 的 Autodesk AutoCAD 新集成

    在 OneDrive 和 SharePoint,我们的愿景是随时随地为你的团队提供简单而安全的文件访问.无论你的职位.专业或行业是什么,我们将始终致力于扩展和连接你最重视的内容.今天,我们发布了与 A ...

  6. 【C++/类与对象总结】

    1.以上是对本章知识的大致梳理,下面通过我自己在编程中遇到的问题再次总结. 私有成员必须通过get()函数访问吗?能不能直接调用? 私有成员必须通过公共函数接口去访问,比如设置set()修改成员内容, ...

  7. windows下怎样测试oracle安装是否成功以及在oracle中创建用户并赋予用户权限;和[Err] ORA-65096: 公用用户名或角色名无效的解决方案

    测试oracle数据安装是否成功,可按顺序执行以下两个步骤: 测试步骤 1:请执行操作系统级的命令:tnsping orcl 上述命令假定全局数据库名是 orcl.以下是命令执行后的示例(请在cmd命 ...

  8. 编译安装centos7 php7.2 mysql5.7 nginx1.9.9

    2018年3月12日 14:09:39 注意时效 centos7 网卡 cd /etc/sysconfig/network-scripts/ TYPE=Ethernet PROXY_METHOD=no ...

  9. Oracle11g 配置DG broker

    在配置DG broker之前需要确保Dataguard配置正常且主库和备库均使用spfile. 1. 主库配置 配置DG_BROKER_START参数 检查主库dg_broker_start设置 SQ ...

  10. ES6 字符串

    拓展的方法 子串的识别 ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法. includes():返回布尔值,判断是否找到参数字符串. startsWith( ...