JVM 内存模型与GC机制

摘要:最近在压测的时候遇到了OutOfMemoryError错误,发现是jvm内存超限,虽然过程是数据库瓶颈导致线程阻塞,垃圾回收不及时所导致,但当时解决问题的时候还是采用了一个治标不治本的法子:使用-Xms -Xmx调整jvm堆查占用内存,后面发现没解决根本为题,无论将 -Xmx调整到多大,只是增大缓存,最终还是会被塞满,报OutOfMemoryError错误。

因此,在解决该问题后有特意了解了下jvm的内存结构和回收机制

一、jvm内存模型

  • 程序计数器
  • java虚拟机栈
  • 本地方法栈
  • java堆
  • 方法区
  • 运行时常量池
1、程序计数器

其实在了解程序计数器之前多线程执行连贯性问题对我有些困扰,总不理解为啥多线程各个指令、数据不会窜线。
程序计数器,是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器,运行的程序已经是字节码了,而字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
每一个线程都有独立的程序计数器,属于每个线程的"私有内存",以此也可以来确保线程命令不会"窜线"。

2、java虚拟机栈

简单理解,就是java的方法,每个方法被执行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
虚拟机栈内存了方法还包括里面包含的局部变量,如数据基本类型(int ,char....),也包方法中所含对象的地址(注意,此处是存对象地址,对象本身存在java堆中)。
当方法出栈时该部分内容会被释放。

3、本地方法栈

这个概念我也没太明白,目前理解就是和java虚拟机栈一样,有点区别就是使用native修饰的方法会放到这个栈中,望大神指教,指正。

4、java堆

java堆是占用java虚拟机最大空间的一块内存,可以粗暴点理解就是存对象的地方,Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的
唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。套用下Java 虚拟机规范:所有的对象实例以及数组都要在堆上分配。但也并非绝对(做判断题要注意)。
这块内存分配就是我上文中所说使用-Xms和-Xmx来调节和实现拓展的。-Xms是最小占用空间,-Xmx是最大占用空间,可浮动。-Xmx默认占用物理内存的1/4,到如两者相等则为固定值。

5、方法区

包括常量、类变量、静态变量、即时编译器编译后的代码等数据。属于多线程恒共享数据。可以理解为jvm不停,这里面的数据就会一直存在。真真的全局变量

二、直接内存

这一块怕说不明白,我引用下:
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现,所以我们放到这里一起讲解。在JDK 1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据。
显然,本机直接内存的分配不会受到Java 堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括RAM 及SWAP 区或者分页文件)的大小及处理器寻址空间的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。
逻辑内存模型我们已经看到了,那当我们建立一个对象的时候是怎么进行访问的呢?
在Java 语言中,对象访问是如何进行的?对象访问在Java 语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会却涉及Java 栈、Java 堆、方法区这三个最重要内存区
域之间的关联关系,如下面的这句代码:
Object obj = new Object();
假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java 栈的本地变量表中,作为一个reference 类型数据出现。而“new Object()”这部分的语义将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java 堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针。
如果使用句柄访问方式,Java 堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的=具体地址信息,如下图所示。


image

如果使用直接指针访问方式,Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference 中直接存储的就是对象地址,如下图所示

image

这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference 本身不需要被修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。就本书讨论的主要虚拟机Sun HotSpot 而言,它是使用第二种方式进行对象访问的,但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。

三、GC机制

GC 垃圾回收,主要针对区域为java堆。回收未被有效引用的无用对象。
此处主要梳理下回收流程,回收详解及算法分析可可参考下文章:
https://www.cnblogs.com/xiaoxi/p/6486852.html

java堆包含NewSpace和OldSpace两部分,且NewSpace区域分为Eden区、From区、To区。三者大小固定。
当有新对象进入时,会率先存入Eden区,from区和to区是两个相对的概念。
步骤 :
1、当有新对象产生,会存入到Eden区,但到Eden区存不下该对象时,此时jvm执行一次Minor GC ,会将所有无用对象回收,将有用的对象放在to区,新增的对象放在Eden区,此时From 区为空

2、当新对象继续产生,Eden区再装满,在执行一次Minor GC,回收无用对象,将Eden中有用的对象和‘1’中To去中有用的对象 存入‘1’的From区。此时‘1’中的From区和To区角色切换。新的From区为空

3、重复1、2,直到执行一次Minor GC时newSpace中的To区装不下依然有用的对象,此时所有老对象被放入OldSpace中。

4、进入OldSpace的几种情况

  • 如3所描述,当执行Minor GC时,newSpace中的To区存不下还存活的对象时,会把所有存活对象放入OldSpace
  • 当新建的对象太大时,直接存到OldSpace
  • 当对象存活年龄较大时(可通过已经挺过多少次Minor GC来计算),存入OldSpace
  • 动态判断年龄,如某个年龄的对象已经超过newSpace中To区的一半时,将大于等于改年龄的对象放入OldSpace

5、再次重复以上过程,当出现OldSpace也存不下还存活的对象时,则对整个java堆执行一次Full GC,检查当前java堆所有对象使用情况,并回收无用对象。若此时还不能给新对象分配内存,或者按规矩要存入OldSpace的对象无法存入时,则报出OutOfMemoryError异常

所以针对最原始的问题,当碰到OutOfMemoryError异常时,一味加大java堆内存也是不可取的,这样可以减少执行Minor GC的次数,如果该被释放的对象一直不被释放,最终还是会导致java堆被撑满,碰到此类情况应着重排查内存泄漏。但有时候java堆内存太小会使程序内存“周转”不过来,也会导致内存溢出,此时可以适当增大jvm内存上限值。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,413评论 3 83
  • 文章转自 http://blog.csdn.net/u012152619/article/details/4696...
    云狗狗狗狗狗阅读 576评论 1 4
  • 最近学习Python的GC机制时,想到了java的GC,忘得差不多了,(⊙﹏⊙)b!!这里便做一下回顾总结。推荐周...
    廿陆小生阅读 816评论 0 0
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,879评论 2 31
  • JVM笔记 JDK:Java、JVM、Java API类库,是支持java程序开发的最小环境。JRE:Java A...
    一条小袍袍YoY阅读 856评论 0 4