孙猴子的水帘洞

http://blogs.163jsp.com/monkeysun/date/20080121 星期一 2008年01月21日

Solaris桌面环境的禁用

  如果将Solaris作为服务器使用,启用桌面没有意义,还占用资源,可以使用dtconfig命令来禁用。

/usr/dt/bin/dtconfig -d

 

dtconfig用法:
 
 CDE 配置公用程序
 
  ./dtconfig -d     (禁用自动启动)
  ./dtconfig -e     (允许自动启动)
  ./dtconfig -kill  (终止 dtlogin)
  ./dtconfig -reset (复位 dtlogin)
  ./dtconfig -p     (打印机动作更新)
 ./dtconfig -inetd    (inetd.conf /usr/dt 守护程序)
 ./dtconfig -inetd.ow (inetd.conf /usr/openwin 守护程序) 

http://blogs.163jsp.com/monkeysun/date/20080110 星期四 2008年01月10日

OutOfMemory

如何处理内存溢出错误

这些错误在开发期间相当普遍,即使是在产品服务器上。这些错误比其它错误恼人,因为不会显示任何堆栈跟踪(stack trace)。原因是堆栈跟踪对这样的错误没有任何帮助。导致内存溢出的代码,在大部分情况下,也是问题的“受害者”,而不是它产生的问题。

虽然这些问题让人迷惑容易把问题推卸到Tomcat上,实际上很多问题是在应用程序(webapps)中导致的“错误”。这些应用程序通常是程序设计模式和技术相当合法和安全的独立运行的应用程序,但是没有正确地管理其运行的环境,例如一个servlet容器(例如Tomcat)。

本文提供了一个这些“常见错误”的列表,任何人遇到这些问题,或者想避免这些问题,能够检查他们的webapps并纠正它们。

内存溢出错误的常见情况

一个servlet装载一个几个GB的文件到内存中将会一定杀死服务器。这种问题必须看作我们程序的一个简单的BUG

为了补偿你的servlet尝试装载的数据,你增加了堆尺寸(heap size),因此没有空间为需要创建的线程创建栈(stack)。注:堆区(heap):是由malloc之类函数分配的空间所在地。地址是由低向高增长的。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。栈区(stack):是自动分配变量,以及函数调用的时候所使用的一些空间。地址是由高向低减少的。由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 每一个线程将消耗2M,在一些操作系统(例如Debian Sarge)使用-Xss参数是不能减少的。一般的规则是在一个32web程序上使用多于1Gheap空间。

非常深的递归算法也能导致内存溢出问题。这种情况,仅有的办法是增加线程的stack空间(-Xss,或者改变算法较少深度或者每次调用的本地数据尺寸。

一个webapp使用很多具有依赖关系的库,或者一个服务器运行了太多的webapp,耗尽了JVMPermGen空间。这个空间是VM用来存储类和方法数据。在这种情况下,要增加这个尺寸。Sun的VM的 –XX:MaxPermSize 允许你设置这个尺寸(默认是64M)

⒌一个类的硬参考能够阻止垃圾回收器释放其占用的内存当ClassLoader被丢弃时。这个在JSP重新编译和webapps重新装载时出现。如果这些操作在一个webapps中是普遍的, 这仅仅是个时间问题,PermGen空间满的时候,内存溢出错误就被抛出。

最后这个情况我打算在这里说明。它是一个直接相关的事实,webapp运行在一个可管理的环境中,修改代码的提交根本不需要停止webapp。也就是说,将被包括的模式将是那些,虽然作为一个独立的应用程序是安全和合法的,需要重新设计来“兼容”servlet容器。

 

线程

servlet里没有其它线程启动运行。否则他们保持本地变量、他们的类和整个类装载器的硬参考。

DriveManager

如果你在你自己的classloader(或者servlets)中装载一个java.sql.Driver,驱动器在应用卸载时应该被移出。每个驱动器在DriverManager中注册,被系统classloader装载,参考本地的驱动器。

                Enumeration<Driver> drivers = DriverManager.getDrivers();
                ArrayList<Driver> driversToUnload=new ArrayList<Driver>();
                while (drivers.hasMoreElements()) {
                        Driver driver = drivers.nextElement();
                        if (driver.getClass().getClassLoader().equals(getClass().getClassLoader())) {
                                driversToUnload.add(driver);
                        }
                }
                for (Driver driver : driversToUnload) {
                    DriverManager.deregisterDriver(driver);
                }

Singleton模式

这是非常有用的模式在很多java程序中。它在一些独立运行的应用程序中是安全和可靠的,它看起来像这样:

public class MyClass {
  private static final MyClass instance = new MyClass();
  public static MyClass getInstance() {
    return instance;
  }
  private MyClass() { }

}

这种模式的问题是它创建了一个类本身的一个硬参考。只要这个实例没释放,类就不会被卸载。最终,这将导致服务器不能为整个webapps类装载器分配空间。下面的很多变通方法为了服务器的完整性牺牲了一些原有模式的优点。这个就让设计或者开发者来决定这些牺牲是否值得。

确认问题
  在你重新考虑你整个应用程序前,请确认这是否是真正的问题。我的singleton最终移出,但是tomcat还是崩溃。
1.在这样的singleton类中覆盖finalize方法,放置一个System.out.println()信息。
2.在构造函数中调用几次System.gc() (因为垃圾回收器是有些懒惰的)
3.反复布置几次应用程序
4.你可能会看到旧的singletons最终被释放,如果类装载器被垃圾回收器回收。
5.反复布置几次,内存溢出发生,在内存中会有多个"singletons"。

下面是一些变通方法,自己看吧。
Workaround 1: Move the class to another classloader

This workaround is for the case this class should be shared between webapps, or if the server will contain only one webapp. That is, we need to use the same instance across several webapps in the same server, or there is no need to worry about it. In this case, the class will need to be deployed on a shared classloader. This means this class must be in the shared/lib or shared/classes directory.

This way, the class will be loaded by a parent classloader, and not by the webapp classloader itself, so no resources need to be reclaimed on webapp reloadings.

This workaround may not always fit well with your code or design. In particular, care must be taken to avoid the singleton to keep references to classes loaded through the webapp classloader, because such references would prevent the classloader from being deallocated. A servlet context listener could be used to get rid of those references before the context is destroyed.

Workaround 2: Use commons-discovery

If you need to have a singleton instance for each webapp, you could use commons-discovery. This library provides a class named DiscoverSingleton that can be used to implement singletons in your webapp.

For using it, the class to be used as singleton will need to implement an interface (SPI) with the methods to be used. The following code is an example of usage of this library:

  MyClass instance = DiscoverSingleton.find(MyClass.class, MyClassImpl.class.getName());

It is important, for this library to work correctly, to not keep static references to the returned instances.

Just by using this syntax, you get the following advantages:

    ●Any class could be used as a singleton, as long as it implements an SPI interface.
    ●Your singleton class has been converted into a replaceable component in your webapp, so you can "plug-in" a different implementation whenever you want.

But only this does not make for a workaround. The most important advantage is the DiscoverSingleton.release() method, that releases all references to instantiated singletons in the current classloader. A call to this method could be placed into a ServletContextListener, into its contextDestroyed() method.

That is, with a ServletContextListener simple implementation like the following:

public class SingletonReleaser implements ServletContextListener {
  public contextInitialized(ServletContextEvent event) { }

  public contextDestroyed(ServletContextEvent event) {
    DiscoverSingleton.release();
  }
}

we could release all cached references to the instantiated singletons. Of course, this listener should be registered on the web.xml descriptor before any other listener that could use a singleton.


Workaround 3: Use ServletContext attributes

This refactoring will work well provided the ServletContext instance is available, as a local variable or as a parameter.

It will be more efficient than using commons-discovery, but has the disadvantage of making your code depend on the web layer (ServletContext class). Anyway, I have found out that, in some cases, it is a reasonable approach.

There are many ways to do this refactoring, so I will just present one implementation that works well for me:

    ●Create the following ServletContextListener:

public class SingletonFactory implements ServletContextListener {
  public static final String MY_CLASS = "...";

  /**
   * @see ServletContextListener#contextInitialized(ServletContextEvent)
   */
  public void contextInitialized(ServletContextEvent event) {
    ServletContext ctx = event.getServletContext();
    ctx.setAttribute(MY_CLASS, new MyClass());
  }

  /**
   * @see ServletContextListener#contextDestroyed(ServletContextEvent)
   */
  public void contextDestroyed(ServletContextEvent event) {
    ctx.setAttribute(MY_CLASS, null);
  }

  /**
   * Optional method for getting the MyClass singleton instance.
   */
  public static MyClass getMyClassInstance(ServletContext ctx) {
    return (MyClass)ctx.getAttribute(MY_CLASS);
  }
}

    ●Register the listener in the web.xml descriptor
    ●Replace the calls to MyClass.getInstance() by:

  MyClass instance = (MyClass)ctx.getAttribute(SingletonFactory.MY_CLASS);

  /* or, if implemented:
  MyClass instance = SingletonFactory.getMyClassInstance(ctx);
  */

http://blogs.163jsp.com/monkeysun/date/20080109 星期三 2008年01月09日

在JSP中取得通过URL获得文件的磁盘绝对路径

  总有用户询问,其程序布置的目录绝对位置是啥。其实很简单,ServletContext中有一个getRealPath函数,它可以通过虚拟路径来得到目录或者文件的绝对路径。

例如在JSP文件中:

application.getRealPath("/")

就可以获得应用目录的绝对路径,如果应用的目录为 D:\Tomcat\webapps\test,那么 application.getRealPath("/")就会返回D:\Tomcat\webapps\test 。

 

http://blogs.163jsp.com/monkeysun/date/20080108 星期二 2008年01月08日

非常感谢FreeBSD

  第一次亲自安装的UNIX就是FreeBSD,那时候Solaris还没有开源。FreeBSD确实开源的,随时都可以下载安装,重要的是还有中文手册,使用我们平常的PC机就可以了,不像其它的UNIX高高在上,PC上根本没法装。能够在自己的机器上联系unix命令那是非常高兴的事,能够快速熟悉unix系统。FreeBSD的性能还是相当不错的,主要用来做web服务器或者邮件服务器,跑PHP没问题,当然还有MySQL数据库。后来只是由于其受java许可的限制,java的支持一直比较慢,即使现在java开源了,相应的jdk版本更新也不是很快。本人是专注java的,没办法,后来听说Solaris开源了,就转到Solaris了。不过无论如何都感谢FreeBSD,她是我unix的初恋。

 

Solaris10上交换空间(swap)的管理

添加裸分区到swap空间

添加一个裸交换分区,你需要在系统上执行如下的步骤:

1. 确定一个空的磁盘分区.

2. 在/etc/vfstab中添加一条记录,把裸分区作为一个交换分区:

/dev/dsk/c0t1d0s0   -   -   swap   -   no   -

3. 执行下面的命令,启用这个交换分区:

#swap -a /dev/desk/c0t1d0s0

4. 使用下面的命令查看交换分区的情况:

#swap -l

添加文件系统到swap
  Solaris支持使用文件来增加交换空间。把一个文件添加到交换空间你需要执行如下步骤:

1. 使用mkfile创建一个文件:

#mkfile 250m /opt/myswapfile

这里创建了一个250M的文件。

2. 为了使用这个交换文件,执行如下命令:

#swap -a /opt/myswapfile

3. 检查变化:

#swap -l

提示: 为了在下次系统启动后也使用这个交换文件,你需要在/etc/vfstab添加如下记录:

/opt/swapfile   -   -   swap   -   no   -

禁用交换空间
  Solaris OS提供了在系统运行时禁用一个交换文件的能力。使用swap命令的-d选项。所有已经分配的块被拷贝到其它交换区域。

solaris# swap -d /opt/myswapfile

检查变化:

solaris# swap -l

监视交换空间
  为交换空间配置合适的大小是很重要的: 太小会影响性能,太大会浪费磁盘空间。
  如果物理内存不够用,Solaris会使用swap空间。这叫做分页。

  这里是一个交换空间的概要:

solaris#swap -s
total: 3500744k bytes allocated + 3048720k reserved = 6549464k used,
23869824k available

  这里是交换空间包含的设备或者文件:

solaris#swap -l
swapfile             dev      swaplo     blocks        free
/dev/md/dsk/d1       85,1         16     41945456      41945456

  如果你的交换空间不够用,你会看到如下的错误信息:

Not Enough Space

or

WARNING /tmp: File system full, swap space limit exceeded

  查看运行的系统是否物理内存不足,可以使用vmstat 和 iostat。

solaris#vmstat
kthr      memory            page            disk          faults      cpu
 r b w   swap  free    re  mf pi po fr de sr m0 m1 m3 m4   in   sy   cs us sy id
 0 0 0 24137360 6421168 70 179 21 14 14 0 0 0  0  0  0  472 3363 1776  4  2 94
 0 0 0 23869912 5953040 11 13  0 0 0  0  0  0  0  0  0  430 1071 1545  7  1 92
 0 0 0 23870896 5953904 58 313 0 2 2  0  0  0  0  0  0  578 2369 1798 20  1 78
 0 0 0 23874712 5957216 11 11  0 0 0  0  0  0  0  0  0  417 1325 1648  0  0 100
 0 0 0 23874744 5957248 22 64  0 3 3  0  0  0  0  0  0  423 1578 1629  1  2 97

  在vmstat输出中查看列sr (Scan Rate)。

solaris#iostat -Pxn
                    extended device statistics
    r/s    w/s   kr/s   kw/s wait actv wsvc_t asvc_t  %w  %b device
    0.1    2.7    1.1    5.6  0.0  0.1    0.2   25.5   0   2 c1t0d0s0
    0.0    0.0    0.0    0.0  0.0  0.0    0.0    9.7   0   0 c1t0d0s1
    0.0    0.0    0.0    0.0  0.0  0.0    0.0    1.2   0   0 c1t0d0s2

  在iostat的输出中看r/s 和 w/s c列,如果这些值高,意味着为了释放空间产生了大量的I/O操作。

  如果物理内存太小,系统会频繁地使用交换设备而产生大量的I/O操作。在这个状态下,CPU的使用率也会增加。所以应该增加物理内存了。

在Solaris10上按部就班地创建一个区域

  这是一个使用Solaris容器技术创建区域的一个简单指南,例子使用了SVM(Solaris卷管理器)和一个Oracle数据库。可以很简单地修改这些步骤添加更多的文件系统到脚本中。

  提示: 在这个例子中, 我仅创建了一个区域(zone), 名字为zone1. 我在步骤2和3中使用了SVM,并测试了Oracle 10.1 和 10.2。

1. 格式化硬盘的片0(slice 0).

2. 创建虚拟设备(meta devices). 例如, 我有三个SAN磁盘, 我想使用三个磁盘串联创建一个虚拟设备。 

# metainit d60 3 1 c2t50060E800456EE02d0s0 1 c2t50060E800456EE02d1s0 
1 c2t50060E800456EE02d2s0
d60: Concat/Stripe is setup

3. 创建软分区:

# metainit d61 -p d60 6g
d61: Soft Partition is setup
# metainit d62 -p d60 10g
d62: Soft Partition is setup
# metainit d63 -p d60 30g
d63: Soft Partition is setup
#

4. 创建文件系统:

# newfs /dev/md/rdsk/d61
newfs: construct a new file system /dev/md/rdsk/d61: (y/n)? y
# newfs /dev/md/rdsk/d62
newfs: construct a new file system /dev/md/rdsk/d62: (y/n)? y
# newfs /dev/md/rdsk/d63
newfs: construct a new file system /dev/md/rdsk/d63: (y/n)? y
#

5. 为根文件系统(/ fs)和Oracle数据库的 /u00 /u01创建挂载点。

 
mkdir -p /export/zone1
mkdir /u00
mkdir /u01
mount /export/zone1

6. 执行下面的脚本, 它的详细内容在步骤11后面。

zonecfg -z zone1 -f /usr/scripts/make.zone1.ksh

# zoneadm list -cv
ID NAME STATUS PATH
0 global running /
- zone1 configured /export/zone1
# chmod 700 /export/zone1

7. 安装zone1:

# zoneadm -z zone1 install
Preparing to install zone <zone1>.
Checking <ufs> file system on device </dev/md/rdsk/d62>
to be mounted at </export/zone1/root>
Checking <ufs> file system on device </dev/md/rdsk/d63>
to be mounted at </export/zone1/root>
Creating list of files to copy from the global zone.
Copying <124550> files to the zone.
Initializing zone product registry.
Determining zone package initialization order.
Preparing to initialize <1021> packages on the zone.
Initializing package <49> of <1021>: percent complete: 4%

8. 运行下面的命令查看区域状态:

# zoneadm list -cv
ID NAME STATUS PATH
0 global running /
- zone1 installed /export/zone1

9. 运行下面的命令把区域的状态改为ready:

# zoneadm -z zone1 ready

10. 使用下面的命令查看区域状态:

# zoneadm list -cv
ID NAME STATUS PATH
0 global running /
1 zone1 ready /export/zone1

11. 启动区域:

# zoneadm -z zone1 boot

/usr/scripts/make.zone1.ksh脚本的详细内容:

create -b
set zonepath=/export/zone1
set autoboot=true
add fs
set dir=/u00
set special=/dev/md/dsk/d62
set raw=/dev/md/rdsk/d62
set type=ufs
end
add fs
set dir=/u01
set special=/dev/md/dsk/d63
set raw=/dev/md/rdsk/d63
set type=ufs
end
add net
set address=10.11.33.144
set physical=ce2
end

Web上使用文本编辑器FCKeditor-超强悍

  这个文本编辑器可是大名鼎鼎了,让你在网页中向使用word那样来编辑文本,并兼容所有的主要流行浏览器。我这里仅仅说明在Tomcat上如何安装它,并运行其附带的例子。

  现在的最新版本是 FCKeditor_2.5.1.zip ,把它下载下来,这个包中包含了主要的javascript脚本,和asp及php使用的例子,并没有jsp实现代码,首先解压,一会儿我们会用到。

  JSP的实现代码最新版本是 FCKeditor-2.3.zip ,下载,解压出里面的web目录即可。你可以把这个目录放在自己的Tomcat\webapps目录下,当然你可以把这个web命名为其它名称。现在这个目录里缺乏核心的javascript等文件,无法运行里面的例子。现在把FCKeditor_2.5.1.zip里面的editor目录、fckconfig.js、fckeditor.js、fckpackager.xml、fckstyles.xml、fcktemplates.xml拷贝到web目录下就可以了,启动Tomcat你就可以体验FCKeditor的强大功能了。

Tomcat下配置虚拟主机

  Tomcat下配置虚拟主机非常简单,只需增加Host元素就可以了,让我们来看看server.xml中默认的localhost主机的定义部分:

      <Host name="localhost" appBase="webapps"
       unpackWARs="true" autoDeploy="true"
       xmlValidation="false" xmlNamespaceAware="false">

      </Host>

  这部分就是定义了一个名称为“localhost”的主机,程序根目录是%Tomcat%\webapps,这里的webapps是相对于Tomcat的安装目录的,是相对路径。明白了这些在定义一个主机就是很简单的事了,例如定义一个localhosta的主机:

      <Host name="localhost" appBase="webapps"
       unpackWARs="true" autoDeploy="true"
       xmlValidation="false" xmlNamespaceAware="false">

      </Host>

      <Host name="localhosta" appBase="D:/hosts/localhosta"
       unpackWARs="true" autoDeploy="true"
       xmlValidation="false" xmlNamespaceAware="false">

      </Host>

   此处的D:/hosts/localhosta目录的作用和%Tomcat%\webapps目录是相同的,你可以在里面创建默认的ROOT目录,并在其下放置程序就可以了,当然你也可以布置其它的应用。

  为了测试这个localhosta主机,你需要修改%Windows%(Windows安装目录)\system32\drivers\etc\hosts文件,用记事本打开就可以。添加一个localhosta的主机名:

127.0.0.1       localhost
127.0.0.1       localhosta

  红色文字为新添加的内容,现在打开浏览器,在地址栏输入 http://localhosta:8080 就可以访问你的新虚拟主机localhosta 。怎么样,简单吧。


Valid HTML! Valid CSS!

This is a personal weblog, I do not speak for my employer.