JAVA深入学习之浅析JVM 内存

摘要:

主要是综合网上一些资料总结JVM内存结构,垃圾回收机制,收集一些java监控工具。

1. JVM内存模型

1.1 JVM内存结构

JVM内存由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:

clip_image001

1)栈

栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文。栈空间随着线程的终止而释放。StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的

2)堆

Java中堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等。

堆按照年代划分:分为新生代,青年代,老年代。

clip_image002

永久代:保存class,method,field对象,一般不参与垃圾回收。一般这部分空间不会溢出,除非一次性加载很多类。不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。

老年代:保存生命周期长的对象。当某些对象在新生代复制转移一定次数后,就会转移到此代。如果系统中用了app级别的缓存,缓存中的对象就会转移到这一区。

新生代:细分为三部分,Eden区和等分的Suivivor区。其中Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Young区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。

1.2 JVM参数设置

◆ Total Heap

-Xms :指定了JVM初始启动以后初始化内存

-Xmx:指定JVM堆得最大内存,在JVM启动以后,会分配-Xmx参数指定大小的内存给JVM,但是不一定全部使用,JVM会根据-Xms参数来调节真正用于JVM的内存。注:-Xmx -Xms之差就是三个Virtual空间的大小

◆ Young Generation

-XX:NewRatio=8  意味着tenured 和 young的比值8:1,这样eden+2*survivor=1/9堆内存

-XX:SurvivorRatio=32意味着eden和一个survivor的比值是32:1,这样一个Survivor就占Young区的1/34.

-Xmn 设置了年轻代的大小

◆ Perm Generation

-XX:PermSize=16M -XX:MaxPermSize=64M u Thread Stack

-XX:Xss=128K

注:对象数据放在堆中,方法运行在栈中。

1.3 各系统JVM堆大小调整

http://java.ccidnet.com/art/3737/20060427/531299_1.html

2. JAVA垃圾回收机制

2.1 JAVA垃圾回收算法

1)引用计数收集器

存储对特定对象的所有引用数,当某对象引用数为0,就可以进行垃圾收集

2) 跟踪收集器

跟踪算法是为了解决引用计数法的问题而提出,它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如对每个可达对象设置一个或多个位。在扫描识别过程中,基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器。

3) 标记-清除收集器

首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。

4)标记-压缩收集器

有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。

5) 复制收集器

这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。

6)增量收集器

增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。

7)分代收集器

这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。jvm生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。

8)并行收集器

并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高java应用程序的可扩展性

9)并发收集器

并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。

2.2 何时触发垃圾回收

1)Scavenge GC/minorGC

当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。大部分对象都是从Eden区开始的,Eden区不会分配的很大,Eden区的GC可以频繁进行。一般Eden使用速度快、效率高的算法,使其能尽快空闲出来。

2) Full GC

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC: 1)年老代(Tenured)被写满;2)永久代(Perm)被写满 ;3)System.gc()被显示调用 ;4)上一次GC之后Heap的各域分配策略动态变化

2.3 垃圾回收步骤

1)对象在Eden区完成内存分配

2)当Eden区满了,再创建对象,会因为申请不到空间,触发minorGC,进行young(eden+1survivor)区的垃圾回收

3)minorGC时,Eden不能被回收的对象被放入到空的survivor(Eden肯定会被清空),另一个survivor里不能被GC回收的对象也会被放入这个survivor,始终保证一个survivor是空的

4)当做第3步的时候,如果发现survivor满了,则这些对象被copy到old区,或者survivor并没有满,但是有些对象已经足够Old,也被放入Old区 XX:MaxTenuringThreshold

5)当Old区被放满的之后,进行fullGC

2.4 GC 参数设置

JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)。

1)串行GC

在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定

2)并行回收GC

在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数

3)并行GC

与旧生代的并发GC配合使用

旧生代的GC:

旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行 GC(SerialMSC)、并行GC(parallelMSC)和并发GC(CMS),具体算法细节还有待进一步深入研究。

以上各种GC机制是需要组合使用的,指定方式由下表所示:

clip_image003

 

3 编程技巧

1)将无用对象赋值为null.

2)重新为引用变量赋值。比如:

Person p = new Person("aaa");

p = new Person("bbb");

这样,new Person("aaa")这个对象就是垃圾了——符合垃圾回收条件了。

3)让相互联系的对象称为“岛”对象

Person p1 = new Person("aaa");

Person p2 = new Person("bbb");

Person p3 = new Person("ccc");

p1=p2; p2=p3; p3=p1;

p1=null; p2=null; p3=null;

在没有对p1、p2、p3置null之前,它们之间是一种三角恋关系。分别置null,三角恋关系依然存在,但是三个变量不在使用它们了。三个Person对象就组成了一个孤岛,最后死在堆上——被垃圾回收掉。

4)强制的垃圾回收System.gc()

实际上这里的强制,是程序员的意愿、建议,什么时候执行是JVM的垃圾回收器说了算。

调用垃圾回收也不一定能保证未使用的对象一定能从内存中删除。

唯一能保证的是,当你内存在极少的情况,垃圾回收器在程序抛出OutofMemaryException之前运行一次。

4 JVM内存观察工具

1) Jvmvisual 可装插件,方法级程序运行分析

2) jconsole 类似jvmvisual

3) jps 监控虚拟机进程状况

4) jstat 监控虚拟机各种运行状态信息

5) jinfo 实时查看和调整虚拟机参数

6) jmap 转储堆栈快照

7) jhat 分析虚拟机堆转存快照

8) jstack 堆栈跟踪

posted @ 2013-03-23 20:37  空城夕  阅读(259)  评论(0编辑  收藏  举报