浅谈JVM的实现与垃圾回收机制

来源:互联网 发布:贴吧防秒删软件 编辑:程序博客网 时间:2024/05/02 06:48

Java被称为是一个人类可读的编程语言,其主要特点是基于类和面向对象,Java的开源版本被称为OpenJDK。Java编程环境由两个部分组成:Java语言和运行环境,运行环境也称为Java虚拟机(JVM),JVM是一个为执行Java程序提供运行时环境的程序。本文主要探讨JVM的实现机制。

什么是JVM

解释之前,先上一张图吓一下大家:

jvm arch

这张图中我们需要注意的是,JVM的核心组件包括三个部分:Heap, JIT Compiler, GC, 当我们要优化JVM的性能的时候主要调优的目标是这三个组件。

JVM负责解释执行字节码文件,所有平台上的JVM向编译器提供相同的编程接口,而编译器只需要面向虚拟机,生成虚拟机能理解的代码,然后由虚拟机来解释执行。JVM是一个为执行Java程序提供运行时环境的程序。没有JVM,Java程序也就不能执行。一个Java程序通常通过以下的方式执行:

java <arguments> <program name>

首先操作系统会启动JVM进程为Java程序提供运行时环境,然后会在刚启动的虚拟机中执行我们的Java程序。需要注意的是,Java程序首先要被转换(编译)成Java字节码(bytecode),JVM上运行的是Java字节码程序。JVM可以看成是一个JAVA字节码程序解释器。

Write Once, Run anywhere

当使用Java编译器编译Java程序时,生成的是与平台无关的字节码,这些字节码不面向任何具体平台,它只面向JVM。不同平台的JVM都是不同的,但它们都提供了相同的接口。JVM是Java程序跨平台的关键部分,只要为不同平台实现了相应的虚拟机,编译后的Java字节码就可以在该平台上运行。

JVM收集运行时信息以为如何执行代码做出更好的决策。这意味着JVM能够自动模拟和优化运行在它上面的程序。JVM在Java程序和操作系统间形成了一个中间层,使得开发者不用过多的处理和操作系统相关的具体细节问题。

JVM的另一个好处是,任何能够编译成字节码的语言都可以在它上面运行,不仅仅是Java,比如Groovy, Scala和Clojure这些基于JVM的语言。这也意味着,这些语言可以轻松的使用其他语言写的函数库。例如一个Scala开发者可以调用Java库,因为他们运行在相同的平台上。

从实际硬件上隔离出来,意味着Java代码就像一个沙盒,这可以避免一运行有害代码对机器硬件造成的伤害。安全性是JVM的主要好处之一。需要注意的是,并不是所有的JVM都是相同的,在Oracle的标准JVM实现标准之上还存在很多其他的实现方式。而我们主要讨论的是HotSport JVM,这也是OpenJDK和Oracle JVM的实现基础。

JIT - Just In Time

正如我们之前讨论的,JVM运行的是字节码。但是,如果有一段代码会被频繁的执行,那么JVM可以决定将这段代码编译为机器码(native code)以加快这段代码的执行速度。JIT可编译的最小执行块是一个方法。默认情况下,一个代码块需要被执行1500次才会被JIT编译,这个次数是可配置的。利用JIT机制可以有效的提升系统的性能,但是,JIT编译并不是无代价的,其需要耗费额外的系统资源和时间。

Java内存管理和GC

在Java中,对象所占用的内存在对象不再使用后会自动被回收。这些工作是由一个叫垃圾回收器Garbage Collector的进程完成的。相比较C/C++程序而言,你必须手动调用free()函数或delete操作符来回收内存。在这里我们主要讨论的是HotSpot JVM(这也是OpenJDK和Oracle Java的实现基础),事实上,还有很多其他的JVM实现方式。

GC的优缺点

好处是

  1. 开发者无需过问内存管理,可以专注于解决实际问题。虽然内存泄露仍有可能会发生,但发生的概率比较小。
  2. GC的智能算法可以在后台自动的进行内存管理,且这种管理在大多数时候是最佳的。

坏处是

  1. 当垃圾回收发生时,它会影响程序的性能,甚至会暂停程序的执行。这个被称为“Stop the world”垃圾回收机制,整个程序进程会被暂停以等待垃圾回收执行完。对某些应用而言,这可能是无法接受的。
  2. 开发者并不能指定何时或使用何种方法执行GC。

Generational GC

在了解更多关于GC的问题之前有必要理解Java内存中的堆区(Heap)事如何工作的。所有的对象都存在于堆中(与此对应的是栈,这里初访者变量和方法,以及堆中对象的引用)。垃圾回收的过程也就是将堆区中不再需要对象清除的过程。几乎所有的GCs都是“分代的(generational)”,也就是说堆区会划分成很多的区块,也称为代。Hotspot的堆结构如下图所示:

Hotspot Heap Structure

New/Young Generation

hypothesis

大多数的应用中持有的对象很大部分是短生命周期的,这被称为“Weak generational hypothesis”。在垃圾回收期间分析应用中所有的对象是一件缓慢而耗时的工作,因此可以将短生命周期的对象在其被创建时就分隔出来。因此New Generation可以进一步划分为:

  • Eden Space (Eden空间):所有的新创建的对象都存在与此。当其变满时,minor GC便会出现。然后所有仍然被引用的对象被移动到幸存者空间中。
  • Survivor Spaces (幸存者空间):对不同的JVM而言,幸存者空间的实现方式也不尽相同,但基本原理都是相同的。New Generation中的每一个GC都会增加幸存者空间中的对象年龄。如果对象的年龄超过某个特定值(默认情况下是15),该对象会被移往Old Generation。

New Generation中的GC也被称为minor GC。使用New Generation的好处是可以减少分片带来的影响。

Old Generation

任何从New Generation中的幸存者空间中幸存下来的对象会被送往Old Generation。Old Generation通常比New Generation大很多。存在于Old Generation中的GC也被称为Full GC。Full GC可以执行“Stop The World”机制,并且通常会占用更长的时间,因此Full GC也称为绝大不多的JVM可以进行优化的地方。

Permanent Generation

Permanent Generation用于存放类的元信息。在Java 8中其被metaspace所取代。通常Permanent Generation无需为了确保其有足够空间而被优化,但是当类没有被正确上传时,其仍有可能发生内存泄露的情况。

Java中的内存泄露

Java所支持的垃圾回收机制有效的减少了内存溢出的发生。内存溢出意味着当分配出去的内存却永远都没有被回收。虽然JVM能够自动回收那些没有被使用的对象所占用的内存,但事实上,Java中还是回发生内存溢出现象。假设存在一个有效的(但是没有被使用)对象引用指向一个没有被使用的对象,这会导致该对象一直占用着内存。

举例来说,当一个方法需要运行很长的时间(或者永远都在运行),方法中的局部变量可以在长时间的持有对象引用,而这远超出自己实际需要的时间。代码如下:

public static void main(String args[]) {int bigArray[] = new int[1000];int result = compute(bigArray);// We no longer need bigArray. It will get garbage collected when // there are no more references to it. Because bigArray is a local // variable, it refers to the array until this method returns. But // this method doesn't return. So we've got to explicitly get rid // of the referenceourselves, so the garbage collector knows it can // reclaim the array.bigArray = null;// Loop forever, handling the user's inputfor(;;) handle_input(result);}

内存泄露也会出现在你使用类似于HashMap这类数据结构时将一个对象与另一个对象相关联的情况。即使两个对象都不再被需要了,这种关联仍会存在于hash表中,除非hash表本身被垃圾回收器回收了,否则其中的关联对象会一直占用内存。如果hash表会运行相当长的时间的话,那么内存泄露便会发生。

System.gc() and finalize()

可以手动执行垃圾回收嘛?

这应该是个有意思的问题。答案是可以,也不可以。我们可以调用System.gc()方法建议JVM执行垃圾回收。然后,并没有任何保证说JVM一定会执行该操作。作为开发者,我们无法知晓JVM是否执行了我们的代码。并且,通常认为使用System.gc()是个很不明智的做法。

finalize()

finalize()方法存在于java.lang.Object类中,可以被所有对象所使用。默认情况下其不执行任何动作。当垃圾回收器确定了一个对象没有任何引用时,其会调用finalize()方法。但是,finalize方法并不一定会被执行,因此也不建议覆写finalize()该方法。

References

  1. Java In a Nutshell, 6th Edition
  2. http://java.dzone.com/articles/jvm-and-garbage-collection
  3. http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
from: http://wwsun.github.io/posts/jvm-gc.html
0 0