深入学习JVM 【1】运行时数据区域划分

前言

在使用c++进行编程时,我们通过new创建的每一个对象都需要有对应的delete操作去释放对象所占用的内存,对内存的掌控度比较高,但是程序员需要知道对象什么时候不需要使用了,并需要手动释放内存,如果忘记了delete释放,很容易出现内存泄漏(申请内存后,没有释放,会一直占用着)和内存溢出(因为过多的内存泄漏导致无法申请足够的内存,即out of memory)的问题。

相比之下,java虚拟机提供了自动内存管理机制,java程序员可以解放双手,不再需要去写delete等手动释放内存的代码,虚拟机会自动将内存中无用的对象占用的内存释放。

了解jvm的必要

虽然有自动内存管理机制的存在,但是不代表写的每个java程序都不存在内存泄漏和内存溢出问题,我们需要对虚拟机有足够的了解,才能在发生内存泄露和内存溢出的时候有效地排查问题。

本文将对jvm虚拟机运行时内存进行一个基本的介绍,后续的文章也会讲解jvm其他知识,大部分都是自己的读书总结加上自己的理解。希望将自己的所学进行总结的同时能惠及他人,如果有什么地方讲的不对,希望各位同学能够指出。

内存划分

java虚拟机将其管理的内存划分为以下几块:

  • 程序计数器 (PC Register)
  • 虚拟机栈 (JVM Stack)
  • 本地方法栈 (Native Method Stack)
  • 堆 (Heap)
  • 方法区 (Method Area)
运行时数据区域.png

各个区域都有其各自的特点和作用,以及不同的创建和销毁的时间

各个区域的介绍

程序计数器

  • 描述
    • 程序计数器是一个较小的内存区域
  • 作用
    • 记录着当前线程所执行的字节码行号
    • 字节码解释器在工作的时候,通过改变这个计数器的值来选取下一条要执行的字节码指令。
    • 分支循环跳转异常处理线程恢复等功能都需要使用到这个程序计数器。
  • 特点
    • 线程私有--每个线程都有一个独立的程序计数器。
    • 如果当前线程正在执行一个java方法,这程序计数器的值为虚拟机字节码指令的地址,如果执行的是一个Native 方法,这个计数器的值则为空。
    • 程序计数器是唯一没有规定OutOfMemoryError的内存区域
  • 创建时间
    • 每个线程启动的时候会创建一个较小的内存区域作为线程的程序计数器
  • 销毁时间
    • 线程结束时会释放该内存区域

扩展问题1:为什么需要程序计数器?

java虚拟机的多线程是通过线程轮转,分配CPU时间片来执行java程序,当线程切换时,为了能够回到原来的字节码执行位置继续程序的执行,所以每个线程会有一个程序计数器。

扩展问题2:Native方法是什么?

java程序执行的时候调用的方法,有些是用java语言实现的,有些是用其他语言编写实现的,用其他语言实现的方法称为Native方法本地方法,native方法会使用native关键字进行标注,如Object类的getClass()方法:

public class Object {
    
    public final native Class<?> getClass();
    ...
}

由于native方法不是java实现的,也就没有字节码行号之说,此时程序计数器的值应当为空(undefined)。

虚拟机栈

  • 描述
    • 虚拟机栈是描述java方法执行过程的一个内存模型
    • 具体描述:每个方法在执行的时候都会创建一个栈帧,栈帧中存储的是java方法的局部变量表、操作数栈、动态链接、方法出口等信息。java程序在执行的时候每调用一个java方法都会对应的创建栈帧并压入虚拟机栈中,当方法执行完毕,又会将栈帧从虚拟机栈中弹出。虚拟机栈就是栈帧存放的一个栈结构的内存区域。
  • 作用
    • 描述java方法执行的过程,保存栈帧。
  • 特点
    • 线程私有
    • 此区域可能会有两种内存异常情况:
      • 当栈的深度大于虚拟机所限制的最大深度,会抛出StackOverflowError异常。
      • 如果虚拟机栈动态扩展无法申请到足够的内存,就会抛出OutOfMemoryError异常。
  • 创建时间
    • 线程启动的时候
  • 销毁时间
    • 线程结束的时候

扩展1:局部变量表

局部变量表用于存放编译期可知的各种基本数据类型、对象引用、returnAddress类型(一条字节码指令的地址)

对于基本数据类型,存放的是变量的名和值;

对于引用类型,存放的是指向对象在堆中的起始地址。

ps: 对于64位的long或double类型的局部变量会占用两个局部变量表空间(Slot),其余的数据类型都是只占用一个局部变量表空间。

局部变量表所需要的空间在编译期间已经计算好了,在一个方法执行时,需要为栈帧分配多少局部变量表空间是完全确定的

本地方法栈

本地方法栈的特性和虚拟机栈几乎一样。

  • 本地方法栈与虚拟机栈的区别
    • 本地方法栈为本地方法服务
  • 本地方法栈可能出现的异常
    • 同虚拟机栈一样可能抛出StackOverflowErrorOutOfMemoryError 异常。

  • 描述
    • 堆内存的唯一目的是存放对象实例
    • 堆内存是垃圾收集的主要区域,因此也叫GC堆
  • 作用
    • 存放对象实例
  • 特点
    • 虚拟机所管理的内存中最大的一块
    • 几乎所有的对象都在堆区分配内存,当然也有例外,JIT编译器有可能会进行优化,直接在栈上分配,有关信息可以直接搜索“逃逸分析”了解,这不在本文的讨论范围内。
    • 所有线程共享的一块内存区域
    • 堆内存在物理上不一定是连续的,保证逻辑连续即可
    • 堆内存区域无法满足分配对象实例所需内存,可能抛出OutOfMemoryError异常
    • 堆内存设置固定大小也可以动态扩展,可在启动参数上指定最小大小及扩容的上限。
  • 创建时间
    • 虚拟机启动的时候就创建了堆内存

扩展1: 堆区细分

jvm为了垃圾回收的方便,将堆划分为新生代老年代,新创建的对象基本上都放在新生代中,而存活比较久的对象则会移到老年代中。新生代和老年代采用不同的垃圾收集算法,可以更高效地回收内存。采用复制算法的新生代还可以细分为EdenFrom SurvivorTo Survivor。具体的详情是怎样的,为了不偏离这篇文章的主旨,这里先打个问号,后序的文章将会详细介绍堆区的几个划分的用途。

堆区虽然是线程共享的,但是如果设定了启动参数-XX:+UseTLAB,则开启了本地线程分配缓冲(Thread local Allocation Buffer, TLAB),会为每个线程单独在堆中划分出一个TLAB,哪个线程需要分配内存,就先在该线程对应的TLAB中分配内存,当TLAB用完,才在堆区的Eden中继续申请一块TLAB

方法区

方法区是用于存放虚拟机加载的类信息、常量、静态变量、编译后的代码等数据。

方法区特点:

  • 线程共享
  • 方法区大小可固定也可以动态扩展。
  • 与堆区一样不需要连续的物理内存,但要求逻辑连续。
  • 该区域的垃圾收集目标主要是针对运行时常量池的回收和对类进行卸载
  • 可能出现OutOfMemoryError异常。

扩展1:运行时常量池:

class文件中有个常量池,运行时常量池就是class文件中常量池经过类加载后存放的内存区域。

常量池主要存放两类常量:字面量和符号引用。

字面量指字符串,声明为final的常量值等;而符号引用是java编译后生成的各种常量,其包括:

  • 类和接口的全限定名
  • 成员变量的名称和描述符
  • 方法的名称和描述符

jdk1.8之前,方法区是用永久代实现的,
jdk1.7以下的版本,运行时常量池是方法区的一部分,而jdk1.7及之后的版本,运行时常量池中的字符串常量池已经不在方法区,而是在java堆中开辟了一块区域作为字符串常量池。

在jdk1.8开始,已经没有永久代的概念,譬如符号引用(Symbols)转移到了native 堆中的元空间;字面量也在 java heap;类的静态变量(class statics)转移到了java heap

扩展2:常量是否只能在编译期产生?
否,运行期也可能将新的常量放入运行时常量池中,比如Stringintern方法。在jdk1.7的表现如下:

// 如果运行时常量池中,存在"10"这个字符串常量
// 则将常量池中的字符串对象返回,
// 如果不存在,则直接在运行时常量池中创建“10"这个字符串,并将其返回。
String s = String.valueOf(10).intern();

直接内存

前面讲的几块都属于虚拟机管理的运行时数据区域,java程序中也有可能会用到不是虚拟机运行时内存区域的一部分。这块内存我们通常称为直接内存

  • 直接内存不受java堆大小的限制,但是受本机物理内存的限制。

  • 直接内存也可能导致出现OutOfMemoryError异常。

直接内存的例子:
jdk 1.4 加入的NIO类,引入了一种基于通道Channel和缓冲区Buffer的IO方式。直接通过Native方法在java堆外的直接内存中分配内存, 通过存储在java堆中的DirectByteBuffer对象作为这块直接内存的引用。操作DirectByteBuffer即可操作直接内存,这样做的好处是避免了要使用直接内存的时候需要先复制到java堆中。直接操作直接内存更加高效。

点赞是对我最大的鼓励

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • JVM内存模型Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: ...
    光剑书架上的书阅读 2,477评论 2 26
  • 一、运行时数据区域 Java虚拟机管理的内存包括几个运行时数据内存:方法区、虚拟机栈、本地方法栈、堆、程序计数器,...
    加油小杜阅读 1,510评论 1 15
  • 环境描述: 阅读本文前,假定您已经能够启动单节点EOSIO node,如果还不能正确操作,请参考官方WIKI:ht...
    eosspark阅读 2,407评论 0 2
  • “破睡须封不夜侯”,不夜侯是茶的雅号,它有着提神醒脑之功。毋庸置疑,这是茶人三部曲的第二部。 絮叨完第一部《南方有...
    小裤兜儿阅读 2,667评论 1 3
  • 你试过深刻的感觉吗? 心,总是莫名的会被感动。所以,路过别人的故事,总是被剧情所左右,所以,才有了后来发生的种种。...
    粟小萸阅读 156评论 0 0