运行jar包解决方案
2006年08月18日 星期五 下午 07:01
当用java -jar yourJarExe.jar来运行一个经过打包的应用程序的时候,你会发现如何设置-classpath参数应用程序都找不到相应的第三方类,报 ClassNotFound错误。实际上这是由于当使用-jar参数运行的时候,java VM会屏蔽所有的外部classpath,而只以本身yourJarExe.jar的内部class作为类的寻找范围。
**解决方案**
一 BootStrap class扩展方案
Java 命令行提供了如何扩展bootStrap 级别class的简单方法.
-Xbootclasspath: 完全取代基本核心的Java class 搜索路径.
不常用,否则要重新写所有Java 核心class
-Xbootclasspath/a: 后缀在核心class搜索路径后面.常用!!
-Xbootclasspath/p: 前缀在核心class搜索路径前面.不常用,避免
引起不必要的冲突.
语法如下:
java -Xbootclasspath/a:/usrhome/thirdlib.jar; -jar yourJarExe.jar
java ?Xbootclasspath:/d:/myclass/account.jar; -jar yourself.jar
非标准参数又称为扩展参数,其列表如下:
-Xint
设置jvm以解释模式运行,所有的字节码将被直接执行,而不会编译成本地码。
-Xbatch
关闭后台代码编译,强制在前台编译,编译完成之后才能进行代码执行;
默认情况下,jvm在后台进行编译,若没有编译完成,则前台运行代码时以解释模式运行。
-Xbootclasspath:bootclasspath
让 jvm从指定路径(可以是分号分隔的目录、jar、或者zip)中加载bootclass,用来替换jdk的rt.jar;若非必要,一般不会用到;
-Xbootclasspath/a:path
将指定路径的所有文件追加到默认bootstrap路径中;
-Xbootclasspath/p:path
让 jvm优先于bootstrap默认路径加载指定路径的所有文件;
-Xcheck:jni
对 JNI函数进行附加check;此时jvm将校验传递给JNI函数参数的合法性,在本地代码中遇到非法数据时,jmv将报一个致命错误而终止;使用该参数后将造成性能下降,请慎用。
-Xfuture
让jvm对类文件执行严格的格式检查(默认 jvm不进行严格格式检查),以符合类文件格式规范,推荐开发人员使用该参数。
-Xnoclassgc
关闭针对class的gc功能;因为其阻止内存回收,所以可能会导致OutOfMemoryError错误,慎用;
-Xincgc
开启增量gc(默认为关闭);这有助于减少长时间GC时应用程序出现的停顿;但由于可能和应用程序并发执行,所以会降低CPU对应用的处理能力。
-Xloggc:file
与 -verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。
若与 verbose命令同时出现在命令行中,则以-Xloggc为准。
-Xmsn
指定jvm 堆的初始大小,默认为物理内存的1/64,最小为1M;可以指定单位,比如k、m,若不指定,则默认为字节。
-Xmxn
指定jvm堆的最大值,默认为物理内存的1/4或者1G,最小为2M;单位与-Xms一致。
-Xprof
跟踪正运行的程序,并将跟踪数据在标准输出输出;适合于开发环境调试。
-Xrs
减少jvm 对操作系统信号(signals)的使用,该参数从1.3.1开始有效;
从jdk1.3.0开始,jvm允许程序在关闭之前还可以执行一些代码(比如关闭数据库的连接池),即使jvm被突然终止;
jvm关闭工具通过监控控制台的相关事件而满足以上的功能;更确切的说,通知在关闭工具执行之前,先注册控制台的控制handler,然后对CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT这几类事件直接返回true。
但如果jvm以服务的形式在后台运行(比如servlet引擎),他能接收CTRL_LOGOFF_EVENT事件,但此时并不需要初始化关闭程序;为了避免类似冲突的再次出现,从jdk1.3.1开始提供-Xrs参数;当此参数被设置之后,jvm将不接收控制台的控制handler,也就是说他不监控和处理 CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, or CTRL_SHUTDOWN_EVENT事件。
-Xssn
设置单个线程栈的大小,一般默认为512k。
上面这些参数中,比如-Xmsn、-Xmxn……都是我们性能优化中很重要的参数;
-Xprof、 -Xloggc:file等都是在没有专业跟踪工具情况下排错的好手;
在上一小节中提到的关于JProfiler的配置中就使用到了-Xbootclasspath/a:path;
方法一、使用Bootstrap Classloader来加载这些类。
我们可以在运行时使用如下参数:
-Xbootclasspath:完全取代系统 Java classpath.最好不用。
-Xbootclasspath/a: 在系统class加载后加载。一般用这个。
-Xbootclasspath/p: 在系统class加载前加载,注意使用,和系统类冲突就不好了.
win32 java -Xbootclasspath/a: some.jar;some2.jar; -jar test.jar
unix java -Xbootclasspath/a: some.jar:some2.jar: -jar test.jar
win32系统每个jar用分号隔开,unix系统下用冒号隔开
方法二、使用Extension Classloader来加载
你可以把需要加载的jar都扔到%JRE_HOME%/lib/ext下面,这个目录下的jar包会在Bootstrap Classloader工作完后由Extension Classloader来加载。非常方便,非常省心。:)
方法三、还是用AppClassloader来加载,不过不需要classpath参数了
我们在MANIFEST.MF中添加如下代码:
Class-Path: lib/some.jar
lib是和test.jar同目录的一个子目录,test.jar要引用的some.jar包就在这里面。
然后测试运行,一切正常!
如果有多个jar包需要引用的情况:
Class-Path: lib/some.jar lib/some2.jar
每个单独的jar用空格隔开就可以了。注意使用相对路径。
另:如果META-INF 下包含INDEX.LIST文件的话,可能会使Class-Path配置失效。INDEX.LIST是Jar打包工具打包时生成的索引文件,删除对运行不产生影响。
方法四、自定义Classloader来加载
这种方法是终极解决方案,基本上那些知名java应用都是那么干的,如tomcat、jboss等等。
这种方式有点复杂,需要专门开贴讨论。关于ClassLoader的原理和自定义 ClassLoader可以参考这篇 http://longdick.javaeye.com/blog /442213。
总结:
以上四种方法都可以用,特别是程序运行在非常单纯的环境中时。但是,如果是运行在多任务,多应用的环境中时,最好每个应用都能相互独立,第一种和第二种方案都有可能对其他应用产生影响,因此最好就是选择第三种和第四种。
如何方便的指定Java程序运行所需要的所有jar包 收藏
学Java的人经常遇到的一个问题是:如果一个程序依赖某个文件夹下的一堆jar包,那么启动它的时候就需要在java -cp参数后面一个一个的加上jar包的名称,很不方便。
比如主程序类叫Main,在目录lib下有aaa.jar,bbb.jar,ccc.jar,则需要输入以下命令才能执行:java -cp lib/aaa.jar;lib/bbb.jar;/lib.ccc.jar Main
(linux系统下用冒号,windows下用分号)。
如果jar包少,倒也不是很麻烦,但如果依赖的jar包数量很多的话,一个个的输就比较麻烦了,当然我们也可以借助一些脚本或者Ant来实现自动化,但总觉得杀鸡焉用牛刀,反而把事情弄麻烦了。
我自己是这样解决的:
java -Djava.ext.dirs=./lib -cp ./bin Main (假设主程序类Mian.class放在bin目录下)
正如你说看到的,-Djava.ext.dirs起到了关键作用,它将告诉JVM从那里加载一些类,为了方便理解记忆,顺便补充一点 ClassLoader的常识:
Java的类装载模型是一种代理(delegation)模型。当JVM 要求类装载器CL(ClassLoader)装载一个类时,CL首先将这个类装载请求转发给他的父装载器。只有当父装载器没有装载并无法装载这个类时, CL才获得装载这个类的机会。这样, 所有类装载器的代理关系构成了一种树状的关系。树的根是类的根装载器(bootstrap ClassLoader) , 在JVM 中它以"null"表示。除根装载器以外的类装载器有且仅有一个父装载器。在创建一个装载器时, 如果没有显式地给出父装载器, 那么JVM将默认系统装载器为其父装载器。
根(Bootstrap) 装载器:该装载器没有父装载器,它是JVM实现的一部分,从sun.boot.class.path装载运行时库的核心代码。
扩展(Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯Java代码实现的,它从java.ext.dirs (扩展目录)中装载代码。(这一段就是为什么可以通过设置-Djava.ext.dirs来加载一堆jar的原理)
======================================
通过ClassLoader调用外部jar包 收藏
我们大家都知道,每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类。
系统默认的contextClassLoader是systemClassLoader,所以一般而言java程序在执行时可以使用JVM自带的类、$JAVA_HOME/jre/lib/ext/中的类和$CLASSPATH/中的类,对于非默认的jar,一般只能手动在配置环境添加。
但事实上,我们可以通过Thread.currentThread().setContextClassLoader()更改当前线程的contextClassLoader行为,实现在程序内加载外部jar。
PS:
ClassLoader的工作原理是:
1) 线程需要用到某个类时,contextClassLoader将被请求来载入该类
2) contextClassLoader请求它的父ClassLoader来完成该载入请求
3) 如果父ClassLoader无法载入类,则contextClassLoader试图自己来载入
package org.loon.framework.jar;
/** *//**
*
Title: LoonFramework
*
Description:JarLoader,用于jar包的外部操作
*
Copyright: Copyright (c) 2007
*
Company: LoonFramework
* @author chenpeng
* @email:
[email protected] * @version 0.1
*/
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
public class JarLoader extends ClassLoader ...{
//资源缓存
public static Hashtable resources = new Hashtable();
public static JarLoader loader = new JarLoader();
public static Class load(byte[] resource) throws Exception ...{
// 主函数所在类全称
String mainClassName = "";
//class资源及实体缓存
ArrayList classNames = new ArrayList();
ArrayList classBuffers = new ArrayList();
// 存储依赖类
HashMap depends = new HashMap();
// 将byte[]转为JarInputStream
JarInputStream jar = new JarInputStream(new ByteArrayInputStream(
resource));
Manifest manifest = jar.getManifest();
// 当Main-Class被声明时,获得主函数所在类全称
if (manifest != null) ...{
mainClassName = manifest.getMainAttributes().getValue("Main-Class");
}
// 依次获得对应JAR文件中封装的各个被压缩文件的JarEntry
JarEntry entry;
while ((entry = jar.getNextJarEntry()) != null) ...{
// 当找到的entry为class时
if (entry.getName().toLowerCase().endsWith(".class")) ...{
// 将类路径转变为类全称
String name = entry.getName().substring(0,
entry.getName().length() - ".class".length()).replace(
'/', '.');
// 加载该类
byte[] data = getResourceData(jar);
// 缓存类名及数据
classNames.add(name);
classBuffers.add(data);
} else ...{
// 非class结尾但开头字符为'/'时
if (entry.getName().charAt(0) == '/') ...{
resources.put(entry.getName(), getResourceData(jar));
// 否则追加'/'后缓存
} else ...{
resources.put("/" + entry.getName(), getResourceData(jar));
}
}
}
//当获得的main-class名不为空时
while (classNames.size() > 0) ...{
//获得类路径全长
int n = classNames.size();
for (int i = classNames.size() - 1; i >= 0; i--) ...{
try ...{
//查询指定类
loader.defineClass((String) classNames.get(i),
(byte[]) classBuffers.get(i), 0,
((byte[]) classBuffers.get(i)).length);
//获得类名
String pkName = (String) classNames.get(i);
if (pkName.lastIndexOf('.') >= 0) ...{
pkName = pkName
.substring(0, pkName.lastIndexOf('.'));
if (loader.getPackage(pkName) == null) ...{
loader.definePackage(pkName, null, null, null,
null, null, null, null);
}
}
//查询后删除缓冲
classNames.remove(i);
classBuffers.remove(i);
} catch (NoClassDefFoundError e) ...{
depends.put((String) classNames.get(i), e.getMessage()
.replaceAll("/", "."));
} catch (UnsupportedClassVersionError e) ...{
//jre版本错误提示
throw new UnsupportedClassVersionError(classNames.get(i)
+ ", " + System.getProperty("java.vm.name") + " "
+ System.getProperty("java.vm.version") + ")");
}
}
if (n == classNames.size()) ...{
for (int i = 0; i < classNames.size(); i++) ...{
System.err.println("NoClassDefFoundError:"
+ classNames.get(i));
String className = (String) classNames.get(i);
while (depends.containsKey(className)) ...{
className = (String) depends.get(className);
}
}
break;
}
}
try ...{
//加载
Thread.currentThread().setContextClassLoader(loader);
// 获得指定类,查找其他类方式相仿
return Class.forName(mainClassName, true, loader);
} catch (ClassNotFoundException e) ...{
String className = mainClassName;
while (depends.containsKey(className)) ...{
className = (String) depends.get(className);
}
throw new ClassNotFoundException(className);
}
}
/** *//**
* 获得指定路径文件的byte[]形式
* @param name
* @return
*/
final static public byte[] getDataSource(String name) ...{
FileInputStream fileInput;
try ...{
fileInput = new FileInputStream(new File(name));
} catch (FileNotFoundException e) ...{
fileInput = null;
}
BufferedInputStream bufferedInput = new BufferedInputStream(fileInput);
return getDataSource(bufferedInput);
}
/** *//**
* 获得指定InputStream的byte[]形式
* @param name
* @return
*/
final static public byte[] getDataSource(InputStream is) ...{
if (is == null) ...{
return null;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] arrayByte = null;
try ...{
byte[] bytes = new byte[8192];
bytes = new byte[is.available()];
int read;
while ((read = is.read(bytes)) >= 0) ...{
byteArrayOutputStream.write(bytes, 0, read);
}
arrayByte = byteArrayOutputStream.toByteArray();
} catch (IOException e) ...{
return null;
} finally ...{
try ...{
if (byteArrayOutputStream != null) ...{
byteArrayOutputStream.close();
byteArrayOutputStream = null;
}
if (is != null) ...{
is.close();
is = null;
}
} catch (IOException e) ...{
}
}
return arrayByte;
}
/** *//**
* 获得指定JarInputStream的byte[]形式
* @param jar
* @return
* @throws IOException
*/
final static private byte[] getResourceData(JarInputStream jar)
throws IOException ...{
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int size;
while (jar.available() > 0) ...{
size = jar.read(buffer);
if (size > 0) ...{
data.write(buffer, 0, size);
}
}
return data.toByteArray();
}
/** *//**
* 重载的getResource,检查是否重复包含
*/
public URL getResource(String name) ...{
if (resources.containsKey("/" + name)) ...{
try ...{
return new URL("file:///" + name);
} catch (MalformedURLException e) ...{
e.printStackTrace();
}
}
return super.getResource(name);
}
/** *//**
* 执行指定class类
* @param clz
* @param methodName
* @param args
*/
public static void callVoidMethod(Class clz, String methodName,
String[] args) ...{
Class[] arg = new Class[1];
arg[0] = args.getClass();
try ...{
Method method = clz.getMethod(methodName, arg);
Object[] inArg = new Object[1];
inArg[0] = args;
method.invoke(clz, inArg);
} catch (Exception e) ...{
System.err.println(e.getMessage());
}
}
/** *//**
* 重载的getResourceAsStream,检查是否重复包含
*/
public InputStream getResourceAsStream(String name) ...{
if (name.charAt(0) == '/') ...{
name = name.substring(1);
}
if (resources.containsKey("/" + name)) ...{
return new ByteArrayInputStream((byte[]) resources.get("/" + name));
}
return super.getResourceAsStream(name);
}
}
运行示例:
package org.loon.framework.jar;
/** *//**
*
* Title: LoonFramework
*
*
* Description:从外部启动jar包
*
*
* Copyright: Copyright (c) 2007
*
*
* Company: LoonFramework
*
*
* @author chenpeng
* @email:
[email protected] * @version 0.1
*/
public class JarTest ...{
public static void main(String[] args) ...{
//将jar包转为byte[]
byte[] resource = JarLoader.getDataSource("D:/fps_test.jar");
try ...{
//通过byte[]获得主函数所在类
Class clz = JarLoader.load(resource);
//调用main函数
JarLoader.callVoidMethod(clz, "main", new String[] ...{""});
} catch (Exception e) ...{
e.getStackTrace();
}
}
}
这时即使指定jar包没有被我们添加到lib中,程序依旧被顺利启动了。
但是有个缺点,在没有优化的前提下,这种直接加载外部包的速度在jvm会有很大损耗。
我们可以看到,fps值降低到正常值的50%左右(此fps实例见我CSDN以前的文章),所以并不适合直接运用在需要复杂运算的jar中类调用上(当然,有兴趣的话,可以在代码中优化,我自己在项目中写的另一个例子速度明显比这个快)。但是对于运算量小的外部jar调用,还是很方便的。