java虚拟机(1)

1.J

ava

虚拟机

java

虚拟机是一个想象中的机器

,

在实际的计算机上通过软件模拟来实现。

java

虚拟机有自己想象中的硬件

,

如处理器、堆栈、寄存器

,

还具有相应的指令系统。

2.

Java

虚拟机体系结构

java

虚拟机由五个部分组成

:

一组指令集、一组寄存器、一个栈、一个无用单元收集堆

(garbage-collected-heap)

、一个方法区域。

这五部分是

java

虚拟机的逻辑成份

,

不依赖任何实现技术或组织方式

,

但它们的功能必须在真实机器上以某种方式实现。

3.

Java

虚拟机区别活动对象与垃圾的方法

Java

虚拟机可以有两种不同的方法来区别活动对象与垃圾:引用计数

(Reference Counting)

与跟踪

(Tracing)

3.1

采用

Reference Counting

的垃圾回收器

对于采用

Reference Counting

的垃圾回收器,系统为堆上每一个对象都维护一个计数器,当一个对象被创建并且别引用时,这个计数就被置为

1

。当有新的变量引用该对象,计数器进行自加运算。当一个引用超出作用范围或者被赋予新值的时候,计数器进行自减运算。引用计数为

0

的对象,会被作为垃圾回收。当一个对象被回收,该对象所引用的对象的引用计数都会相应减少,因而,一个对象的回收有时会引起其它对象的回收。

Reference Counting

方式的垃圾回收器,好处在于可以在很短的时间内运行

,不会长时间的中断普通的程序运行,因而在

RealTime

的系统中应用较为普遍。

Reference Counting

方式的垃圾回收器,问题在于无法识别循环引用,比如父类对象还有子类引用的情况,即便父类和子类都已经不再能被访问到

(unreachable)

,引用计数也把它们清除。另外一个问题是引用计数器的加减运算会增加系统的计算开销。

3.2

采用

Tracing

的垃圾回收器

采用

Tracing

的垃圾回收器,遍历由根节点

(root nodes)

出发的引用关系图。在遍历过程中遇到的对象,就被标记为活动。标记既可以是对应对象中的某一个标志,也可以是独立的位图中的标志。当遍历完成以后,那些没有被标记的对象,就被作为垃圾回收了。

最基本

Tracing

算法是

"Mark and Sweep"

垃圾回收器的另外一个责任是清除堆上的碎片

(Fragmentation)

。对于

Mark and Sweep

的垃圾回收器通常有两种实现方法来减少堆上的碎片:

压缩

(Compacting)

和拷贝

(Copying)

4.

基本回收算法

4.1

引用计数(

Reference Counting

)

比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为

0

的对象。此算法最致命的是无法处理循环引用的问题。

4.2

标记

-

清除(

Mark-Sweep

)

此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。

4.3

复制(

Copying

)

此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过出现

碎片

问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

4.4

标记-

整理(Mark-Compact

)

此算法结合了

标记

-

清除

复制

两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象

压缩

到堆的其中一块,按顺序排放。此算法避免了

标记

-

清除

的碎片问题,同时也避免了

复制

算法的空间问题。

4.5

增量收集(

Incremental Collecting

)

实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因

JDK5.0

中的收集器没有使用这种算法的。

4.6

分代(

Generational Collecting

)

基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从

J2SE1.2

开始)都是使用此算法的。

4.6.1

分代垃圾回收详述

Java

堆分可为

Young(

年轻代

)

Tenured(

年老代

)

Perm(

持久代

)

三个部分,下面是各代的分布情况。

l

Young

(年轻代)

年轻代分三个区。一个

Eden

区,两个

Survivor

。大部分对象在

Eden

区中生成。当

Eden

区满时,还存活的对象将被复制到

Survivor

区(两个中的一个),当这个

Survivor

区满时,此区的存活对象将被复制到另外一个

Survivor

区,当这个

Survivor

去也满了的时候,从第一个

Survivor

区复制过来的并且此时还存活的对象,将被复制

年老区

(Tenured)”

。需要注意,

Survivor

的两个区是对称的,没先后关系,所以同一个区中可能同时存在从

Eden

复制过来对象,和从前一个

Survivor

复制过来的对象,而复制到年老区的只有从第一个

Survivor

去过来的对象。而且,

Survivor

区总有一个是空的。

l

Tenured

(年老代)

年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。

l

Perm

(持久代)

用于存放静态文件,如今

Java

类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些

class

,例如

Hibernate

等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过

-XX:MaxPermSize=

进行设置。

4.6.2

分代垃圾回收演示过程:

5.

垃圾回收器

5.1

发生垃圾回收的两种类型:

Scavengc GC

Full GC

5.1.1

Scavenge GC

一般情况下,当新对象生成,并且在

Eden

申请空间失败时,就好触发

Scavenge GC

,堆

Eden

区域进行

GC

,清除非存活对象,并且把尚且存活的对象移动到

Survivor

区。然后整理

Survivor

的两个区。

5.1.2

Full GC

对整个堆进行整理,包括

Young

Tenured

Perm

Full

GC

Scavenge GC

要慢,因此应该尽可能减少

Full GC

。有如下原因可能导致

Full GC

l

Tenured

被写满

l

Perm

域被写满

l

System.gc()

被显示调用

l

上一次

GC

之后

Heap

的各域分配策略动态变化

5.2

使用的垃圾收集器种类

目前所使用的垃圾收集器主要有三种:串行收集器、并行收集器、并发收集器。

l

串行收集器

使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量(

100M

左右)情况下的多处理器机器上。可以使用

-XX:+UseSerialGC

打开。

l

并行收集器

对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用

-XX:+UseParallelGC.

打开。并行收集器在

J2SE5.0

第六

6

更新上引入,在

Java SE6.0

中进行了增强,可以对年老代进行并行收集。如果年老代不使用并发收集的话,是使用单线程进行垃圾回收,因此会制约扩展能力。使用

-XX:+UseParallelOldGC

打开。使用

-XX:ParallelGCThreads=

设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等。

此收集器可以进行如下配置:

最大垃圾回收暂停

:

指定垃圾回收时的最长暂停时间,通过

-XX:MaxGCPauseMillis=

指定。

为毫秒

.

如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。设定此值可能会减少应用的吞吐量。

吞吐量

:

吞吐量为垃圾回收时间与非垃圾回收时间的比值,通过

-XX:GCTimeRatio=

来设定,公式为

1/

(

1+N

)。例如,

-XX:GCTimeRatio=19

时,表示

5%

的时间用于垃圾回收。默认情况为

99

,即

1%

的时间用于垃圾回收。

l

并发收集器

可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用

-XX:+UseConcMarkSweepGC

打开。

并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。在每个年老代垃圾回收周期中,在收集初期并发收集器会对整个应用进行简短的暂停,在收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程中多个线程同时进行垃圾回收工作。

并发收集器使用处理器换来短暂的停顿时间。在一个

N

个处理器的系统上,并发收集部分使用

K/N

个可用处理器进行回收,一般情况下

1<=K<=N/4

在只有一个处理器的主机上使用并发收集器,设置为

incremental mode

模式也可获得较短的停顿时间。

浮动垃圾:由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了

“Floating Garbage”

,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要

20%

的预留空间用于这些浮动垃圾。

Concurrent

Mode Failure

:并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生

并发模式失败

,此时整个应用将会暂停,进行垃圾回收。

启动并发收集器:因为并发收集在应用运行时进行收集,所以必须保证收集完成之前有足够的内存空间供程序使用,否则会出现

“Concurrent Mode Failure”

。通过设置

-XX:CMSInitiatingOccupancyFraction=

指定还有多少剩余堆时开始执行并发收集