欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM调优总结

程序员文章站 2022-06-01 12:51:53
...
数据类型

Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值
就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身
存放在这个引用值所表示的地址的位置。

基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress

引用类型包括:类类型,接口类型和数组。

堆与栈
堆和栈是程序运行的关键,很有必要把他们的关系说清楚。

栈是运行时的单位,而堆是存储的单位。

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、
放在哪儿。

为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?

第一,栈代表了处理逻辑,而堆代表了数据。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面
都有体现。

第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种
共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的
共享常量和缓存可以被所有栈访问,节省了空间。

第三,由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要
动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

第四,面向对象就是堆和栈的完美结合。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在
堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,
也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

在Java中,Main函数就是栈的起始点,也是程序的起始点。

程序要运行总是有一个起点的。无论什么java程序,找到main就找到了程序执行的入口:)

堆中存什么?栈中存什么?
堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以
动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:))。

为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是
基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有
什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且
都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象
本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时
的问题。

Java中的参数传递时传值呢?还是传引用?

Java中的参数传递时传值呢?还是传引用?

要说明这个问题,先要明确两点:
1. 不要试图与C进行类比,Java中没有指针的概念
2. 程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。
不会直接传对象本身。

可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),
基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,
如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。

堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。而堆是为栈进行数据存储
服务,说白了堆就是一块共享的内存。不过,正是因为堆和栈的分离的思想,才使得Java的垃圾回收成为
可能。

Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会
出现java.lang.*Error异常。常见的出现这个异常的是无法返回的递归,因为
此时栈中保存的信息都是方法返回的记录点。

Java对象的大小
基本数据的类型的大小是固定的,这里就不多说了。对于非基本类型的Java对象,其大小就值得商榷。
在Java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。

看下面语句:
Object ob = new Object();

这样在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。
4byte是上面部分所说的Java栈中保存引用的所需要的空间。而那8byte则是Java堆中对象的信息。
因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小
都必须是大于8byte。

Class NewObject {
    int count;
    boolean flag;
    Object ob;
}
 其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的
大小(4byte)=17byte。但是因为Java在对对象内存分配时都是以8的整数倍来分,因此大于
17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。

需要注意一下基本类型的包装类型的大小。因为这种包装类型已经成为对象了,因此需要把他们作为对象
来看待。包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何
有效信息,同时,因为Java对象大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。

这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),有些类型的内存占用更是夸张
(随便想下就知道了)。因此,可能的话应尽量少使用包装类。

在JDK5.0以后,因为加入了自动类型装换,因此,Java虚拟机会在存储方面进行相应的优化。

引用类型
对象引用类型分为强引用、软引用、弱引用和虚引用。

强引用:就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断
当前对象是否被强引用,如果被强引用,则不会被垃圾回收

软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的
剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果
剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,
是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

按照基本回收策略分

引用计数(Reference Counting):
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。
垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题

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

复制(Copying):
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,
把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,
同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,
就是需要两倍内存空间。

标记-整理(Mark-Compact):
此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

按分区对待的方式分

增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。
不知道什么原因JDK5.0中的收集器没有使用这种算法的。

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

按系统线程分

串行收集:串行收集使用单线程处理所有垃圾回收工作, 因为无需多线程交互,实现容易,而且效率比较高。
但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。当然,此收集器
也可以用在小数据量(100M左右)情况下的多处理器机器上。

并行收集:并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能
体现出并行收集器的优势。

并发收集:相对于串行收集和并行收集而言,前面两个在进行垃圾回收工作时,需要暂停整个运行环境,
而只有垃圾回收程序在运行,因此,系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。

如何区分垃圾
垃圾回收的起点是一些根对象(java栈, 静态变量, 寄存器...)。而最简单的Java栈就是Java程序执行
的main函数。这种回收方式,也是上面提到的“标记-清除”的回收方式


如何处理碎片
基本垃圾回收算法中,“复制”方式和“标记-整理”方式,都可以解决碎片的问题。

如何解决同时存在的对象创建和对象回收问题
这种方式有一个很明显的弊端,就是当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用
暂停的时间也会相应的增大。解决这种矛盾,有了并发垃圾回收算法,使用这种算法,垃圾回收线程与程序
运行线程同时运行。在这种方式下,解决了暂停的问题,但是因为需要在新生成对象的同时又要回收对象,
算法复杂性会大大增加,系统的处理能力也会相应降低,同时,“碎片”问题将会比较难解决。

为什么要分代
不同的对象的生命周期是不一样的。
有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接
挂钩,因此生命周期比较长。还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期
会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次
即可回收。

如何分代
虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和
持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集
要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

年轻代:
所有新生成的对象首先都是放在年轻代的。
年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区
满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象
将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且
此时还存活的对象,将被复制“年老区(Tenured)”。

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

持久代:
用于存放静态文件,如今Java类、方法等。

什么情况下触发垃圾回收

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。
GC有两种类型:Scavenge GC和Full GC。

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

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

有如下原因可能导致Full GC:
· 年老代(Tenured)被写满
· 持久代(Perm)被写满 
· System.gc()被显示调用 
·上一次GC之后Heap的各域分配策略动态变化

回收器选择
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,
所以这里的选择主要针对并行收集器和并发收集器。
默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。
JDK5.0以后,JVM会根据当前系统配置进行判断。

吞吐量优先的并行收集器
并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。

响应时间优先的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、
电信领域等。

调优总结
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。
在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以
并行进行,一般适合8CPU以上的应用。

年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话
持续时间等一些参数。

内存泄漏检查
内存泄漏一般可以理解为系统资源(各方面的资源,堆、栈、线程等)在错误使用的情况下,导致
使用完毕的资源无法回收(或没有回收),从而导致新的资源分配请求无法完成,引起系统错误。
内存泄漏对系统危害比较大,因为他可以直接导致系统的崩溃。
需要区别一下,内存泄漏和系统超负荷两者是有区别的,虽然可能导致的最终结果是一样的。内存泄漏是
用完的资源没有回收引起错误,而系统超负荷则是系统确实没有那么多资源可以分配了
(其他的资源都在使用)。

年老代堆空间被占满
异常: java.lang.OutOfMemoryError: Java heap space
这是最典型的内存泄漏方式,简单说就是所有堆空间都被无法回收的垃圾对象占满,虚拟机无法再分配新空间。

持久代被占满
异常:java.lang.OutOfMemoryError: PermGen space
说明:
Perm空间被占满。无法为新的class分配存储空间而引发的异常。这个异常以前是没有的,但是在
Java反射大量使用的今天这个异常比较常见了。主要原因就是大量动态反射生成的类不断被加载,
最终导致Perm区被占满。
解决:
-XX:MaxPermSize=16m
换用JDK。比如JRocket。

异常:java.lang.*Error
说明:这个就不多说了,一般就是递归没返回,或者循环调用造成

线程堆栈满
异常:Fatal: Stack size too small
说明:java中一个线程的空间大小是有限制的。JDK5.0以后这个值是1M。与这个线程相关的数据将会
保存在其中。但是当线程空间满了以后,将会出现上面异常。
解决:增加线程栈大小。-Xss2m。但这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部分。

系统内存被占满
说明:
这个异常是由于操作系统没有足够的资源来产生这个线程造成的。系统创建线程时,除了要在Java堆中分配
内存外,操作系统本身也需要分配资源来创建线程。因此,当线程数量大到一定程度以后,堆中或许还有
空间,但是操作系统分配不出资源来了,就出现这个异常了。

垃圾回收的悖论
目前JDK的垃圾回收算法,始终无法解决垃圾回收时的暂停问题,因为这个暂停严重影响了程序的相应时间,
造成拥塞或堆积。这也是后续JDK增加G1算法的一个重要原因。

我们的内存中都放了什么
内存中需要放的是你的应用需要在不久的将来再次用到到的东西。想想看,如果你在将来不用这些东西,
何必放内存呢?放文件、数据库不是更好?
1.系统运行时业务相关的数据。比如web应用中的session、即时消息的session等。这些数据一般在一个
用户访问周期或者一个使用过程中都需要存在。
2.缓存。缓存就比较多了,你所要快速访问的都可以放这里面。其实上面的业务数据也可以理解为一种缓存。
3.线程。

因此,我们是不是可以这么认为,如果我们不把业务数据和缓存放在JVM中,或者把他们独立出来,
那么Java应用使用时所需的内存将会大大减少,同时垃圾回收时间也会相应减少。

我认为这是可能的。

解决之道

数据库、文件系统
把所有数据都放入数据库或者文件系统,这是一种最为简单的方式。数据的获取都在每次请求时从数据库
和文件系统中获取。也可以理解为,一次业务访问以后,所有对象都可以进行回收了。
但是从应用角度来说,这种方式很低效。

内存-硬盘映射
数据库和文件系统都是实实在在进行了持久化,但是当我们并不需要这样持久化的时候,我们可以做一些
变通——把内存当硬盘使。
Java应用还是Java应用,他只知道读写的还是文件,但是实际上是内存。
Java应用与缓存两方面的好处。memcached的广泛使用也正是这一类的代表。

同一机器部署多个JVM
纵拆可以理解为把Java应用划分为不同模块,各个模块使用一个独立的Java进程。而横拆则是同样功能的
应用部署多个JVM。

程序控制的对象生命周期
线程分配
Java的阻塞式的线程模型基本上可以抛弃了,目前成熟的NIO框架也比较多了。阻塞式IO带来的问题是
线程数量的线性增长,而NIO则可以转换成为常数线程。因此,对于服务端的应用而言,NIO还是唯一选择。
不过,JDK7中为我们带来的AIO是否能让人眼前一亮呢?我们拭目以待。

JVM调优浅谈

 1.数据类型
    java虚拟机中,数据类型可以分为两类:基本类型和引用类型。

 基本类型的变量保存原始值,即:它代表的值就是数值本身,而引用类型的变量保存引用值。

 “引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。

   基本类型包括:byte、short、int、long、char、float、double、boolean

引用类型包括:类类型、接口类型和数组


2. 堆(heap)与栈(stack)
    在java中,Main函数就是栈的起始点,也是程序的起始点。程序要运行总是有一个起点的
(程序执行的入口)。

概括:   
 1  栈是运行时的单位 , 而堆是存储的单元。
 2  栈解决程序的运行问题,即程序如何执行,或者说如何处理数据, 堆解决的是数据存储的问题,
    即数据怎么放,放在哪儿。

在java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,
因此需要一个独立的线程栈。而堆则是所有线程共享的。


为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?

1. 从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。
分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

2.堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。
  好处:  a.提供了一种有效的数据交互方式(如:共享内存)
         b.堆中的共享常量和缓存可以被所有栈访问,节省了空间。

3. 栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。
 由于栈只能向上增长,因此就会限制住栈存储内容的能力,
  而堆不同,堆中的对象是可以根据需要动态增长的,
  因此栈和堆的拆分使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

4. 面向对象就是堆和栈的完美结合。
  我们在编写对象的时候,其实就是编写了数据结构,也编写了处理数据的逻辑。不得不承认,
面向对象的设计,确实很美。

 堆中存什么?栈中存什么?
1. 栈存储的信息都是跟当前线程(或程序)相关的信息。(局部变量、程序运行状态、方法、方法返回值)等,
 栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,
但是 在栈中,一个对象只对应了一个4byte的引用(堆栈分离的好处)。

2. 堆只负责存储对象信息。

 为什么不把基本类型放堆中呢?
 1. 其占用的空间一般是1~8个字节---需要空间比较少,
 2.而且因为是基本类型,所以不会出现动态增长的情况---长度固定,因此栈中存储就够了,
如果把它存在堆中是没有什么意义的。

 java中的参数传递是传值呢?还是传引用?
  对象传递是引用值传递,原始类型数据传递是值传递
  实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,所以还是按值传递
  堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。
  而堆是为栈进行数据存储服务的,说白了堆就是一块共享的内存。
  java中,栈的大小通过-Xss来设置,当栈中存储的数据比较多时,需要适当调大这个值,
  否则会出现 java.lang.*Error异常。

 java对象的大小如何计算?

 在java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。
看看下面语句:
 Object  ob = new  Object();
 这样在程序中完成了一个java对象的声明,但是它所占的空间为:4byte+8byte。
 (4byte是上面部分所说的java栈中保存引用的所需要空间,而那8byte则是java堆中对象的信息)。
因为所有的java非基本类型的对象都需要默认继承Object对象,因此不论什么样的java对象,
其大小都必须是大于8byte。
 但是因为java在对对象内存分配时都是以8的整数倍来分的,因此大于17byte的最接近8的整数倍的是24,
因此此对象的大小为24byte。
  包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,
   同时,因为java对象大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。
 这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),这些类型的内存占用更是夸张。
因此,可能的话应尽量少使用包装类。
  在JDK5.0以后,因为加入了自动类型装换,因此,java虚拟机会在存储方面进行相应的优化。

3. 引用类型
1. 强引用:我们一般声明对象时虚拟机生成的引用, 强引用环境下,垃圾回收时需要严格判断当前对象
是否被强引用,如果被强引用,则不会被垃圾回收。
 eg:  Sample sample = new Sample(); 
 创建一个对象,new出来的对象都是分配在java堆中的

 2.软引用:一般被作为缓存来使用。
 与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。

3.弱引用:弱引用与软引用类似,都是作为缓存来使用。
弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

4.虚引用 :
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。   

4.  垃圾回收算法
 a) 按照基本回收策略分
  a1. 引用计数(Reference Counting)
 比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。
垃圾回收时,只收集计数为0的对象。

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

a3. 复制(Copying)
此算法把内存空间划分为两个相等的区域,每次只是用其中一个区域。
垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。
 此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,
不会出现“碎片”问题。
 当然,此算法的缺点也是比较明显的,就是需要两倍内存空间。

 a4. 标记-整理(Mark-Compact)
  此算法结合了“标记-清除”和“复制”两个算法的优点。
也是分两个阶段,第一阶段从根节点开始标记所有被引用对象,
第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆中的其中一块,按顺序排放。
此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

b) 按分区对待的方式分
增量收集(Incremental Collecting):
 实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器
没有使用这种算法。

分代收集(Generational Collecting):
    基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年轻代、年老代、持久代,
    对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器
(从J2SE1.2开始)都是使用此算法的。

c) 按系统线程分
串行收集:
 使用单线程处理所有垃圾回收工作,因为无需多线程交互,实现容易,而且效率比较高。
 但是,其局限性也比较明显,即无法使用多处理器的优势,所以此收集适合单处理器机器。
 当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。

并行收集:
 并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。
而且理论上CPU数目越多,越能体现出并行收集器的优势。

 并发收集:
采用此收集器(如tenured generation),收集频率不能大,否则会影响到cpu的利用率,进而影响吞吐量。

 

上一篇: python基础语法小总结

下一篇: 插件