2008年6月1日星期日

[转]Oracle中的日期操作

一个月的第一天
SELECT to_date(to_char(SYSDATE,'yyyy-mm')||'-01','yyyy-mm-dd')
FROM dual

sysdate 为数据库服务器的当前系统时间。
to_char 是将日期型转为字符型的函数。
to_date 是将字符型转为日期型的函数,一般使用 yyyy-mm-dd hh24:mi:ss格式,当没有指定时间部分时,则默认时间为 00:00:00
dual 表为sys用户的表,这个表仅有一条记录,可以用于计算一些表达式,如果有好事者用 sys 用户登录系统,然后在 dual 表增加了记录的话,那么系统99.999%不能使用了。为什么使用的时候不用 sys.dual 格式呢,因为 sys 已经为 dual 表建立了所有用户均可使用的别名。

一年的第一天
SELECT to_date(
        to_char(SYSDATE,'yyyy')||'-01-01', 'yyyy-mm-dd'
        )
FROM dual

季度的第一天
SELECT to_date(
               to_char(SYSDATE,'yyyy-')||
               lpad(floor(to_number(to_char(SYSDATE,'mm'))/3)*3+1,2,'0')||
               '-01',
               'yyyy-mm-dd')
FROM dual

floor 为向下取整
lpad 为向左使用指定的字符扩充字符串,这个扩充字符串至2位,不足的补'0'。

当天的半夜
SELECT trunc(SYSDATE)+1-1/24/60/60
FROM dual

trunc 是将 sysdate 的时间部分截掉,即时间部分变成 00:00:00
Oracle中日期加减是按照天数进行的,所以 +1-1/24/60/60 使时间部分变成了 23:59:59。
Oracle 8i 中仅支持时间到秒,9i以上则支持到 1/100000000 秒。

上个月的最后一天
SELECT trunc(last_day(add_months(SYSDATE,-1)))+1-1/24/60/60
FROM dual

add_months 是月份加减函数。
last_day 是求该月份的最后一天的函数。

本年的最后一天
SELECT trunc(
      last_day(to_date(to_char(SYSDATE,'yyyy')||'-12-01','yyyy-mm-dd'))
     )+1-1/24/60/60
FROM dual

本月的最后一天
select trunc(last_day(sysdate))+1-1/24/60/60
from dual

本月的第一个星期一
SELECT next_day(
to_date(to_char(SYSDATE,'yyyy-mm')||'-01','yyyy-mm-dd'),
         '星期一'
         )
FROM dual

next_day 为计算从指定日期开始的第一个符合要求的日期,这里的'星期一'将根据NLS_DATE_LANGUAGE的设置稍有不同。

去掉时分秒
select trunc(sysdate)
from dual

显示星期几
SELECT to_char(SYSDATE,'Day')
FROM dual

取得某个月的天数
SELECT trunc(last_day(SYSDATE))-
       to_date(to_char(SYSDATE,'yyyy-mm')||'-01','yyyy-mm-dd')+
       1
FROM dual

判断是否闰年
SELECT decode(
       to_char(last_day(to_date(to_char(SYSDATE,'yyyy')||'-02-01','yyyy-mm-dd')),'dd'),
               '28','平年','闰年'
       )
FROM dual

一个季度多少天
SELECT last_day(to_date(
                       to_char(SYSDATE,'yyyy-')||
                       lpad(floor(to_number(to_char(SYSDATE,'mm'))/3)*3+3,2,'0')||
                       '-01','yyyy-mm-dd'
                       )
               )
       - 
       to_date(
               to_char(SYSDATE,'yyyy-')||
               lpad(floor(to_number(to_char(SYSDATE,'mm'))/3)*3+1,2,'0')||
               '-01','yyyy-mm-dd')
       +1
FROM dual

一个月的第一天
select trunc(sysdate, 'mm') from dual

一年的第一天
select trunc(sysdate, 'yy') from dual

[转帖] SQL Server各种日期计算方法(收藏)

通常,你需要获得当前日期和计算一些其他的日期,例如,你的程序可能需要判断一个月的第一天或者最后一天。你们大部分人大概都知道怎样把日期进行分割(年、月、日等),然后仅仅用分割出来的年、月、日等放在几个函数中计算出自己所需要的日期!在这篇文 章里,我将告诉你如何使用DATEADD和DATEDIFF函数来计算出在你的程序中可能你要用到的一些不同日期。
  在使用本文中的例子之前,你必须注意以下的问题。大部分可能不是所有例子在不同的机器上执行的结果可能不一样,这完全由哪一天是一个星期的第一天这个设置决定。第一天(DATEFIRST)设定决定了你的系统使用哪一天作为一周的第一天。所有以下的例 子都是以星期天作为一周的第一天来建立,也就是第一天设置为7。假如你的第一天设置不一样,你可能需要调整这些例子,使它和不同的第一天设置相符合。你可以通过@@DATEFIRST函数来检查第一天设置。
  为了理解这些例子,我们先复习一下DATEDIFF和DATEADD函数。DATEDIFF函数计算两个日期之间的小时、天、周、月、年等时间间隔总数。DATEADD函数计算一个日期通过给时间间隔加减来获得一个新的日期。要了解更多的DATEDI FF和DATEADD函数以及时间间隔可以阅读微软联机帮助。
  使用DATEDIFF和DATEADD函数来计算日期,和本来从当前日期转换到你需要的日期的考虑方法有点不同。你必须从时间间隔这个方面来考虑。比如,从当前日期到你要得到的日期之间有多少时间间隔,或者,从今天到某一天(比如1900-1-1)之间有多少时间间隔,等等。理解怎样着眼于时间间隔有助于你轻松的理解我的不同的日期计算例子。
一个月的第一天
  第一个例子,我将告诉你如何从当前日期去这个月的最后一天。请注意:这个例子以及这篇文章中的其他例子都将只使用DATEDIFF和DATEADD函数来计算我们想要的日期。每一个例子都将通过计算但前的时间间隔,然后进行加减来得到想要计算的日期。
  这是计算一个月第一天的SQL 脚本:
  SELECT DATEADD(mm, DATEDIFF(mm,0,getdate()), 0)
  我们把这个语句分开来看看它是如何工作的。最核心的函数是getdate(),大部分人都知道这个是返回当前的日期和时间的函数。下一个执行的函数DATEDIFF(mm,0,getdate())是计算当前日期和“1900-01-01 00:00:00.000”这个日期之间的月数。记住:时期和时间变量和毫秒一样是从“1900-01-01 00:00:00.000”开始计算的。这就是为什么你可以在DATEDIFF函数中指定第一个时间表达式为“0”。下一个函数是DATEADD,增加当前日期到“1900-01-01”的月数。通过增加预定义的日期“1900-01-01”和当前日期的月数,我们可以获得这个月的第一天。另外,计算出来的日期的时间部分将会是“00:00:00.000”。
  这个计算的技巧是先计算当前日期到“1900-01-01”的时间间隔数,然后把它加到“1900-01-01”上来获得特殊的日期,这个技巧可以用来计算很多不同的日期。下一个例子也是用这个技巧从当前日期来产生不同的日期。
本周的星期一
  这里我是用周(wk)的时间间隔来计算哪一天是本周的星期一。
  SELECT DATEADD(wk, DATEDIFF(wk,0,getdate()), 0)
一年的第一天
  现在用年(yy)的时间间隔来显示这一年的第一天。
  SELECT DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)
季度的第一天
  假如你要计算这个季度的第一天,这个例子告诉你该如何做。
  SELECT DATEADD(qq, DATEDIFF(qq,0,getdate()), 0)
当天的半夜
  曾经需要通过getdate()函数为了返回时间值截掉时间部分,就会考虑到当前日期是不是在半夜。假如这样,这个例子使用DATEDIFF和DATEADD函数来获得半夜的时间点。
  SELECT DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)
  深入DATEDIFF和DATEADD函数计算
  你可以明白,通过使用简单的DATEDIFF和DATEADD函数计算,你可以发现很多不同的可能有意义的日期。
  目前为止的所有例子只是仅仅计算当前的时间和“1900-01-01”之间的时间间隔数量,然后把它加到“1900-01-01”的时间间隔上来计算出日期。假定你修改时间间隔的数量,或者使用不同的时间间隔来调用DATEADD函数,或者减去时间间隔而不是增加,那么通过这些小的调整你可以发现和多不同的日期。
  这里有四个例子使用另外一个DATEADD函数来计算最后一天来分别替换DATEADD函数前后两个时间间隔。
上个月的最后一天
  这是一个计算上个月最后一天的例子。它通过从一个月的最后一天这个例子上减去3毫秒来获得。有一点要记住,在Sql Server中时间是精确到3毫秒。这就是为什么我需要减去3毫秒来获得我要的日期和时间。
  SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate()), 0))
  计算出来的日期的时间部分包含了一个Sql Server可以记录的一天的最后时刻(“23:59:59:997”)的时间。
去年的最后一天
  连接上面的例子,为了要得到去年的最后一天,你需要在今年的第一天上减去3毫秒。
  SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0))
本月的最后一天
  现在,为了获得本月的最后一天,我需要稍微修改一下获得上个月的最后一天的语句。修改需要给用DATEDIFF比较当前日期和“1900-01-01”返回的时间间隔上加1。通过加1个月,我计算出下个月的第一天,然后减去3毫秒,这样就计算出了这个月的最后一天。这是计算本月最后一天的SQL脚本。
  SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0))
本年的最后一天
  你现在应该掌握这个的做法,这是计算本年最后一天脚本
  SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate())+1, 0))。
本月的第一个星期一
  好了,现在是最后一个例子。这里我要计算这个月的第一个星期一。这是计算的脚本。
  select DATEADD(wk, DATEDIFF(wk,0,
  dateadd(dd,6-datepart(day,getdate()),getdate())
  ), 0)
  在这个例子里,我使用了“本周的星期一”的脚本,并作了一点点修改。修改的部分是把原来脚本中“getdate()”部分替换成计算本月的第6天,在计算中用本月的第6天来替换当前日期使得计算可以获得这个月的第一个星期一。
  总结
  我希望这些例子可以在你用DATEADD和DATEDIFF函数计算日期时给你一点启发。通过使用这个计算日期的时间间隔的数学方法,我发现为了显示两个日期之间间隔的有用历法是有价值的。注意,这只是计算出这些日期的一种方法。要牢记,还有很多方法 可以得到相同的计算结果。假如你有其他的方法,那很不错,要是你没有,我希望这些例子可以给你一些启发,当你要用DATEADD和DATEDIFF函数计算你程序可能要用到的日期时。
附录,其他日期处理方法
  1)去掉时分秒
  declare @ datetime
  set @ = getdate() --'2003-7-1 10:00:00'
  SELECT @,DATEADD(day, DATEDIFF(day,0,@), 0)
  2)显示星期几
  select datename(weekday,getdate())
  3)如何取得某个月的天数
  declare @m int
  set @m=2 --月份
  select datediff(day,'2003-'+cast(@m as varchar)+'-15' ,'2003-'+cast(@m+1 as varchar)+'-15')
  另外,取得本月天数
  select datediff(day,cast(month(GetDate()) as varchar)+'-'+cast(month(GetDate()) as varchar)+'-15' ,cast(month(GetDate()) as varchar)+'-'+cast(month(GetDate())+1 as varchar)+'-15')
  或者使用计算本月的最后一天的脚本,然后用DAY函数区最后一天
  SELECT Day(dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0)))
  4)判断是否闰年:
  SELECT case day(dateadd(mm, 2, dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)))) when 28 then '平年' else '闰年' end
  或者
  select case datediff(day,datename(year,getdate())+'-02-01',dateadd(mm,1,datename(year,getdate())+'-02-01'))
  when 28 then '平年' else '闰年' end
  5)一个季度多少天
  declare @m tinyint,@time smalldatetime
  select @m=month(getdate())
  select @m=case when @m between 1 and 3 then 1
  when @m between 4 and 6 then 4
  when @m between 7 and 9 then 7
  else 10 end
  select @time=datename(year,getdate())+'-'+convert(varchar(10),@m)+'-01'
  select datediff(day,@time,dateadd(mm,3,@time))

Java的自动装箱与拆箱

在JDK 1.5之前,只能往集合类中存放对象。基本类型的数据只能先包装成包装类对象才能放进去。在JDK 1.5中引入了自动装箱和拆箱的功能。

Integer i = 100;

相当于编译器自动为您作以下的语法编译:

Integer i = new Integer(100);

所以自动装箱与拆箱的功能是所谓的“编译器蜜糖”(Compiler Sugar),虽然使用这个功能很方便,但在程序运行阶段您得了解Java的语义。例如下面的程序是可以通过编译的:

Integer i = null;
int j = i;

这样的语法在编译时期是合法的,但是在运行时期会有错误,因为这种写法相当于:

Integer i = null;
int j = i.intValue();

null表示i没有参考至任何的对象实体,它可以合法地指定给对象参考名称。由于实际上i并没有参考至任何的对象,所以也就不可能操作intValue()方法,这样上面的写法在运行时会出现NullPointerException错误。

自动装箱、拆箱的功能提供了方便性,但隐藏了一些细节,所以必须小心。

那么自动装箱的原理是什么呢?

《使用JProfiler剖析Java Application中对象的分配和回收(gc)》一文中,原先的代码是:

package my;

import java.io.IOException;
import java.util.ArrayList;

public class Test {

    public static void main(String[] args) throws IOException {
        char ch=' ';
        while(ch!='n')
            ch=(char)System.in.read();
        for(int i=0;i<1000;i++)
            test();
        ch=' ';
        while(ch!='n')
            ch=(char)System.in.read();
    }

    public static void test(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        for(int i=0;i<10000;i++)
            arr.add((int)(Math.random()*100));
        //arr.clear();
        //arr=null;
    }
}

后来发现,程序运行结束以后总有256个Integer对象不能回收,百思不得其解。后来反编译了代码,看到实际上是编译器自动完成了将基本类型数据变成包装类对象的操作。下面是反编译的结果,注意红色的部分是编译器做的工作。

package my;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

public class Test
{
  public static void main(String[] args)
    throws IOException
  {
    char ch = ' ';
    while (ch != 'n')
      ch = (char)System.in.read();
    for (int i = 0; i < 1000; ++i)
      test();
    ch = ' ';
    while (ch != 'n')
      ch = (char)System.in.read();
  }

  public static void test()
  {
    ArrayList arr = new ArrayList();
    for (int i = 0; i < 10000; ++i)
      arr.add(Integer.valueOf((int)(Math.random() * 100.0D)));
  }
}

而这个valueOf()方法的源代码如下:

/**
     * Returns a <tt>Integer</tt> instance representing the specified
     * <tt>int</tt> value.
     * If a new <tt>Integer</tt> instance is not required, this method
     * should generally be used in preference to the constructor
     * {@link #Integer(int)}, as this method is likely to yield
     * significantly better space and time performance by caching
     * frequently requested values.
     *
     * @param  i an <code>int</code> value.
     * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
    final int offset = 128;
    if (i >= -128 && i <= 127) { // must cache
        return IntegerCache.cache[i + offset];
    }
        return new Integer(i);
    }

IntegerCache是Integer类的内部类

private static class IntegerCache {
    private IntegerCache(){}

    static final Integer cache[] = new Integer[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
        cache[i] = new Integer(i - 128);
    }
    }

当要包装的数值在-128-+127之间时,就会产生256个Integer对象,并返回其中的一个。如果在此范围之外,则直接返回一个Integer对象。这样就不难理解为什么最后有256个不能回收的Integer对象了。

带来的问题:

下面的三个程序输出各是什么?

public class AutoBoxDemo {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        if (i1 == i2)
            System.out.println("i1 == i2");
        else
            System.out.println("i1 != i2");
    }
}

答案:i1==i2  (数值在-128到127之间,自动装箱返回的是IntegerCache中的引用,因此是同一个对象)

public class AutoBoxDemo {
    public static void main(String[] args) {
        Integer i1 = 200;
        Integer i2 = 200;
        if (i1 == i2)
            System.out.println("i1 == i2");
        else
            System.out.println("i1 != i2");
    }
}

答案:i1!=i2 (数值超出-128到127这个范围,直接new一个对象返回,数值相等,但是对象不是同一个)

public class AutoBoxDemo {
    public static void main(String[] args) {
        Integer i1 = 200;
        Integer i2 = 200;
        if (i1.equals(i2))
            System.out.println("i1 == i2");
        else
            System.out.println("i1 != i2");
    }
}

答案:i1==i2 (数值超出-128到127这个范围,直接new一个对象返回,数值相等,用equals比较当然是相等的)

再次提醒:对象的相等性比较应该用equals,==进行的是同一性比较。

使用JProfiler剖析Java Application中对象的分配和回收(gc)

JProfiler是一款Java的性能监控工具。可以查看当前应用的对象、对象引用、内存、CPU使用情况、线程、线程运行情况(阻塞、等待等),同时可以查找应用内存使用得热点,即:哪个对象占用的内存比较多;或者CPU热点,即:哪儿方法占用的较大的CPU资源。

下面结合一个简单的例子说明Jprofiler如何使用

package my;

import java.io.IOException;
import java.util.ArrayList;

public class Test {

    public static void main(String[] args) throws IOException {
       char ch=' ';
        while(ch!='n')
            ch=(char)System.in.read();

        for(int i=0;i<1000;i++)
            test();
        ch=' ';
        while(ch!='n')
            ch=(char)System.in.read();

    }

    public static void test(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        for(int i=0;i<10000;i++)
            arr.add(new Integer((int)(Math.random()*100)));
        //arr.clear();
        //arr=null;

    }
}

代码中红色的部分是为了停下来对JProfiler进行操作和观察结果而设置的。

经过实验,发现蓝色部分加上和不加结果是一样的。因为arr是局部变量,test()运行结束时其生命周期也结束了,arr所引用的对象也被回收。因此也没必要设置为null。

1、启动JProfiler,选择local application

1

2、进入下一步,设置参数

2

3、确认,在下面的对话框中也点OK确认

3

Profiling Setting

5

系统中已存在的对象

4、设置要记录的数据

6

记录对象分配情况

7

记录对类的跟踪数据

8

9

在类跟踪选项中添加要跟踪的类

5、开始测试

10

在Concole窗口中按n(代码里边要求的),进入下一步对象的分配和释放阶段

6、测试程序段结束,观察结果

11

运行结束时,有42470个Integer对象尚未回收

12

所关注的类的实例数量变化情况,下降的地方是gc起作用了

13

GC在运行过程中,每一时刻回收的对象数量。(GC不是一直运行的,要在JVM的内存不够时才运行)

14

所记录的对象的数量变化情况

15

堆内存分配变化情况

7、按F4执行gc,观察结果

在第6步看到,运行结束时,有42470个Integer对象尚未回收。现在按F4执行gc

16

Integer对象全部被回收

17

18

19

20

图中的小的尖峰就是对之前未回收的Integer对象进行回收时产生的数据。

[转]java垃圾收集算法(http://www.javaeye.com/topic/144859)

1.垃圾收集算法的核心思想

  Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。

  垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。

2.触发主GC(Garbage Collector)的条件

  JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大。更值得关注的是主GC的触发条件,因为它对系统影响很明显。总的来说,有两个条件会触发主GC:

  ①当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。

  ②Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。

  由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。

3.减少GC开销的措施

  根据上述GC的机制,程序的运行会直接影响系统环境的变化,从而影响GC的触发。若不针对GC的特点进行设计和编码,就会出现内存驻留等一系列负面影响。为了避免这些影响,基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。具体措施包括以下几个方面:

  (1)不要显式调用System.gc()

  此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。

  (2)尽量减少临时对象的使用

  临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

  (3)对象不用时最好显式置为Null

  一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。

  (4)尽量使用StringBuffer,而不用String来累加字符串(详见blog另一篇文章JAVA中String与StringBuffer)

  由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。

  (5)能用基本类型如Int,Long,就不用Integer,Long对象

  基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

  (6)尽量少用静态对象变量

  静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

  (7)分散对象创建或删除的时间

  集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

4.gc与finalize方法

  ⑴gc方法请求垃圾回收

  使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。需要注意的是,调用System.gc()也仅仅是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。

  ⑵finalize方法透视垃圾收集器的运行

  在JVM垃圾收集器收集一个对象之前 ,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止化该对象释放资源,这个方法就是finalize()。它的原型为:

  protected void finalize() throws Throwable

  在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。

  因此,当对象即将被销毁时,有时需要做一些善后工作。可以把这些操作写在finalize()方法里。

java 代码

  1. protected void finalize()    
  2.   {    
  3. // finalization code here 
  4.   } 

⑶代码示例

java 代码

  1. class Garbage{    
  2. int index;    
  3. static int count;    
  4.   Garbage() {    
  5.   count++;    
  6.   System.out.println("object "+count+" construct");    
  7.   setID(count);    
  8.   }    
  9. void setID(int id) {    
  10.   index=id;    
  11.   }    
  12. protected void finalize() //重写finalize方法 
  13.   {    
  14.   System.out.println("object "+index+" is reclaimed");    
  15.   }    
  16. public static void main(String[] args)    
  17.   {    
  18. new Garbage();    
  19. new Garbage();    
  20. new Garbage();    
  21. new Garbage();    
  22.   System.gc(); //请求运行垃圾收集器 
  23.   }    
  24.  } 

5.Java 内存泄漏
  由于采用了垃圾回收机制,任何不可达对象(对象不再被引用)都可以由垃圾收集线程回收。因此通常说的Java 内存泄漏其实是指无意识的、非故意的对象引用,或者无意识的对象保持。无意识的对象引用是指代码的开发人员本来已经对对象使用完毕,却因为编码的错误而意外地保存了对该对象的引用(这个引用的存在并不是编码人员的主观意愿),从而使得该对象一直无法被垃圾回收器回收掉,这种本来以为可以释放掉的却最终未能被释放的空间可以认为是被“泄漏了”。

  考虑下面的程序,在ObjStack类中,使用push和pop方法来管理堆栈中的对象。两个方法中的索引(index)用于指示堆栈中下一个可用位置。push方法存储对新对象的引用并增加索引值,而pop方法减小索引值并返回堆栈最上面的元素。在main方法中,创建了容量为64的栈,并64次调用push方法向它添加对象,此时index的值为64,随后又32次调用pop方法,则index的值变为32,出栈意味着在堆栈中的空间应该被收集。但事实上,pop方法只是减小了索引值,堆栈仍然保持着对那些对象的引用。故32个无用对象不会被GC回收,造成了内存渗漏。

java 代码

  1. public class ObjStack {    
  2. private Object[] stack;    
  3. private int index;    
  4.   ObjStack(int indexcount) {    
  5.   stack = new Object[indexcount];    
  6.   index = 0;    
  7.   }    
  8. public void push(Object obj) {    
  9.   stack[index] = obj;    
  10.   index++;    
  11.   }    
  12. public Object pop() {    
  13.   index--;    
  14. return stack[index];    
  15.   }    
  16.   }    
  17. public class Pushpop {    
  18. public static void main(String[] args) {    
  19. int i = 0;    
  20.   Object tempobj;    
  21. //new一个ObjStack对象,并调用有参构造函数。分配stack Obj数组的空间大小为64,可以存64个对象,从0开始存储
  22.   ObjStack stack1 = new ObjStack(64);   
  23. while (i < 64)    
  24.   {    
  25.   tempobj = new Object();//循环new Obj对象,把每次循环的对象一一存放在stack Obj数组中。 
  26.   stack1.push(tempobj);    
  27.   i++;    
  28.   System.out.println("第" + i + "次进栈" + "\t");    
  29.   }    
  30. while (i > 32)    
  31.   {    
  32.   tempobj = stack1.pop();//这里造成了空间的浪费。 
  33. //正确的pop方法可改成如下所指示,当引用被返回后,堆栈删除对他们的引用,因此垃圾收集器在以后可以回收他们。 
  34. /* 
  35.   * public Object pop() {index - -;Object temp = stack [index];stack [index]=null;return temp;} 
  36.   */
  37.   i--;    
  38.   System.out.println("第" + (64 - i) + "次出栈" + "\t");    
  39.   }    
  40.   }    
  41.   } 

6.如何消除内存泄漏

  虽然Java虚拟机(JVM)及其垃圾收集器(garbage collector,GC)负责管理大多数的内存任务,Java软件程序中还是有可能出现内存泄漏。实际上,这在大型项目中是一个常见的问题。避免内存泄漏的第一步是要弄清楚它是如何发生的。本文介绍了编写Java代码的一些常见的内存泄漏陷阱,以及编写不泄漏代码的一些最佳实践。一旦发生了内存泄漏,要指出造成泄漏的代码是非常困难的。因此本文还介绍了一种新工具,用来诊断泄漏并指出根本原因。该工具的开销非常小,因此可以使用它来寻找处于生产中的系统的内存泄漏。

  垃圾收集器的作用

  虽然垃圾收集器处理了大多数内存管理问题,从而使编程人员的生活变得更轻松了,但是编程人员还是可能犯错而导致出现内存问题。简单地说,GC循环地跟踪所有来自“根”对象(堆栈对象、静态对象、JNI句柄指向的对象,诸如此类)的引用,并将所有它所能到达的对象标记为活动的。程序只可以操纵这些对象;其他的对象都被删除了。因为GC使程序不可能到达已被删除的对象,这么做就是安全的。

  虽然内存管理可以说是自动化的,但是这并不能使编程人员免受思考内存管理问题之苦。例如,分配(以及释放)内存总会有开销,虽然这种开销对编程人员来说是不可见的。创建了太多对象的程序将会比完成同样的功能而创建的对象却比较少的程序更慢一些(在其他条件相同的情况下)。

  而且,与本文更为密切相关的是,如果忘记“释放”先前分配的内存,就可能造成内存泄漏。如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存,这是因为自动化的垃圾收集器无法证明这些对象将不再使用。正如我们先前所说的,如果存在一个对对象的引用,对象就被定义为活动的,因此不能删除。为了确保能回收对象占用的内存,编程人员必须确保该对象不能到达。这通常是通过将对象字段设置为null或者从集合(collection)中移除对象而完成的。但是,注意,当局部变量不再使用时,没有必要将其显式地设置为null。对这些变量的引用将随着方法的退出而自动清除。

  概括地说,这就是内存托管语言中的内存泄漏产生的主要原因:保留下来却永远不再使用的对象引用。

  典型泄漏

  既然我们知道了在Java中确实有可能发生内存泄漏,就让我们来看一些典型的内存泄漏及其原因。

  全局集合

  在大的应用程序中有某种全局的数据储存库是很常见的,例如一个JNDI树或一个会话表。在这些情况下,必须注意管理储存库的大小。必须有某种机制从储存库中移除不再需要的数据。

  这可能有多种方法,但是最常见的一种是周期性运行的某种清除任务。该任务将验证储存库中的数据,并移除任何不再需要的数据。

  另一种管理储存库的方法是使用反向链接(referrer)计数。然后集合负责统计集合中每个入口的反向链接的数目。这要求反向链接告诉集合何时会退出入口。当反向链接数目为零时,该元素就可以从集合中移除了。

  缓存

  缓存是一种数据结构,用于快速查找已经执行的操作的结果。因此,如果一个操作执行起来很慢,对于常用的输入数据,就可以将操作的结果缓存,并在下次调用该操作时使用缓存的数据。

  缓存通常都是以动态方式实现的,其中新的结果是在执行时添加到缓存中的。典型的算法是:

  检查结果是否在缓存中,如果在,就返回结果。

  如果结果不在缓存中,就进行计算。

  将计算出来的结果添加到缓存中,以便以后对该操作的调用可以使用。

  该算法的问题(或者说是潜在的内存泄漏)出在最后一步。如果调用该操作时有相当多的不同输入,就将有相当多的结果存储在缓存中。很明显这不是正确的方法。

  为了预防这种具有潜在破坏性的设计,程序必须确保对于缓存所使用的内存容量有一个上限。因此,更好的算法是:

  检查结果是否在缓存中,如果在,就返回结果。

  如果结果不在缓存中,就进行计算。

  如果缓存所占的空间过大,就移除缓存最久的结果。

  将计算出来的结果添加到缓存中,以便以后对该操作的调用可以使用。

  通过始终移除缓存最久的结果,我们实际上进行了这样的假设:在将来,比起缓存最久的数据,最近输入的数据更有可能用到。这通常是一个不错的假设。

  新算法将确保缓存的容量处于预定义的内存范围之内。确切的范围可能很难计算,因为缓存中的对象在不断变化,而且它们的引用包罗万象。为缓存设置正确的大小是一项非常复杂的任务,需要将所使用的内存容量与检索数据的速度加以平衡。

  解决这个问题的另一种方法是使用java.lang.ref.SoftReference类跟踪缓存中的对象。这种方法保证这些引用能够被移除,如果虚拟机的内存用尽而需要更多堆的话。

  ClassLoader

  Java ClassLoader结构的使用为内存泄漏提供了许多可乘之机。正是该结构本身的复杂性使ClassLoader在内存泄漏方面存在如此多的问题。ClassLoader的特别之处在于它不仅涉及“常规”的对象引用,还涉及元对象引用,比如:字段、方法和类。这意味着只要有对字段、方法、类或ClassLoader的对象的引用,ClassLoader就会驻留在JVM中。因为ClassLoader本身可以关联许多类及其静态字段,所以就有许多内存被泄漏了。

  确定泄漏的位置

  通常发生内存泄漏的第一个迹象是:在应用程序中出现了OutOfMemoryError。这通常发生在您最不愿意它发生的生产环境中,此时几乎不能进行调试。有可能是因为测试环境运行应用程序的方式与生产系统不完全相同,因而导致泄漏只出现在生产中。在这种情况下,需要使用一些开销较低的工具来监控和查找内存泄漏。还需要能够无需重启系统或修改代码就可以将这些工具连接到正在运行的系统上。可能最重要的是,当进行分析时,需要能够断开工具而保持系统不受干扰。

  虽然OutOfMemoryError通常都是内存泄漏的信号,但是也有可能应用程序确实正在使用这么多的内存;对于后者,或者必须增加JVM可用的堆的数量,或者对应用程序进行某种更改,使它使用较少的内存。但是,在许多情况下,OutOfMemoryError都是内存泄漏的信号。一种查明方法是不间断地监控GC的活动,确定内存使用量是否随着时间增加。如果确实如此,就可能发生了内存泄漏。

Java内存泄漏初探(2)

第二个内存泄漏的例子是:没有正确实现pop方法的ObjStack

class ObjStack
{
private Object[] stack;
private int index;
public void push(Object o)
{
stack[index] = o;
index++;
}
public Object pop()
{
index-;
return stack[index];
}
//...
}


现在创建一个容量为10的对象,然后调用8次push方法向它添加对象,那么此时索引值为8。现在考虑三次调用pop方法后发生什么?此时的索引值为5,但是请注意,除了这个索引值发生变化外堆栈其实没有其它任何变化!虽然pop方法减小了索引值,但是实际上堆栈仍然保持着对那些对象的引用。调用pop方法往往意味着那些对象应该被收集(大多情况是如此的,即使不是马上,也是在稍后使用完该对象后)。然而由于堆栈仍然保留有对该对象的引用,它就不能被收集。这些对象只能在调用push后被替换才可能被收集

正确的pop的实现如下:


public Object pop()
{
index-;
Object o = stack[index];
stack[index] = null;
return o;
}


在这个版本的pop方法中,当引用被返回后,堆栈删除对他们的引用因此垃圾收集器在以后可以回收他们。

同样地,对于不正确的pop()方法,我在JProfiler中进行了测试

public class Pushpop {
    public static void main(String[] args) {
        int i = 0;
        Object tempobj;
        // new一个ObjStack对象,并调用有参构造函数。分配stack Obj数组的空间大小为64,可以存64个对象,从0开始存储
        ObjStack stack1 = new ObjStack(64);
        // 循环new Obj对象,把每次循环的对象一一存放在stack Obj数组中。
        while (i < 64) {
            tempobj = new Object();
            stack1.push(tempobj);
            i++;
            System.out.println("第" + i + "次进栈" + "\t");
        }
        //==========A===================
        while (i > 32) {
            tempobj = stack1.pop();// 这里造成了空间的浪费。
            i--;
            System.out.println("第" + (64 - i) + "次出栈" + "\t");
        }
        //==========B===================
        stack1=null;
        //==========C===================
    }
}

在A点和B点,Obejct对象数量都是64个,原因就是数组仍然维持着对于对象的引用。当执行stack1=null之后,在C点,在Jprofiler中按F4进行gc,会发现Object数量降为0,对象均被回收。

Java内存泄漏初探(1)

进来编写的一个程序在长时间运行后似乎会停止响应,想到了是否是内存泄漏的缘故。之前没有好好研究过Java内存泄漏的问题,所以首先搜索了一下网络,结合Jprofiler工具,想先对Java Application的内存泄漏问题作一下研究。

通过搜索,网上所举的关于内存泄漏的问题其中一个为:

释放不用的对象:参见《java内存泄漏》,了解JVM的工作原理及机制规范;
ArrayList al = new ArrayList();
for(int i = 0;i<200;i++){
Object o = new Object();
o.name = 1;
o.id = 1;
al.add(o);
}
以上代码中o仍被al引用,没有释放,不会被回收。因此需修正:
Arraylist al = new Arraylist();
for(int i = 0;i<200;i++){
Object o = new Object();
o.name = 1;
o.id = 1;
al.add(o);
o = null;
}

注意上面红色字体部分是错误的。而修正中的 o=null; 更是画蛇添足。因为o本来就是一个局部变量,每次循环时都会去引用一个新的对象,而且再离开复合语句时o就不存在了,但是由于对象被加入了集合ArrayList中,因此对象还是被引用的,并不会被回收。

为了在JProfiler中查看对象分配和回收的情况,我写了下面的两个类

package my;

public class MyObject {
    public int name;
    public int id;
}

package my;

import java.io.IOException;
import java.util.ArrayList;

public class Test2 {
    public static void main(String[] args) throws IOException {
        char ch = ' ';
        while (ch != 'y')
            ch = (char) System.in.read();
        ArrayList<MyObject> al = new ArrayList<MyObject>();
        for (int i = 0; i < 200; i++) {
            MyObject o = new MyObject();
            o.name = 1;
            o.id = 1;
            al.add(o);
           o = null;
        }
        ch = ' ';
        while (ch != 'n')
            ch = (char) System.in.read();
        al=null;
        ch = ' ';
        while (ch != 'n')
            ch = (char) System.in.read();
    }
}

经过分析,程序在运行时,上述代码中蓝色部分 o=null; 加与不加,对对象的分配和回收是没有任何影响的。而红色的 al=null; 如果不加的话,那么在程序中在这一点明显可以看到内存是不能回收的,但是,由于al本身是一个局部变量,因此在main()运行结束时,al的生命周期结束,它所引用的对象都会被释放。

江南白衣所写的《编写对GC友好,又不泄漏的代码》 (最新版链接:http://blog.csdn.net/calvinxiu/archive/2007/05/22/1621051.aspx)中,提到了这样几条原则:

1.使用更多生命周期短的、小的、不改变指向(immutable)的对象,编写清晰的代码。

    出于懒惰也好,朴素的节俭意识也好,我们都习惯对一个变量重用再重用。但是....

  • Java的垃圾收集器喜欢短生命周期的对象,对象如果在新生代内,在垃圾收集发生前就死掉了,垃圾收集器就什么都不用做了。
  • 现代JVM构建一个新对象只需要10个本地CPU指令,并不弱于C/C++。 (但垃圾收集没有压缩算法时会稍慢,更频繁的New对象也导致更频繁的GC)。
  • 大对象的分配效率更低,而且对非压缩算法的垃圾收集器,更容易造成碎片。
  • 对象重用增加了代码的复杂度,降低了可读性。

   所以有标题的呼吁,比如不要害怕为中间结果分配小对象。但编程习惯的改变也不是一朝一夕的事情。

2.将用完的对象设为NULL其实没什么作用。

    貌似很酷的把对象主动设为Null 的"好习惯"其实没什么用,JIT Compiler会自动分析local变量的生命周期。
    只有一个例外情况,就是String[1024] foo 这种赤裸裸的数组,你需要主动的foo[100]=null释放第100号元素,所以最好还是直接用ArrayList这些标准库算了。

3.避免显式GC--System.gc()。

    大家都知道System.gc()不好,full-gc浪费巨大,gc的时机把握不一定对等等,甚至有-XX:+DisableExplicitGC的JVM参数来禁止它。

    哈哈,但我还不会用System.gc()呢,不怕不怕。真的不怕吗?

  • 先用FindBugs 查一下所用到的全部第三方类库吧...
  • 至少RMI 就会老实不客气的执行System.gc()来实现分布式GC算法。但我也不会用RMI啊。那EJB呢,EJB可是建在RMI上的....

    如果无可避免,用-Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 (单位为微妙) 增大大GC的间隔(原默认值为1分钟),-XX:+ExplicitGCInvokesConcurrent 让System.gc() 也CMS并发执行。

4.继续千夫所指的finalize()

    大家也都知道finalize()不好,分配代价昂贵,释放代价更昂贵(要多走一个循环,而且他们死得慢,和他们相关联的对象也跟着死得慢了),又不确定能否被调用(JVM开始关闭时,就不会再进行垃圾收集),又不确定何时被调用(GC时间不定,即使system.gc()也只是提醒而不是强迫GC,又不确定以什么样的顺序调用,所以finalize不是C++的析构函数,也不像C++的析构函数。

   我们都知道啊,所以我从来都没使用。都是在显式的维护那些外部资源,比如在finally{}里释放。

在上面的例子中,确实也如白衣的文章所述,一般情况下没有必要将对象设置为null。

另外,一些文章中提到了使用ArrayList的方法clear()来清除ArrayList对对象的引用,该方法的源代码如下,实际上就是将其中的每个引用变量设置为null,断开对对象的引用。

    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
    modCount++;

    // Let gc do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
    }

而前面的代码已经说明,对ArrayList对象消亡的时候,所引用的对象也会被回收,所以这个clear方法一般不需要调用。