JVM理论与实践【堆内存结构与垃圾回收】

标签: jvm 理论 实践 | 发表时间:2014-09-13 16:57 | 作者:
出处:http://www.iteye.com

        在生产环境下,通常都需要对JVM进行参数优化,其中对垃圾回收器的参数优化是一个非常重要的一方面。下面重点介绍Java的堆内存,垃圾回收算法,常用的垃圾回收器以及Java堆内存的分配策略,这些内容将作为对JVM进行垃圾回收参数优化的重要基础。然后通过简单示例验证Java的垃圾回收机制。

 

【Java堆内存结构】

       Java的堆(Heap)是存放对象的内存区域。在逻辑上我们可以把堆细分为新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)。

  1.  新生代:可以再划分为Eden(伊甸)、From Survivor(存活者)和To Survivor三个逻辑区域, 对象优先存放在新生代的Eden区域。 尴尬

  2. 老年代:新生代的对象经过几次垃圾回收之后,仍然存活的将存放到老年代,并且大对象可以不经过新生代而直接存放在老年代。 微笑

  3. 永久代:方法区使用永久代作为存储区域,在逻辑上,永久代是Java堆的一部分、但通常称之为“非堆”(Non-Heap)内存以示区别。方法区(Method Area)通常用来存放类的相关信息 (类加载器所加载的类的字段、方法签名等)、运行时常量池(如字符串常量池)、静态引用变量等。 叫喊

    Java的堆内存结构可下图简单描述,其中Eden、From Survivor和To Survivor区域这三部分将构成堆内存中的新生堆区域。 尴尬

 【对象是否存活】

       在进行垃圾回收(Garbage Collection,GC)之前,需要判断堆中哪些对象是可回收的(不再被引用的)、哪些对象是不能被回收的。在面向对象的语言中,通常使用如下两种方式来进行对象是否存活的判断。

  1. 引用计数法:Reference Counting

    可以给每个对象添加引用计数器,对象有新的引用时、计数器+1操作,引用失效时、计数器-1操作,计数器的值为0时、该对象就是可回收的。Python语言的垃圾回收机制就采用引用计数法,但是这种方法很难解决对象的循环引用问题。

  2. 根搜索算法:GC Roots Tracing 酷

    如果对象到GC Roots(比如,线程栈中的对象、静态引用变量等就可作为GC Roots)之间有引用链相连,表示该对象仍然被使用着的、不能被回收的,否则即认为对象没有被引用、是可以进行回收的。典型的高级语言如Java、C#都采用该方法。为了说明Java语言确实是采用根搜索算法判断对象是否存活的,编写程序: 

public class CircularRefTest {
    private CircularRefTest instance = null;
    private byte[] buffer = new byte[1024 * 1024];
    
    public static void main(String[] args) {
        CircularRefTest a = new CircularRefTest();
        CircularRefTest b = new CircularRefTest();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        System.gc();
    }
}     

     设置该程序运行时的VM Arguments参数:  

-Xms3m -Xmx3m -XX:+PrintGCDetails

     运行该程序,可看到控制台输出内容: 

2014-09-13T16:07:35.998+0800: [GC [DefNew: 623K->64K(960K), 0.0028993 secs][Tenured: 1407K->1471K(2048K), 0.0045221 secs] 1647K->1471K(3008K), [Perm : 1732K->1732K(12288K)], 0.0075367 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2014-09-13T16:07:35.998+0800: [Full GC (System) [Tenured: 2495K->446K(3484K), 0.0050437 secs] 2536K->446K(4636K), [Perm : 1734K->1734K(12288K)], 0.0051196 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
Heap
 def new generation   total 1664K, used 15K [0x02a50000, 0x02c10000, 0x02c50000)
  eden space 1536K,   1% used [0x02a50000, 0x02a53dd8, 0x02bd0000)
  from space 128K,   0% used [0x02bd0000, 0x02bd0000, 0x02bf0000)
  to   space 128K,   0% used [0x02bf0000, 0x02bf0000, 0x02c10000)
 tenured generation   total 3484K, used 446K [0x02c50000, 0x02fb7000, 0x03050000)
   the space 3484K,  12% used [0x02c50000, 0x02cbf9d0, 0x02cbfa00, 0x02fb7000)
 compacting perm gen  total 12288K, used 1739K [0x03050000, 0x03c50000, 0x07050000)
   the space 12288K,  14% used [0x03050000, 0x03202e80, 0x03203000, 0x03c50000)
No shared spaces configured.

     说明:在该程序中,先后定义了两个对象,并且每个对象先后被引用了两次,然后每个对象其中的一个引用失效,如果采用引用计数法,这两个对象是不能被回收的,因为每个对象都还有一个未失效的引用。但是通过控制台的观察发现,这两个对象确实是被回收了的,这说明Java并未采用引用计数法。在上述程序中,引用变量a和b是线程栈引用变量,都可以作为GC Roots, 天真a和b先后被置为null,这意味着对象通过instance引用无法和GC Roots建立一个有效的引用链,因此这两个对象都被回收了。这说明Java确实是采用根搜索算法来判断对象是否可回收的。

 

【引用类型的扩展】

     强引用(Strong):传统意义的引用。

     软引用(soft):在内存紧张时、会回收软引用对象(结合使用SoftReference类)。 尴尬

     弱引用:对象只能生存到下一次垃圾回收之前。

     虚引用:引用关系最弱、无法通过虚引用获取对象。

public class SoftRefTest {
    private byte[] buffer = new byte[2 * 1024 * 1024]; 
        
    public static void main(String[] args) {
        SoftRefTest objA = new SoftRefTest();
        SoftReference<SoftRefTest> softRef = new SoftReference<SoftRefTest>(objA);
        objA = null;
        SoftRefTest objB = new SoftRefTest();
        //System.gc();
    }
}

     设置VM Arguments参数: 

-Xms3m -Xmx3m -XX:+PrintGCDetails -XX:+PrintGCDateStamps

    运行该程序,通过控制台观察到软引用对象objA确实被回收了。 微笑

 

【关于finalize()方法】

    如果堆中的对象到GC Roots之间没有任何引用链,GC就可以对其进行回收. 在回收之前会调用对象的finalize()方法,可以通过覆盖该方法、把当前对象的引用重新和GC Roots连接起来、以阻止GC进行回收。 需要注意的是, 大笑一个对象的finalize()方法只会被执行一次、如果GC再次回收该对象,无法阻止被GC回收。

 

【永久代的垃圾回收】

    在Sun公司的HotSpot虚拟机中,方法区存放在Java堆的永久代(Permanent Generation)。在大量涉及反射、动态代理、cglib等字节码(bytecode)技术的场景(如项目中使用Spring、Hibernate等框架),需要虚拟机具有类卸载的功能, 皱眉保证永久代不会溢出。

 

【垃圾收集算法】

  1. 复制算法:Copying  蠢话

    将堆内存划分为两块,当其中一块正在使用中的的内存空间紧张时、把其中“存活”(仍然被引用)着的对象复制到另外一块空闲着的内存区域,然后清空当前内存空间. 复制算法通常作为新生代的垃圾回收策略。

  2. 标记-清除算法:Mark-Sweep 吐舌头

    先标记出可回收的对象,然后进行统一清除. 缺点:效率低、并且产生大量不连续的内存碎片。

  3. 标记-整理算法:Mark-Compact

    标记出可回收的对象、将所有存活的对象向其中一端移动,然后直接清理掉另一端的内存区域。

  4. 分代收集算法:Generational Collection 大笑

    将Java堆划分为新生代、老年代,新生代中的大多数对象都是可回收的,而老年代中的对象大多数都是不可回收的 。新生代采用复制算法:大多数对象都是可回收的、只需复制少数存活的对象、回收效率较高。老年代只有少数对象可回收、标记效率较高,因此采用标记-清除(无须移动对象)、标记-整理(移动存活对象到其中一侧)算法相结合进行回收。

 

【垃圾收集器】

  1. Serial收集器:串行收集器(collector) 惊讶

    单线程的垃圾收集器,是JVM运行在client模式下的默认收集器,进行垃圾回收时、必须暂停其他所有的工作线程(Sun称之为“Stop The World”)。

  2. ParNew收集器:并行收集器

    Serial收集器的多线程版本、多条线程并行进行垃圾回收、以减少暂停时间,通常用于JVM在server模式下新生代的收集器。并行:(Parallel):多个垃圾回收线程并行工作、仍需暂停其他工作线程。 哭

  3. CMS收集器:Concurrent Mark Sweep 吻

    并发标记清除收集器,通常作为老年代的收集器。并发(Concurrent):多条垃圾回收线程和工作线程交替运行、无须暂停工作线程,最大程度的提高垃圾效率、减少工作线程的停顿时间。

 

【堆内存分配策略】 吐舌头

   1. 新创建的对象将存放在新生代的Eden(伊甸)区域、以及其中一个Survivor(存活者)区域(From Survivor)。

  2. 堆内存紧张时、进行新生代对象的回收,存活着的对象将从Eden和From Survivor区域复制到To Survivor区域,如果To Survivor区域内存紧张、一部分存活对象将直接复制到老年代存放,然后清空Eden和From Survivor区域.。在下一次新生代垃圾回收时、From Survivor和To Survivor区域的角色互换.

 3. 大对象(通常是指内容很长的字符串或者数组)直接放入老年代、以避免大对象在新生代的反复拷贝。

 4. (新生代中)长期存活的对象将放入老年代,新生代中的对象每在Survivor区域完成一次拷贝、该对象的

 年龄(Age)加1,当对象的年龄增加到一定值(默认为15)时、该对象将被存放到老年代,以避免该对象在

 新生代的反复拷贝。

 

【Minor/Major GC】

  新生代GC(Minor GC):新生代的垃圾回收非常频繁(尽可能快的释放出可用空间)、效率很高(采用复制算法,大多数对象可回收、只需复制少数存活对象)。 微笑

  老年代GC(Major/Full GC):老年代的垃圾回收、效率通常比新生代的Minor GC慢至少10倍,(采用标记-清除、标记-整理算法),每次Full GC会同时进行至少一次Minor GC, 通常在堆内存紧张、或者显示的调用System.gc()时触发Full GC。 叫喊

 

=================================

垃圾回收机制的学习,确实枯燥乏味,但这却是进行JVM参数调优的重要基础!

大笑

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [jvm 理论 实践] 推荐:

JVM理论与实践【JVM参数调优】

- - ITeye博客
         在生产环境下对Java虚拟机JVM进行参数调优是必不可少的. 作为普通的开发人员,如果对JVM的参数优化有一定的了解,即使是从知识体系的完整性来考虑也是大有裨益的. 另外,了解JVM的运行原理也有助于编写性能良好的程序,而不是让代码成为服务器CPU和内存的杀手. 【Windows平台的参数调优】.

JVM理论与实践【堆内存结构与垃圾回收】

- - ITeye博客
        在生产环境下,通常都需要对JVM进行参数优化,其中对垃圾回收器的参数优化是一个非常重要的一方面. 下面重点介绍Java的堆内存,垃圾回收算法,常用的垃圾回收器以及Java堆内存的分配策略,这些内容将作为对JVM进行垃圾回收参数优化的重要基础. 然后通过简单示例验证Java的垃圾回收机制.

java 内存移到堆外!!! Jvm gcih 淘宝优化JVM实践

- - CSDN博客互联网推荐文章
出自Jvm  GC-Invisible Heap. GC-Invisible Heap,简称GCIH,是一种将Java对象从Java堆内移动到堆外,并且可以在JVM间共享这些对象的技术. GCIH顾名思义就是GC访问不到的堆,它是对JVM内存管理机制的一个有益的补充. 在某些特殊的应用中有大量生命周期很长的对象,在应用运行的整个过程中它们都存在,不需要被GC回收.

容器环境的JVM内存设置最佳实践 - JadePeng - 博客园

- -
Docker和K8S的兴起,很多服务已经运行在容器环境,对于java程序,JVM设置是一个重要的环节. 这里总结下我们项目里的最佳实践. 默认情况下,jvm自动分配的heap大小取决于机器配置,比如我们到一台64G内存服务器:. 可以看到,JVM 分配的最大MaxHeapSize为 16G,计算公式如下:.

JVM 内存分析工具 MAT 的深度讲解与实践——进阶篇

- - 掘金后端本月最热
注:本文原创,转发需标明作者及原文链接. 【0广告微信公众号:Q的博客】. 本系列共三篇文章, 本文是系列第2篇——进阶篇,详细讲解 MAT 各种工具的 核心功能、用法、适用场景,并在具体实战场景下讲解帮大家学习如何针对各类内存问题. JVM 内存分析工具 MAT 的深度讲解与实践——入门篇》 介绍 MAT 产品功能、基础概念、与其他工具对比、Quick Start 指南.

Hive Lock 理论与实践

- - leejun2005的个人页面
最近两天数据仓库中一张核心表遭遇了锁的问题,导致数据插入失败,影响挺大,之前一直没注意到这个问题,借此总结一下这块的知识和遇到的坑. hive 在 0.7 版本之后开始支持并发,线上的环境默认是用 zookeeper 做 hive 的锁管理,Hive开启并发功能的时候自动开启锁功能. hive 目前主要有两种锁,SHARED(共享锁 S)和 Exclusive(排他锁 X).

JVM研究

- - 开源软件 - ITeye博客
每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了. 我想Java做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢. JVM全称是Java Virtual Machine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中.

jvm调优

- - 互联网 - ITeye博客
printf "%x\n" 21742  找到耗时最长的进程. jstack pid | grep 54ee  定位某个类的方法. jstack 10535|grep -A 10 2a1d (最后十行). jmap 查询pid 内存线程. 附:TOP命令中需要关注的值:. (1)load average:此值反映了任务队列的平均长度;如果此值超过了CPU数量,则表示当前CPU数量不足以处理任务,负载过高.

【外刊IT评论网】理论与实践

- - 外刊IT评论
你每天都在仔细翻阅Hacker News,惊奇于那些经过整理的创业传奇、功效无比的最佳实践理论、天才的技术解决方案,以及各种链接指向的大量的一站式的,极简主义的,色彩柔和的网站. 你参加过精益(Lean)创业研讨会,读过了《Four Steps to the Epiphany》,订阅了硅谷产品类博客.

数据治理理论 + 实践

- - IT瘾-dev
数据治理无论是在数仓建设过程中还是数仓建设完成之后都是及其重要的,是数据部门基础建设的必经之路,是降本提效,形成企业数据资产的关键一环. 数据质量管理(Data Quality Management),是指对数据从计划、获取、存储、共享、维护、应用、消亡生命周期的每个阶段里可能引发的各类数据质量问题,进行识别、度量、监控、预警等一系列管理活动,并通过改善和提高组织的管理水平使得数据质量获得进一步提高.