Java 垃圾收集(GC)浅谈

Java 垃圾收集(GC)浅谈

为什么需要垃圾回收?

哪些内存需要回收?

什么时候回收?

如何回收?

为什么需要垃圾回收?

​ 当需要排查各种内存溢出、内存泄露问题时,当垃圾收集称为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。在构建大型程序时,GC直接影响着内存优化和运行速度。

Java 内存区域

了解GC机制之前,需要先搞清楚Java程序在执行的时候,内存究竟是如何划分的。


gc_memo.jpg

私有内存区的区域名和相应的特性如下表所示:

区域名称 特性
程序计数器 指示当前程序执行到了哪一行,执行Java方法时记录正在执行的虚拟机字节地址;执行本地方法时,计数器值为undefined
虚拟机栈 用于执行Java方法。栈帧存储局部变量表、操作数栈、动态链接、方法返回一些额外的附加信息。程序执行时栈帧入栈;执行完成后栈帧出栈
本地方法栈 用于执行本地方法,其他和虚拟机栈类似

虚拟机栈中的局部变量表

里面存放了三个信息:

  • 各种基本数据类型(boolean、byte、char、short、int、float、long、double)
  • 对象引用(reference)
  • returnAddress地址

这个returnAddress和程序计数器有什么区别?前者是指示JVM的指令执行到了哪一行,后者是指你的代码执行到哪一行。

哪些内存需要回收?

私有内存区伴随着线程的产生而产生,一旦线程中止,私有内存区也会自动消除,因此我们在本文中讨论的内存回收主要是针对共享内存区。

共享内存区:

区域名称 特性
Java堆 Java虚拟机管理的内存中最大的一块,所有线程共享,几乎所有的对象实例数组都在这类分配内存。GC主要就是在Java堆中进行的。
方法区 用于存储已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。但是已被最新的JVM取消了。现在,被加载的类作为元数据加载到底层操作系统的本地内存区。

Java 堆

堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。堆是应用程序在运行期请求操作系统分配给自己的向搞地质扩展的数据结构,是不连续的内存区域。用一句话总结堆的作用:程序运行时动态申请某个大小的内存空间。

gc_heap_generation.jpg

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大都具备朝生夕灭的特性,所以Minor GC 非常频繁,一般回收速度也比较快。

老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC (但非绝对,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上

新生代:刚刚新建的对象在Eden中,经历一次Minor GC, Eden中的存货对象就被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC, Eden和S0中的存活对象会被复制送入第二块survivor space S1。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16此,改对象就被送到老年代中。

至于为什么兴盛带要分出两个survivor区,参考博客为什么新生代内存需要有两个Sruvivor区

老年代:如果某个对象经历了几次垃圾回收之后还存活,就会被存放到老年代中。老年代的空间一般比新生代大。

对象创建后的内存分配

创建一个对象后,他会被放在堆内存的哪个部分呢?

gc_object_create.jpg

什么时候回收?

Java并没有给我们提供明确的代码来标注一块内存并将其回收。或许你会说,我们可以将相关对象设为null或者用System.gc()。然而,后者将会严重影响代码的性能,因为每一次显示调用system.gc()都会停止所有响应,去检查内存中是否有可回收的对象,这回对程序的正常运行造成极大威胁。另外,调用该方法并不能保障JVM立即进行垃圾回收,仅仅是通知JVM要进行垃圾回收了,具体回收与否完全由JVM决定。

生存还是死亡

可达性算法

这个算法的基本思路是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

gc_reachability.png

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

如何回收?

标记-清除(Mark-Sweep)算法

分为两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点:效率问题,标记和清除两个过程的效率都不高;空间问题,会产生很多碎片。

gc_mark_sweep.jpg

复制算法

将可用内存按容量划分为大小相等的两块,每次只用其中一块。当这一块用完了,就将还存活的对象复制到另外一块上面,然后把原始空间全部回收。高效、简单。
缺点:将内存缩小为原来的一半。


gc_copying.jpg

标记-整理(Mark-Compat)算法

gc_mark_compact.jpg

标记过程与标记-清除算法过程一样,但后面不是简单的清除,而是让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

分代收集(Generational Collection)算法

gc_generation.jpg
  • 新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;
  • 老年代中,其存活率较高、没有额外空间对它进行分配担保,就应该使用“标记-整理”“标记-清除”算法进行回收。

一些收集器

Serial收集器

单线程收集器,表示在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。"Stop The World".

ParNew收集器

实际就是Serial收集器的多线程版本。

  • 并发(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;
  • 并行(Concurrent):指用户线程与垃圾收集线程同时执行,用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

Parallel Scavenge收集器

该收集器比较关注吞吐量(Throughout)(CPU用于用户代码的时间与CPU总消耗时间的比值),保证吞吐量在一个可控的范围内。

CMS(Concurrent Mark Sweep)收集器

CMS收集器是一种以获得最短停顿时间为目标的收集器。

G1(Garbage First)收集器

从JDK1.7 Update 14之后的HotSpot虚拟机正式提供了商用的G1收集器,与其他收集器相比,它具有如下优点:并行与并发;分代收集;空间整合;可预测的停顿等。

新生代收集器使用的收集器:Serial、ParNew、Parallel Scavenge

老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

gc_sjq.jpg

Java性能优化

大多数针对内存的调优,都是针对特定情况的。但是实际中,调优很难与Java运行动态特性的实际情况和工作负载保持一致。也就是说,几乎不可能通过单纯的调优来消除GC的目的。

写程序的时候应该注意的点:

  1. 减少new对象。每次new对象之后,都要开辟新的内存空间。这些对象不被引用之后,还要回收掉。因此,如果最大限度地合理重用对象,或者使用基本数据类型替代对象,都有助于节省内存。
  2. 多使用局部变量,减少使用静态变量。局部变量被创建在栈中,存取速度快。静态变量则是存储在堆内存中。
  3. 避免使用finalize,该方法会给GC增添很大的负担
  4. 如果是单线程,尽量使用非多线程安全的,因为线程安全来自于同步机制,同步机制会降低性能。例如,单线程程序,能使用HashMap,就不要使用HashTabl。同理,尽量减少使用synchronized。
  5. 用移位符号替代乘除号。比如:a*8应该写作a<<3
  6. 对于经常反复使用的对象使用缓存
  7. 尽量使用基本类型而不是包装类型,尽量使用一维数组而不是二维数组
  8. 尽量使用final修饰符,final表示不可修改,访问效率高
  9. 单线程下(或者是针对于局部变量),字符串尽量使用StringBuilder,比StringBuffer要快
  10. 尽量使用StringBuffer来连接字符串。这里需要注意的是,StringBuffer的默认缓存容量是16个字符,如果超过16,append犯法调用私有的expandCapacity()方法,来保证足够的缓存容量。因此,如果可以预设StringBuffer的容量,避免append再去扩展容量。

参考资料:

1.《深入理解Java虚拟机-JVM高级特性与最佳实践》

2.橙子wj的博客 (强推)

3.Java性能优化之JVM GC(垃圾回收机制)

4.面试题:“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”

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

推荐阅读更多精彩内容