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内存分为堆内存和栈内存,这种是比较笼统的,这里的栈内存实际指的仅仅是虚拟机栈中的一个栈帧的局部变量表区域。局部变量表中存储八种基本数据类型、对象引用(不同的虚拟机可能是指向对象起始地址的引用指针,也可能是指向一个对象的句柄或与对象相关的位置)
虚拟机栈内存区域每执行一个方法,有创建一个栈帧压栈,执行完后出栈,栈的特点是先进后出,后进先出,如果线程请求的栈深度大于虚拟机允许的深度,会抛*Error(栈溢出);而如果虚拟机栈内存不足且扩展时也无法申请到足够的内存的话,则会抛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方法服务,而本地方法栈是为本地方法服务,而本地方法使用的语言不同的虚拟机是不一样的,也有的虚拟机会将本地方法栈和虚拟机栈合二为一。本地方法栈和虚拟机栈一样也会抛*Error和OutOfMemoryError。
除了上面的JVM运行时内存划分,还有一类内存叫做直接内存,这类内存是不受JVM管控的,但是也会出现OutOfMemoryError异常。JDK1.4加入了NIO,引入了通道和缓冲区的I/O方式,可以使用本地方法直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作,可以避免java堆和native堆中来回复制数据而提高性能。直接内存不受JVM控制,但是会受到机器总内存的影响,如果JVM内存分配过大加上其他的内存大于了服务器的总内存,就会导致直接内存无法分配,同样也会出现OutOfMemoryError异常。
JVM探秘1--JVM内存运行时区域划分的更多相关文章
-
JVM知识总结-运行时区域划分
区域简介 JVM运行时区域有些随着虚拟机进程的启动而存在,有些依赖于用户线程的启动和结束而建立和销毁,大致分为以下几类:方法区,虚拟机栈,本地方法栈,堆,程序计数器,概念图如下(源于<深入理解J ...
-
JVM学习笔记:Java运行时数据区域
JVM执行Java程序的过程中,会使用到各种数据区域,这些区域有各自的用途.创建和销毁时间.根据<Java虚拟机规范>,JVM包括下列几个运行时数据区域,如下图所示: 其中红色部分是线程私 ...
-
JVM运行时区域详解。
我们知道的JVM内存区域有:堆和栈,这是一种泛的分法,也是按运行时区域的一种分法,堆是所有线程共享的一块区域,而栈是线程隔离的,每个线程互不共享. 线程不共享区域 每个线程的数据区域包括程序计数器.虚 ...
-
JVM笔记【1】-- 运行时数据区
目录 (一)java内存区域管理 1.1 程序计数器 1.2 虚拟机栈 1.3 本地方法栈 1.4 java堆 1.5 方法区 1.5.1 运行时常量池 (二)直接内存 (一)java内存区域管理 C ...
-
Java 内存管理、JVM 工作原理与 Java 运行时系统
Java 虚拟机规范中说明:所有的对象实例(all class instances)以及数组都要在堆上分配: the heap is the runtime data area from which ...
-
JVM探秘:Java内存区域
本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 概述 Java 虚拟机为程序员分担了很多内存管理的工作,不再像 C/C++ 那样容易出 ...
-
JVM 专题十二:运行时数据区(七)对象的实例化内存布局与访问定位
1. 对象的实例化 1.1 创建对象的方式 new 最常见的方式 变形1 : Xxx的静态方法 变形2 : XxBuilder/XxoxFactory的静态方法 Class的newInstance() ...
-
JVM之基础概念(运行时数据区域、TLAB、逃逸分析、分层编译)
运行时数据区域 JDK8 之前的内存布局 JDK8 之后的 JVM 内存布局 JDK8 之前,Hotspot 中方法区的实现是永久代(Perm),JDK8 开始使用元空间(Metaspace),以前永 ...
-
深入浅出JVM(一):运行时数据区域
程序计数器 线程私有 指向了正在执行的虚拟机字节码指令的地址:如果是本地方法,数值为空 没有 OutOfMemoryError 错误的区域 Java虚拟机栈 线程私有: 生命周期与线程相同: 代表着 ...
随机推荐
-
小米Git
这个题目的意思其实就是要分别从根节点开始遍历(dfs)到给定的两个点,然后从得出的路径中获取最早相同的点即为结果. class Solution { public: /** * 返回git树上两点 ...
-
java多线程-CountDownLatch
简介 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待.用给定的计数 初始化 CountDownLatch.由于调用了 countDown() 方法,所以在当前计数 ...
-
流媒体学习一-------mediastreamer2 的简介
Mediastreamer2 是一个功能强大且小巧的流引擎,专门为音视频电话应用而开发.这个库为linphone中所有的接收.发送多媒体流提供处理,包括音/视频捕获,编码和解码,渲染. 特性: 接收. ...
-
TIFF6 Packbit algorithm
“Packbits” from ISO 12369 参考TIFF 6.0 Specification,点击TIFF, Version 6.0: @Section 9: PackBits Compres ...
-
Java中方法的重载和重置(覆盖)的区别
简单来说,重载就是在同一类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可,而重置(覆盖)是子类重新定义父类中己经定义的方法,即子类重写父类方法. 方法的重载 方法的重载就是在同 ...
-
3、简单了解Angular应用的启动过程
首先,了解一下目录结构: 然后,简明扼要的说一下应用的启动过程: 1.首先找到main.ts(模块启动入口),main.ts去找到app中的根模块app.module.ts 2.根模块app.modu ...
-
SQLite中的表达式
SQLite中的表达式 在SELECT的基本完整形式中,我们会看到几乎是所有的子句都会使用到表达式.以下是SQLite支持的表达式类型. expr binary-op expr | ...
-
http_build_query用法,挺方便的
http_build_query (PHP 5) http_build_query -- 生成 url-encoded 之后的请求字符串描述string http_build_query ( arra ...
-
null 解决方法
在iOS开发过程中经常需要与服务器进行数据通讯,Json就是一种常用的高效简洁的数据格式. 问题现象 但是几个项目下来一直遇到一个坑爹的问题,程序在获取某些数据之后莫名崩溃.其实很早就发现了原因:由于 ...
-
Laravel源码分析--Laravel生命周期详解
一.XDEBUG调试 这里我们需要用到php的 xdebug 拓展,所以需要小伙伴们自己去装一下,因为我这里用的是docker,所以就简单介绍下在docker中使用xdebug的注意点. 1.在php ...