Java 内存和垃圾回收

概述

Java GC机制概括的说就是:该机制对JVM中的对象进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,保证JVM中的内存空间,防止出现内存泄漏和溢出问题。

内存区域

在Java运行时的数据区里,由JVM管理的内存区域主要分5部分:

内存区域

.

程序计数器 (Program Counter Register)

程序计数器是一个比较小的区域,它主要用来指示当前线程所执行的字节码执行到了第几行,可以理解为当前线程的行号指示器。字节码解释器可以通过改变它的值来获取下一条指令。

每个程序计数器只用来记录一个线程的行号,所以它是 线程私有的,一个线程一个程序计数器。由于程序计数器知识纪录当前指令地址,所以不存在内存溢出的情况。因此程序计数器是所有JVM内存区域中唯一没有OutOfMemoryError的区域。

虚拟机栈 (JVM Stack)

一个线程的每个方法执行时,都会创建一个栈帧(Stack Frame),栈帧中存储有局部变量表、操作栈、动态链接、方法出口等。当方法被调用时,栈帧被压入虚拟机栈,方法完成时,栈帧出栈。

局部变量表中存储方法的局部变量,包括基本数据类型、对象引用和返回地址等。局部变量表是在编译时确定,运行时所分配的空间是完全确定的,在方法的生命周期内都不会改变。

虚拟机栈定义了两种异常,如果线程调用的栈深度大于虚拟机所允许的最大深度,则抛出StackOverFlowError;如果内存不足,则抛出OutOfMemoryError

每个线程对应一个虚拟机栈,所以虚拟机栈也是线程私有

本地方法栈 (Native Stack)

本地方法栈作用、运行机制、异常类型等都与虚拟栈相同,唯一的区别是:虚拟机栈时执行Java方法的,而本地方法栈是用来执行native方法的,很多虚拟机会将这两个放在一起。

堆区(Heap)

在JVM管理的内存中,堆区是最大的一块,也是GC机制所管理的主要内存。堆区由所有线程共享,在虚拟机启动时创建,堆区的存在时为了存储对象实例。如果在执行垃圾回收后,仍然没有足够的内存分配,也不能在扩展时,将会抛出OutOfMemoryError:Java heap space异常。

方法区(Method Area)

在Java虚拟机规范中,将方法区作为堆的一个逻辑部分来对待,但事实场,方法区并不是堆(Non-Heap),但是它也是一般人理解中的永生代

方法区是所有线程共享的,用于存储已经被虚拟机加载的类信息(包括版本、Field、方法、接口等信息)、final常亮、静态变量、编译器即时编译的代码等。

方法区上一般执行的垃圾收集很少,但这并不代表方法区上完全没有垃圾收集,其上的垃圾收集主要是针对常量池和对已加载类的卸载。

但在方法区上做垃圾收集,条件苛刻而且效果也不理想,所以一般不会太考虑。如果内存不足时,会抛出OutOfMemoryError:PermGen space异常。

内存分配

这里所说的内存分配,主要是在堆区的分配。Java的内存分配和回收规则概括的说就是:分代分配,分代回收。对象根据存活时间被分到:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。

年轻代(Young Generation)

对象被创建时,首先分配到年轻代,大对象会直接分配到年老代,大部分对象在创建后很快就不用了,因此很快变的不可到达,被年轻代的GC回收。

年轻代分为3个区域:伊甸区(Eden区)和两个存活区(Survivor 0, Survivor 1)。

  • 绝大多数刚创建的对象被分配到Eden区,大多数很快会被回收,Eden区是连续的内存空间,分配内存极快;
  • 存活区是用来放置Minor GC中幸存的对象的,也是实施“停止-复制”回收的关键。

在Eden区,HotSpot虚拟机使用了两种技术来加快内存分配。分别是bump-the-pointerTLAB(Thread-Local Allocation Buffers)

  1. 由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度;
  2. TLAB技术是对于多线程而言的,将Eden区分为若干段,每个线程使用独立的一段,避免相互影响。

TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内存。

年老代(Old Generation)

如果对象在年轻代存活足够多的时间而没有被清理掉,则会被复制到年老代。年老代的空间一般比年轻代大,能够存更多的对象,在年老代发成的GC次数要比年轻代少很多。当年老代内存不足时,会执行Major GC,也叫Full GC。

如果对象比较大,年轻代无法存放,大对象会直接分配到年老代上,所以尽量少用大对象。

可能存在年老代的对象引用年轻代对象的情况,如果需要执行Minor GC,则可能需要查询整个年老代以确定是否可以清理回收,这过于低效。解决方法:年老代中维护一个512 byte的块--“Card Table”,所有老年代引用新生代对象的纪录都在这里,Minor GC只需要查询这个块就可以。

垃圾回收机制

垃圾回收的基本规则:分代收集,每代的回收机制都不同。

年轻代
  • 当Eden区满的时候,执行Minor GC,将存活对象复制到Survivor0 中,清空Eden区;
  • 下次Eden区满了,再执行Minor GC,将存活对象复制到Survivor1 中,清空Eden区;
  • 将Survivor0 中可以晋级的对象挪到年老代,存活的对象复制到Survivor1 中,清空Survivor0区;
  • 对象在两个Survivor区中复制多次后,(Hot Spot默认15次),对象会复制到年老代。

从上面的过程看出,Eden区是一个连续的空间,且Survivor区总有一个为空。经过一次GC和复制,一个Survivor区保存当前存活的对象,而Eden区和另一个Survivor区的内容都不在需要,直接清空。到下一次GC时,两个Survivor区的角色互换。这种方式分配内存和清理内存效率极高,这种垃圾回收的方式就是 停止-复制(stop-and-copy)清理法

由于绝大部分的对象都是短命的,甚至存活不到Survivor中,所以,Eden区与Survivor的比例较大,HotSpot默认是 8:1,即分别占新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下来的内存超过了10%,则需要将一部分对象分配到年老代。

年老代

年老代存储的对象比年轻代多很多,而且不乏大对象,对年老代实施停止-复制算法,则相当低效。一般,年老代使用的算法是“标记-整理”算法,即:标记仍然存活的对象,将所有存活的对象向一端移动,以保证内存的连续。

在发生Minor GC时,虚拟机会检查每次晋升进入年老代的对象大小是否大于年老代的剩余空间。如果年老代的剩余空间不够,则直接出发一次Full GC。否则,就需要看是否设置 XX:+HandlePromotionFailure,如果不允许升级失败,则每次Minor GC都会触发Full GC,哪怕年老代还有足够的内存空间。

方法区(永久代)

永久代的回收有两种:常量池中的常量和无用的类信息。其中对于无用的类进行回收,必须保证3点:

  1. 类的所有实例都已经被回收
  2. 加载类的ClassLoader已经被回收
  3. 类对象的Class对象没有被引用(即没有通过反射引用该类)

垃圾收集器

在GC机制中,起重要作用的是垃圾收集器,垃圾收集器是GC的具体实现,Java虚拟机规范中对于垃圾收集器没有任何规定,所以不同厂商实现的垃圾 收集器各不相同,HotSpot 1.6版使用的垃圾收集器如:

年轻代

需要明确一点,年轻代采用的停止-复制算法中,停止的意义是在回收内存时,需要暂停其他所有线程的执行,这个是很低效的,现在各种年轻代收集器越来越优化这一点,但仍然只是将停止时间变短,并未彻底取消停止。

  1. Serial收集器:使用一个线程进行GC,其他工作线程暂停;
  2. ParNew收集器:Serial收集器的多线程版本,用多个线程进行GC,并行,其他工作线程停止,缩短垃圾回收时间;
  3. Parallel Scavenge 收集器:关注CPU吞吐量,即玉宁用户代码的时间/总时间,比如JVM运行100分钟,其中运行用户代码99分钟,垃圾收集1分钟,则吞吐量是99%,这种收集器能最高效率的利用CPU,适合运行后台运行
年老代
  1. Serial Old收集器:单线程收集器,串行,使用标记-整理算法,整理的方法是Sweep(清理)和Compact(压缩),使用单线程进行GC,其他工作线程暂停;
  2. Parallel Old收集器:多线程并行收集器,多线程机制和Parallel Scavenge差不多,使用标记整理,整理时Summary(汇总)和Compact(压缩),执行时,仍然要暂停其他线程。它在多核计算中很有用;
  3. CMS(Concurrent Mark Sweep)收集器:致力于获取最短停顿时间,使用标记清除算法,多线程,优点是并发收集,用户线程可以和GC线程同时工作,停顿小
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供...
    简欲明心阅读 89,365评论 17 311
  • 来自: Android梦想特工队作者: Aaron主页: http://www.wxtlife.com/原...
    技术特工队阅读 4,354评论 0 28
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,174评论 11 349
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,644评论 0 7
  • 作者:一字马胡 转载标志 【2017-11-12】 更新日志 日期更新内容备注 2017-11-12新建文章初版 ...
    beneke阅读 2,184评论 0 7