﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-黑灵客栈-随笔分类-客户端技术</title><link>http://www.blogjava.net/mstar/category/12524.html</link><description>搞软件开发就像被强奸,如果不能反抗,就享受它吧！</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 18:20:10 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 18:20:10 GMT</pubDate><ttl>60</ttl><item><title>[备份]Eclipse - a tale of two VMs (and many classloaders)</title><link>http://www.blogjava.net/mstar/archive/2006/06/26/55194.html</link><dc:creator>黑灵</dc:creator><author>黑灵</author><pubDate>Mon, 26 Jun 2006 09:37:00 GMT</pubDate><guid>http://www.blogjava.net/mstar/archive/2006/06/26/55194.html</guid><wfw:comment>http://www.blogjava.net/mstar/comments/55194.html</wfw:comment><comments>http://www.blogjava.net/mstar/archive/2006/06/26/55194.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/mstar/comments/commentRss/55194.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/mstar/services/trackbacks/55194.html</trackback:ping><description><![CDATA[
		<strong>
				<a href="http://www.eclipsezone.com/articles/eclipse-vms/">
						<font size="5">http://www.eclipsezone.com/articles/eclipse-vms/</font>
				</a>
		</strong>
		<div id="leftColumn">
				<h1>Eclipse - a tale of two VMs (and many classloaders)</h1>
				<p>When starting off with Eclipse plugin development or rich client platform development, you're more than likely to run into issues like <code>ClassNotFoundException</code> or problems with the Java command line and properties like <code>java.endorsed.dirs</code>.</p>
				<p>Most often, these problems arise because many Eclipse developers don't realise the magic that lets Eclipse do its work. Amongst these are the fact that there's actually two processes under the covers, and that each bundle has its own classloader. Once you understand how these fit together, debugging problems may be somewhat easier.<br /></p>
				<h1>Eclipse Boot Process</h1>
				<p>Why does Eclipse need two processes to run? The simple answer is that an Eclipse application needs to be run with several system properties set in order to function properly. (These include parameters like <code>osgi.configuration.area</code> and <code>osgi.instance.area</code>.) It's a key requirement in the way that Eclipse starts up, because it needs to know where to put both temporary and persistent data that's not stored directly in one of your project locations. (Examples include your preferences and the workbench history, which tracks changes to files that you have made.)</p>
				<p>Many other tools use a shell script to launch an application with a particular classpath; not only IDEs (like NetBeans) but also server-side tools (Maven, Ant, CruiseControl ...). Most of the time, these shell scripts use shell-specific routines to add entries to a classpath (e.g. <code>for i in lib/*.jar</code>), and on the whole, this approach works. But some operating systems aren't as expressive as others, and unless you have the ability to add other complex logic to the script via external programs, it may not be possible to do everything that you'd want. It also means that you potentially have to test the shell script on other operating systems to determine whether it's correct or not; though generally if it works on one Unix system, it will work on others too.</p>
				<p>To solve this issue (and others), Eclipse doesn't use a shell script to boot itself. Instead, it uses a native executable (<code>eclipse.exe</code> or <code>eclipse</code>), to parse the command-line arguments (like <code>-vmargs</code>) and make them available to the OSGi platform. Importantly, it calculates defaults for values that aren't overridden. It then fires up the OSGi platform <em>in a new process</em> (the platform VM), and hands the rest of the booting process over.</p>
				<p>You may wonder why it fires up a new process, rather than continuing booting by incorporating a VM in the same one. There's two reasons for this: firstly, the OSGi platform that's being launched can call <code>System.exit()</code> without taking down the launcher (c.f. the JavaDoc task in Ant, or the fork attribute of the Java task). The second (perhaps more important) one is that it allows the OSGi platform to be launched with a different set of parameters -- or even use a specfic version of the VM. The launcher can run on many versions of the Java VM, and the launcher can guarantee that your platform code will run in a known VM (for example, by shipping one with your code and placing it in the <code>jre/</code> directory).</p>
				<p>The launcher also reads a variety of configuration files; <code>eclipse.ini</code>, if present, is a newline separated list of flags that are passed into the platform VM. The <code>-vmargs</code> flag is used to pass any subsequent arguments as VM arguments rather than normal arguments.</p>
				<p>All of this means that when launching Eclipse via <code>startup.jar</code> the arguments and extra options are effectively ignored by the actual Eclipse runtime instance. You may think that:</p>
				<pre>eclipse -Xmx1024m <br /></pre>
				<p>would give you a huge Eclipse memory runtime, but in actual fact, all you're doing is specifying an argument which is ignored by the <code>eclipse</code> launcher. The native launcher deals with some arguments directly; but otherwise, they're passed on verbatim to the Java launcher <code>org.eclipse.core.launcher.Main</code> where the real work is done. The arguments that the native launcher explicitly deals with are:</p>
				<dl>
						<dt>-vmargs 
</dt>
						<dd>Arguments following this are passed directly into the platform VM's Java executable, before the class name. It must be the last argument, since anything following is put on the command line. Importantly, spaces need to be appropriately quoted or escaped since otherwise you may get the dreaded "Program/Eclipse cannot be located", which is usually because one of the VM arguments have <code>C:\Program Files\Eclipse</code> in them. This is available from the <code>eclipse.vmargs</code> system property, but it cannot be changed once the VM is started. 
</dd>
						<dt>-vm 
</dt>
						<dd>Full path to a Java executable that can be executed. The system path is searched if this is not specified. This is available from the <code>eclipse.vm</code> system property, but it cannot be changed once the VM is started. </dd>
				</dl>
				<p>To run Eclipse with a larger memory area, you need to pass the <code>-vmargs</code> to the launcher, followed by the options that you want:</p>
				<pre>eclipse <i>arg1 arg2 arg3</i><b>-vmargs -Xmx1024m</b></pre>
				<p>this instructs the launcher to create the platform VM, passing in arguments <i>arg1 arg2 arg3</i> and setting up the platform VM with <code>-Xmx1024m</code>. However, we could equally have had a file called <code>eclipse.ini</code>:</p>
				<pre>arg1<br />arg2<br />arg3<br />-vmargs<br />-Xmx1024m<br /></pre>
				<p>which would have the same effect. The launcher looks for <code><i>application.ini</i></code> and <code>eclipse.ini</code> to derive these parameters, if your executable is called <code>application.exe</code>.</p>
				<p>The <code>eclipse</code> launcher then fires up a VM, which uses <code>org.eclipse.core.launcher.Main</code> (from <code>startup.jar</code>) to instigate the next phase of the boot process. The <code>Main</code> launcher sets up specific areas that are needed by the OSGi platform, including setting up the properties such as where the configuration area will be. It then boots the <code>OSGi</code> platform, which reads which plugins to start from the <code>osgi.bundles</code> entry from the <code>config.ini</code>, and applies product branding from <code>-product</code> and OS/Language specific settings from <code>-os</code> and <code>-nl</code>. Once that's running, the Eclipse platform task over which locates and loads the bundle associated with the <code>-application</code> or <code>-feature</code> arguments. Certain plugins (like <code>org.eclipse.core.resources</code>) interpret specific command line arguments (like <code>-refresh</code>), but this is the exception rather than the rule.</p>
				<p>If you want to embed Eclipse into another Java process, you can use the <code><a class="externalLink" title="External Link" href="http://help.eclipse.org/help31/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/adaptor/EclipseStarter.html">EclipseStarter</a></code> class, as long as you supply the appropriate entries. That will fire up all the necessary plugin support to kick your application off. There's also <a class="externalLink" title="External Link" href="http://help.eclipse.org/help31/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/launcher/WebStartMain.html">WebStartMain</a> that can be used to kick off an Eclipse install via Java WebStart (although with <a class="externalLink" title="External Link" href="http://help.eclipse.org/help31/topic/org.eclipse.platform.doc.isv/guide/java_web_start.htm">some limitations</a>).</p>
				<p>There's a full list of the arguments and when they are interpreted in the <a class="externalLink" title="External Link" href="http://help.eclipse.org/">Eclipse help pages</a> under "<a class="externalLink" title="External Link" href="http://help.eclipse.org/help31/topic/org.eclipse.platform.doc.isv/reference/misc/runtime-options.html">runtime options</a>", and can be summarised as:</p>
				<table class="bodyTable">
						<thead>
								<tr class="a">
										<th>Handled by eclipse.exe</th>
										<th>Handled by <code>org.eclipse.core.launcher.Main</code></th>
										<th>Handed by the OSGi platform</th>
										<th>Handled by the Eclipse platform</th>
								</tr>
						</thead>
						<tbody>
								<tr class="b">
										<td>
												<ul>
														<li>-vmargs 
</li>
														<li>-vm 
</li>
														<li>-name 
</li>
														<li>-noSplash 
</li>
														<li>-startup </li>
												</ul>
										</td>
										<td>
												<ul>
														<li>-configuration 
</li>
														<li>-endSplash 
</li>
														<li>-framework 
</li>
														<li>-initialize 
</li>
														<li>-install 
</li>
														<li>-noSplash 
</li>
														<li>-showSplash </li>
												</ul>
										</td>
										<td>
												<ul>
														<li>-arch 
</li>
														<li>-clean 
</li>
														<li>-console 
</li>
														<li>-data 
</li>
														<li>-debug 
</li>
														<li>-dev 
</li>
														<li>-nl 
</li>
														<li>-os 
</li>
														<li>-product 
</li>
														<li>-user 
</li>
														<li>-ws </li>
												</ul>
										</td>
										<td>
												<ul>
														<li>-application 
</li>
														<li>-feature 
</li>
														<li>-keyring 
</li>
														<li>-noLazyRegistryCacheLoading 
</li>
														<li>-noRegistryCache 
</li>
														<li>-password 
</li>
														<li>-pluginCustomization </li>
												</ul>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<h1>ClassLoaders, Bundles and Buddies</h1>
				<p>You may recall that Java uses a <code>ClassLoader</code> to bring classes into the VM. Most of the time, you probably don't need to know anything about how they work (or even that they exist) but every class in Java is loaded via a <code>ClassLoader</code>. Its job is to turn a sequence of bytes into a <code>java.lang.Class</code>. Where those bytes come from doesn't really matter; and it's this fact (and the <code>URLClassLoader</code>) that brought Java to fame with Applets in the first place. Mostly, client side applications don't need to worry about the <code>ClassLoader</code>: they just set up a <code>classpath</code> and It Just Works.</p>
				<p>Server-side applications (J2EE servers and others like Tomcat) have long used <code>ClassLoader</code>s to enable classes to be loaded on demand from different structures (like WAR formats). As well as providing a way of accessing the classes, the <code>ClassLoader</code> also provides another key benefit; it separates out classes with the same name. Every class in Java has an associated <code>ClassLoader</code> (and you can find out which by <code>obj.getClass().getClassLoader()</code> if you want).</p>
				<p>Importantly, a class name is unique only within its <code>ClassLoader</code>. That means it's possible to have two classes with the same name loaded into a VM at once, provided that they have two separate <code>ClassLoader</code>s that loaded them. Whilst this may sound freaky and unnatural, in fact it's how application servers like Tomcat can host any Web application and allow hot deployment. It simply creates a new <code>ClassLoader</code>, loads in the classes (again) and runs the new version. Without this ability, an application server would not be able to reload a web application without having to restart the server in its entirety.</p>
				<p>Eclipse uses this to its advantage. One of the key features of Eclipse 3.0 was the ability to stop and start a bundle whilst the platform continued running. (It's used when you update, and choose the 'Apply changes now' instead of restarting Eclipse, amongst other things.) Perhaps more importantly, Eclipse allows multiple different versions of a bundle to be loaded at one time, which might be useful if you have many parts of your plugin relying either on 2.7.1 or 2.8.0 of Xerces.</p>
				<p>To do this, each bundle (OSGi) or plugin (Eclipse) has its own <code>ClassLoader</code>. When a bundle is started, it gets given its own <code>ClassLoader</code> for its lifetime. When it's stopped and restarted, a new <code>ClassLoader</code> is created. This allows the new bundle to have a different set of classes than the previous version.</p>
				<p>The OSGi platform translates the <code>Manifest.MF</code> present in every (OSGi-enabled) plugin into a hierarchy of <code>ClassLoader</code>s. When you can't load a class from your plugin directly, the bundle mechanism searches through all the required bundles to see if it can find the class there instead. Packages that depend on the same bundles (e.g. <code>org.eclipse.ui</code>) will result in the same class being loaded, because it comes from <code>org.eclipse.ui</code>'s <code>ClassLoader</code>.</p>
				<p>This causes confusion for new developers to the Eclipse platform. It doesn't matter what's in the <code>CLASSPATH</code> environment variable, or what's specified on the <code>-classpath</code> argument, because pretty much every <code>ClassLoader</code> only sees what its <code>Manifest.MF</code> tells it to see. If there's no dependency at the bundle level, then as far as Eclipse is concerned, it isn't there. (Incidentally, that's how the <code>.internal.</code> packages work; whilst they exist in the bundles, and can be loaded by the bundle's <code>ClassLoader</code>, the Eclipse class loading mechanism hides any package that's not explicitly mentioned in the <code>Export-Package</code> header in the <code>Manifest.MF</code>. It also doesn't help that the PDE runtime only consults the entries in the <code>.classpath</code> when running/debugging inside Eclipse, and it's only when you try to export the product that the <code>Manifest.MF</code> is consulted, often resulting in errors. A <code>ClassNotFoundException</code> during product/plugin exporting is almost always down to the fact that it's mentioned in the <code>.classpath</code> (Java Build path) but not the <code>Manifest.MF</code>.</p>
				<p>A larger problem occurs when using libraries that expect to be able to see your code as well as their own. This includes libraries such as <code>log4j</code> and generator tools such as <code>JibX</code> that both supply services and consume code. Because Eclipse uses a partitioned class loading mechanism, the <code>ClassLoader</code> of the <code>org.apache.log4j</code> can't see the classes defined in <code>org.example.myplugin</code>, since it's a separate <code>ClassLoader</code>. As a result, <code>log4j</code> can't use your supplied class for logging, and complains.</p>
				<p>This is the purpose of the <b>buddy class loading</b> in Eclipse. It is designed to work around the issues that can be found in this type of problem. (It's quite similar to the options that you can configure with some of the application servers, such as "parent first" and "child first" lookup policies.) The buddy policy (specified with the <code>Eclipse-BuddyPolicy</code> entry) takes one of:<br /></p>
				<dl>
						<dt>dependent 
</dt>
						<dd>search the dependent's classloaders 
</dd>
						<dt>global 
</dt>
						<dd>search the global packages exported via <code>Export-Package</code></dd>
						<dt>app 
</dt>
						<dd>search the application's classloader 
</dd>
						<dt>ext 
</dt>
						<dd>search the extension's classloader 
</dd>
						<dt>boot 
</dt>
						<dd>search the boot's classloader 
</dd>
						<dt>registered 
</dt>
						<dd>search the registered buddies classloaders </dd>
				</dl>
				<table style="TEXT-ALIGN: left" cellspacing="2" cellpadding="2" border="0">
						<tbody>
								<tr>
										<td style="VERTICAL-ALIGN: top">  
<dl><img style="WIDTH: 692px; HEIGHT: 452px" height="586" alt="Eclipse2VMCL.gif" src="http://www.blogjava.net/images/blogjava_net/mstar/pics20050930/Eclipse2VMCL.gif" width="1137" border="0" /></dl></td>
								</tr>
								<tr>
										<td style="VERTICAL-ALIGN: top; TEXT-ALIGN: center">
												<small>
														<span style="FONT-STYLE: italic">Figure 1. Buddy classloader diagram</span>
												</small>
												<br />
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<p>Whilst all of these are generally fine, if you want to use a tool like <code>log4j</code> then it generally boils down to:</p>
				<pre># log4j Manifest.MF<br />Bundle-Name: org.apache.log4j<br />Bundle-Version: 1.2.13<br />...<br />Eclipse-BuddyPolicy: registered<br /></pre>
				<p>This says "If anyone registers with me, and I need to find a class that I can't otherwise find, I'll check with them before failing". You also need to define in your own plugin that you want to register with it:</p>
				<pre># myplugin Manifest.MF<br />Bundle-Name: org.example.myplugin<br />Bundle-Version: 1.0.0<br />Requires-Bundle: org.apache.log4j,...<br />...<br />Eclipse-RegisterBuddy: org.apache.log4j<br /></pre>
				<p>This now makes the <code>org.apache.log4j</code> and <code>org.example.myplugin</code> buddies. Of course, our <code>org.example.myplugin</code> always depended on <code>org.apache.log4j</code> before, but now we've added the extra hook back that allows the <code>log4j</code> to see our classes as well. In essence, it's like adding <code>Requires-Bundle: org.example.myplugin</code> to the <code>org.apache.log4j</code> bundle, except that obviously you can't do that (it would create a circular reference) and I doubt that Ceki G?lc? would have had the foresight to depend on myplugin in advance. But by putting <code>Eclipse-BuddyPolicy: registered</code> into the definition, now any future bundle can plug in and register its code with <code>org.apache.log4j</code>.</p>
				<p>It's probably a pretty good idea to add the <code>Eclipse-BuddyPolicy: registered</code> to your plugin now, because in the future someone might need it.</p>
				<h1>Native code and classloaders</h1>
				<p>As well as resolving classes, the <code>ClassLoader</code> is also responsible for finding native code in response to a <code>System.loadLibrary()</code> call. Whilst it doesn't actually perform the loading of the DLL itself, it does figure out where the DLL is and passes a full path to the operating system to install the code. In the case of packed OSGi bundles, this call automatically extracts a copy of the DLL to a temporary location before passing the location back to the OS.</p>
				<p>It's also worth bearing in mind that the DLL loaded may have other dependencies. Unfortunately, although Eclipse knows where to look for further dependencies (e.g. the <code>java.library.path</code> or the contents of a packed Jar file), once the operating system only knows about the <code>PATH</code> or <code>LD_LIBRARY_PATH</code> environment variables (depending on your operating system). So if you have <code>a.dll</code> which depends on <code>b.dll</code>, and you do <code>System.loadLibrary("a")</code>, it will fail with an <code>UnsatisfiedLinkError</code>. That's because although Eclipse knows where to find the dependent dll, the operating system doesn't know where to look for <code>b.dll</code> and gives up. Instead, if you did <code>System.loadLibrary("b"); System.loadLibrary("a");</code> then the DLL is available in memory for <code>a.dll</code> to trivially hook up against, and it all works. The moral of the story is always load the DLLs in the order in which they are dependent.</p>
				<p>It's worth bearing in mind that Windows and Java have some issues when it comes to loading DLLs. A DLL can only be loaded once in a VM and only associated with one <code>ClassLoader</code>. If you have two <code>ClassLoader</code>s, and they attempt to load the same DLL, then the second one will fail and not be able to execute any native code. As a result of this, you cannot have two bundles that attempt to load the same native code in Eclipse. This will also apply if the bundle is updated or restarted, because the old <code>ClassLoader</code> may retain a reference to the DLL whilst the new one starts. This is a Windows-specific problem that doesn't appear to manifest itself on other operating systems.</p>
				<p>Lastly, whilst Eclipse 2.1 used a combination of directories (<i>ws/os/arch</i>, such as <code>win32/win32/x86/swt1234.dll</code>) for storing native code, this is no longer the preferred way of doing it. Instead, native code should be at the root level of the plugin, probably with a suffix like swt1234-win32x86.dll. Even better, instead of doing <code>System.loadLibrary()</code>, you can define <code>Bundle-NativeCode: swt1234.dll</code> and the OSGi platform will load it automatically. You can either provide per-os fragments, or split them apart and use a platform filter (such as <code>Eclipse-PlatformFilter: (&amp; (osgi.ws=win32) (osgi.os=win32) (osgi.arch=x86))</code>).</p>
				<h1>Conclusion</h1>
				<p>Once you understand how the processes work within the Eclipse boot sequence, and the fact that it utilises many classloaders, some of the <code>ClassNotFoundException</code>s become easier to understand. Specifying arguments works on a command line <code>java</code> invocation because you're specifying the VM on the command line (and most of the time, only using one <code>ClassLoader</code> too). In the Eclipse world, if you want to affect Eclipse's VM, you need to remember to prefix any arguments with <code>-vmargs</code>, either on the command line or on via the <code>eclipse.ini</code> file.</p>
				<p>It's also worth checking when you have <code>ClassNotFoundException</code>s when exporting or running an application outside of Eclipse's debugger, that you have the dependencies listed in <code>Manifest.MF</code> and not just in the <code>.classpath</code> (Java Build Path). In fact, you shouldn't need to put any extra entries in the <code>.classpath</code> if you're building a plugin project, because the entries in <code>Manifest.MF</code> are automatically available in the <code>.classpath</code> through the "Plugin Dependencies" classpath container.</p>
				<p>Lastly, if you're using native code in an Eclipse application, it's a very good idea to isolate the native code in its own bundle. Any plugin that needs to use that native functionality can then express a dependency on that bundle, which allows it to be shared by as many bundles as need it. That way, if you have any non-native updates to your bundle, you don't need to restart Eclipse to see those changes. Of course, if you have native bundle updates then you have to restart anyway to get the benefit.</p>
				<p>We encourage you to ask the author any questions or discuss the article <a href="http://www.eclipsezone.com/forums/thread.jspa?threadID=71009">here</a>. </p>
		</div>
<img src ="http://www.blogjava.net/mstar/aggbug/55194.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/mstar/" target="_blank">黑灵</a> 2006-06-26 17:37 <a href="http://www.blogjava.net/mstar/archive/2006/06/26/55194.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>