Java垃圾收集机制

jopen 10年前
所谓“ java”,其实不只是一门语言,而是一种设计的美丽,更是一种关于世界观、关于架构之美的映射。
    一次,所谓的“面试”,让我开始正视 java的底层架构,一个我所未知的世界,一个我想了解的世界。所以,我开始结识到java虚拟机(JVM),以及java之中的垃圾收集机制。
一、所谓“JVM”
    既然我们时常说到“JVM(java虚拟机)”,那么所谓的java 虚拟机到底是什么吗?而,我认为他应该是这样的:
    java虚拟机,Java Virtual Machine,跟我们平常使用的Virtual PC或者VMware一样,是在操作系统上面运行的,一类模仿操作系统环境,虚构出来的计算机。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄 存器等,还具有相应的指令系统。不过,VMware等是属于系统虚拟机,而JVM是属于程序虚拟机。
    既然我们都说java虚拟机十分重要,那么他为什么这么重要呢?
    我们都曾弄过java的环境配置,知道,要从Oracle官网下载jdk(java的开发工具包)进行安装,知道jdk里面含有Jre(Java的运行环 境),知道我们要设置计算机的环境路径。但是,对于新手来说,可能不知道,我们做这些工作,其实都是在做一件事情——配置java的虚拟机。我们所编写的 java程序,经过编译之后,会生成可以在Java虚拟机上运行的字节码文件。因为每一个可以运行java程序的计算机,都拥有java虚拟机,拥有一样 的运行环境。所以,在一种平台下编译生成的字节码文件,便可以在不同的平台下运行。
     综上可知,JVM的优势是:屏蔽具体平台的信息,实现跨平台开发。
    一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。 Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以 在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
    显然,没有JVM,java将不会有今天这般的辉煌。那么,JVM具体上是如何工作的呢?
     JVM的工作:
    1、先通过调用jdk之中的java.exe,来让操作系统装载jvm。
    2、配置jvm的环境:
        1)创建JVM装载环境和配置

        2)装载JVM.dll

        3)初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例

        4)调用JNIENV实例装载并处理class类

    JVM在整个jdk中处于最底层的,负责操作系统的交互,用来屏蔽操作系统环境,提供一个完整的java运行环境。
    首先是将JVM装入环境,JVM提供的方式是操作系统的动态链接文件。既然是装载JVM的文件,那么操作系统会在你的path下面找到你的程序的 java.exe,再从里面通过各种调用寻找jvm.dll以及jvm.cfg,将其装载到操作系统,这样就可以在java中调用JVM的函数了。这些工 作做完之后就开始运行java程序了。
     Java程序有两种方式一种是jar包,一种是class.
    运行jar,Java -jar XXX.jar运行的时候
    1、Java.exe调用GetMainClassName函数,该函数先获得JNIEnv实例,然后调用Java类           Java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取 getAttributes(“Main-Class”)的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的 主类名作为运行的主类。
    2、之后main函数会调用Java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。main函数直接调用Java.c中LoadClass方法装载该类。
    如果是执行class方法。main函数直接调用Java.c中LoadClass方法装载该类。
二、java的垃圾收集机制
    既然,我们已经了解到了何为“JVM”,那么我们也要接着了解一下,JVM是如何进行平时的内存分配的?
    过去的语言(如C语言)要求程序员显示的分配内存、释放内存。程序在需要时分配内存,不需要时释放内存。但是这种做法常常会引起“ 内存泄漏”, 即是说,由于某种原因是分配的内存始终没有得到释放。如果该任务不断地重复,程序最终会耗尽内存并异常终止,至少无法正常的运行下去。相比之下,java 不要求程序员显式的分配内存和释放内存,避免了很多的潜在问题。java在创建对象时自动分配内存,并当该对象的引用不存在是释放这块内存。
   而这也就是,java垃圾回收机制,java相对于以前的语言的优势。
     java的垃圾收集机制,gc(garbage collection),是指JVM用于释放哪些不在会用的对象所占用的内存。,虽说,java语言并没有要求JVM有gc,也没有规定gc要如何进行工 作,但是,常用的JVM都有GC,而且,大多数gc都使用类似的算法管理内存,和执行收集操作。
    但是一般的,在java语言之中,判断一块内存空间是否符合 垃圾回收器标准的标准只有两个:
    (1)给对象赋予了控制null,以后再没有调试过。
    (2)给对象赋予了新值,即重新分配了内存空间。
    注:一块内存空间符合了垃圾回收器的收集标准,并不意味着这块内存控件就一定会被垃圾回收器收集。
    虽说,java是自动进行内存的分配与释放的。但是,我们也可以使用System.gc()方法做到强制执行。但是,并不保证每次调用 System.gc()方法就一定能够启动垃圾回收,他只不过会向jvm发出这样的一个申请,但是当地会不会真正执行垃圾回收,一切都是未知数。
    所以,我们可以在执行system.gc()前,先执行finalize()方法,对对象进行强制释放内存。虽说,在没有明确释放内存志愿的情况下,java也会隐形的调用finalize()方法,终止对象,以此来释放资源。

三、详解Java GC的工作原理:

1、 首先来看一下JVM内存结构,它是由堆、栈等部分组成,结构图如下所示。

Java垃圾收集机制

1)堆

    所有通过new创建的对象的内存都在堆中分配。显然,如果不对堆中的这些对象内存进行分类的话,是多而杂的。在不进行对象存活时间区分的情况下,每次垃圾 回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果 的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最 适合它的垃圾回收方式进行回收。

Java垃圾收集机制

    年轻代:

    所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制 过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时 存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空 的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

    年老代:

    在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

    持久代:

    用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设 置。

2)栈

    每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。

2. JVM垃圾回收机制

    针对上述的分类,GC有两种类型:Scavenge GC和Full GC。

    1)Scavenge GC

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

    2)Full GC
    对整个堆进行整理,包括Young、Tenured和Perm。因为需要对整个块进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。 在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

    (1) 年老代(Tenured)被写满

(2)持久代(Perm)被写满

(3)System.gc()被显示调用

(4)上一次GC之后Heap的各域分配策略动态变化

原文 :http://cyw.iteye.com/blog/2199665