您的位置:首页 > 其它

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

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


什么是JVM

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



这张图中我们需要注意的是,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的优缺点

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

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


Generational GC

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




New/Young Generation



大多数的应用中持有的对象很大部分是短生命周期的,这被称为“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 input
for(;;) 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

Java In a Nutshell, 6th Edition http://java.dzone.com/articles/jvm-and-garbage-collection http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
from: http://wwsun.github.io/posts/jvm-gc.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息