Eclipse
原文地址:http://www.eclipsezone.com/articles/eclipse-vms/When starting off with Eclipse plugin development or rich clientplatform development, you're more than likely to run into issues like ClassNotFoundExceptionor problems with the Java command line and properties like java.endorsed.dirs.
Most often, these problems arise because many Eclipse developersdon'trealise the magic that lets Eclipse do its work. Amongst these are thefact that there's actually two processes under the covers, and thateach bundle has its own classloader. Once you understand how these fittogether, debugging problems may be somewhat easier.
Eclipse Boot Process
Why does Eclipse need two processes to run? The simple answer isthat an Eclipse application needs to be run with several systemproperties set in order to function properly. (These include parameterslike osgi.configuration.area and osgi.instance.area.)It's a key requirement in the way that Eclipse starts up, because itneeds to know where to put both temporary and persistent data that'snot stored directly in one of your project locations. (Examples includeyour preferences and the workbench history, which tracks changes tofiles that you have made.)
Many other tools use a shell script tolaunch an application with a particular classpath; not only IDEs (likeNetBeans) but also server-side tools (Maven, Ant, CruiseControl ...).Most of the time, these shell scripts use shell-specific routines toadd entries to a classpath (e.g. for i in lib/*.jar), andon the whole, this approach works. But some operating systems aren't asexpressive as others, and unless you have the ability to add othercomplex logic to the script via external programs, it may not bepossible to do everything that you'd want. It also means that youpotentially have to test the shell script on other operating systems todetermine whether it's correct or not; though generally if it works onone Unix system, it will work on others too.
To solve this issue (and others), Eclipse doesn't use a shell scriptto boot itself. Instead, it uses a native executable (eclipse.exeor eclipse), to parse the command-line arguments (like -vmargs)and make them available to the OSGi platform. Importantly, itcalculates defaults for values that aren't overridden. It then fires upthe OSGi platform in a new process (the platform VM), andhands the rest of the booting process over.
Youmay wonder why it fires up a new process, rather than continuingbooting by incorporating a VM in the same one. There's two reasons forthis: firstly, the OSGi platform that's being launched can call System.exit()without taking down the launcher (c.f. the JavaDoc task in Ant, or thefork attribute of the Java task).The second (perhaps more important) one is that it allows the OSGiplatform to be launched with a different set of parameters -- or evenuse a specfic version of the VM. The launcher can run on many versionsof the Java VM, and the launcher can guarantee that your platform codewill run in a known VM (for example, by shipping one with your code andplacing it in the jre/ directory).
The launcher also reads a variety of configuration files; eclipse.ini,if present, is a newline separated list of flags that are passed intothe platform VM. The -vmargs flag is used to pass anysubsequent arguments as VM arguments rather than normal arguments.
All of this means that when launching Eclipse via startup.jarthe arguments and extra options are effectively ignored by the actualEclipse runtime instance. You may think that:
eclipse -Xmx1024m would give you a huge Eclipse memory runtime, but in actualfact, all you're doing is specifying an argument which is ignored bythe eclipse launcher. The native launcher deals with somearguments directly; but otherwise, they're passed on verbatim to theJava launcher org.eclipse.core.launcher.Main where thereal work is done. The arguments that the native launcher explicitlydeals with are:
-vmargsArguments following this are passed directly intothe platform VM's Java executable, before the class name. It must bethe last argument, since anything following is put on the command line.Importantly, spaces need to be appropriately quoted or escaped sinceotherwise you may get the dreaded "Program/Eclipse cannot be located",which is usually because one of the VM arguments have C:\ProgramFiles\Eclipse in them. This is available from the eclipse.vmargssystem property, but it cannot be changed once the VM is started.-vmFull path to a Java executable that can be executed.The system path is searched if this is not specified. This is availablefrom the eclipse.vm system property, but it cannot bechanged once the VM is started.To run Eclipse with a larger memory area, you need to pass the -vmargsto the launcher, followed by the options that you want:
eclipse arg1 arg2 arg3 -vmargs -Xmx1024mthis instructs the launcher to create the platform VM, passing inarguments arg1 arg2 arg3 and setting up the platform VM with -Xmx1024m.However, we could equally have had a file called eclipse.ini:
arg1arg2arg3-vmargs-Xmx1024mwhich would have the same effect. The launcher looks for application.iniand eclipse.ini to derive these parameters, if yourexecutable is called application.exe.
The eclipse launcher then fires up a VM, which uses org.eclipse.core.launcher.Main(from startup.jar) to instigate the next phase of theboot process. The Mainlauncher sets up specific areas that are needed by the OSGi platform,including setting up the properties such as where the configurationarea will be. It then boots the OSGi platform, whichreads which plugins to start from the osgi.bundles entryfrom the config.ini, and applies product branding from -productand OS/Language specific settings from -os and -nl.Once that's running, the Eclipse platform task over which locates andloads the bundle associated with the -application or -featurearguments. Certain plugins (like org.eclipse.core.resources)interpret specific command line arguments (like -refresh),but this is the exception rather than the rule.
If you want to embed Eclipse into another Java process, you can usethe EclipseStarterclass, as long as you supply the appropriate entries. That will fire upall the necessary plugin support to kick your application off. There'salso WebStartMain that canbe used to kick off an Eclipse install via Java WebStart (although withsome limitations).
There's a full list of the arguments and when they are interpretedin the Eclipse help pages under "runtime options", andcan be summarised as:
Handled by eclipse.exeHandled by org.eclipse.core.launcher.MainHanded by the OSGi platformHandled by the Eclipse platform
[*]-vmargs
[*]-vm
[*]-name
[*]-noSplash
[*]-startup
[*]-configuration
[*]-endSplash
[*]-framework
[*]-initialize
[*]-install
[*]-noSplash
[*]-showSplash
[*]-arch
[*]-clean
[*]-console
[*]-data
[*]-debug
[*]-dev
[*]-nl
[*]-os
[*]-product
[*]-user
[*]-ws
[*]-application
[*]-feature
[*]-keyring
[*]-noLazyRegistryCacheLoading
[*]-noRegistryCache
[*]-password
[*]-pluginCustomization
ClassLoaders, Bundles and Buddies
You may recall that Java uses a ClassLoader to bringclasses into the VM. Most of the time, you probably don't need to knowanything about how they work (or even that they exist) but every classin Java is loaded via a ClassLoader. Its job is to turn asequence of bytes into a java.lang.Class. Where thosebytes come from doesn't really matter; and it's this fact (and the URLClassLoader)that brought Java to fame with Applets in the first place. Mostly,client side applications don't need to worry about the ClassLoader:they just set up a classpath and It Just Works.
Server-side applications (J2EE servers and others like Tomcat) havelong used ClassLoadersto enable classes to be loaded on demand from different structures(like WAR formats). As well as providing a way of accessing theclasses, the ClassLoader also provides another keybenefit; it separates out classes with the same name. Every class inJava has an associated ClassLoader (and you can find outwhich by obj.getClass().getClassLoader() if you want).
Importantly, a class name is unique only within its ClassLoader.That means it's possible to have two classes with the same name loadedinto a VM at once, provided that they have two separate ClassLoadersthat loaded them. Whilst this may sound freaky and unnatural, in factit's how application servers like Tomcat can host any Web applicationand allow hot deployment. It simply creates a new ClassLoader,loads in the classes (again) and runs the new version. Without thisability, an application server would not be able to reload a webapplication without having to restart the server in its entirety.
Eclipseuses this to its advantage. One of the key features of Eclipse 3.0 wasthe ability to stop and start a bundle whilst the platform continuedrunning. (It's used when you update, and choose the 'Apply changes now'instead of restarting Eclipse, amongst other things.) Perhaps moreimportantly, Eclipse allows multiple different versions of a bundle tobe loaded at one time, which might be useful if you have many parts ofyour plugin relying either on 2.7.1 or 2.8.0 of Xerces.
To do this, each bundle (OSGi) or plugin (Eclipse) has its own ClassLoader.When a bundle is started, it gets given its own ClassLoaderfor its lifetime. When it's stopped and restarted, a new ClassLoaderis created. This allows the new bundle to have a different set ofclasses than the previous version.
The OSGi platform translates the Manifest.MF presentin every (OSGi-enabled) plugin into a hierarchy of ClassLoaders.When you can't load a class from your plugin directly, the bundlemechanism searches through all the required bundles to see if it canfind the class there instead. Packages that depend on the same bundles(e.g. org.eclipse.ui) will result in the same class beingloaded, because it comes from org.eclipse.ui's ClassLoader.
This causes confusion for new developers to the Eclipse platform. Itdoesn't matter what's in the CLASSPATH environmentvariable, or what's specified on the -classpath argument,because pretty much every ClassLoader only sees what its Manifest.MFtells it to see. If there's no dependency at the bundle level, then asfar as Eclipse is concerned, it isn't there. (Incidentally, that's howthe .internal. packages work; whilst they exist in thebundles, and can be loaded by the bundle's ClassLoader,the Eclipse class loading mechanism hides any package that's notexplicitly mentioned in the Export-Package header in the Manifest.MF.It also doesn't help that the PDE runtime only consults the entries inthe .classpath when running/debugging inside Eclipse, andit's only when you try to export the product that the Manifest.MFis consulted, often resulting in errors. A ClassNotFoundExceptionduring product/plugin exporting is almost always down to the fact thatit's mentioned in the .classpath (Java Build path) butnot the Manifest.MF.
Alarger problem occurs when using libraries that expect to be able tosee your code as well as their own. This includes libraries such as log4jand generator tools such as JibX that both supplyservices and consume code. Because Eclipse uses a partitioned classloading mechanism, the ClassLoader of the org.apache.log4jcan't see the classes defined in org.example.myplugin,since it's a separate ClassLoader. As a result, log4jcan't use your supplied class for logging, and complains.
This is the purpose of the buddy class loadingin Eclipse. It is designed to work around the issues that can be foundin this type of problem. (It's quite similar to the options that youcan configure with some of the application servers, such as "parentfirst" and "child first" lookup policies.) The buddy policy (specifiedwith the Eclipse-BuddyPolicy entry) takes one of:
dependentsearch the dependent's classloadersglobalsearch the global packages exported via Export-Packageappsearch the application's classloaderextsearch the extension's classloaderbootsearch the boot's classloaderregisteredsearch the registered buddies classloaders http://www.eclipsezone.com/articles/eclipse-vms/images/Eclipse2VMCL.gifFigure 1. Buddy classloader diagram
Whilst all of these are generally fine, if you want to use a toollike log4j then it generally boils down to:
# log4j Manifest.MFBundle-Name: org.apache.log4jBundle-Version: 1.2.13...Eclipse-BuddyPolicy: registeredThis says "If anyone registers with me, and I need to find aclass that I can't otherwise find, I'll check with them beforefailing". You also need to define in your own plugin that you want toregister with it:
# myplugin Manifest.MFBundle-Name: org.example.mypluginBundle-Version: 1.0.0Requires-Bundle: org.apache.log4j,......Eclipse-RegisterBuddy: org.apache.log4jThis now makes the org.apache.log4j and org.example.mypluginbuddies. Of course, our org.example.myplugin alwaysdepended on org.apache.log4j before, but now we've addedthe extra hook back that allows the log4j to see ourclasses as well. In essence, it's like adding Requires-Bundle:org.example.myplugin to the org.apache.log4jbundle, except that obviously you can't do that (it would create acircular reference) and I doubt that Ceki G?lc? would have had theforesight to depend on myplugin in advance. But by putting Eclipse-BuddyPolicy:registered into the definition, now any future bundle can plugin and register its code with org.apache.log4j.
It's probably a pretty good idea to add the Eclipse-BuddyPolicy:registered to your plugin now, because in the future someonemight need it.
Native code and classloaders
As well as resolving classes, the ClassLoader is alsoresponsible for finding native code in response to a System.loadLibrary()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 theoperating system to install the code. In the case of packed OSGibundles, this call automatically extracts a copy of the DLL to atemporary location before passing the location back to the OS.
It'salso worth bearing in mind that the DLL loaded may have otherdependencies. Unfortunately, although Eclipse knows where to look forfurther dependencies (e.g. the java.library.path or thecontents of a packed Jar file), once the operating system only knowsabout the PATH or LD_LIBRARY_PATHenvironment variables (depending on your operating system). So if youhave a.dll which depends on b.dll, and youdo System.loadLibrary("a"), it will fail with an UnsatisfiedLinkError.That's because although Eclipse knows where to find the dependent dll,the operating system doesn't know where to look for b.dlland gives up. Instead, if you did System.loadLibrary("b");System.loadLibrary("a"); then the DLL is available in memory fora.dllto trivially hook up against, and it all works. The moral of the storyis always load the DLLs in the order in which they are dependent.
It'sworth bearing in mind that Windows and Java have some issues when itcomes to loading DLLs. A DLL can only be loaded once in a VM and onlyassociated with one ClassLoader. If you have two ClassLoaders,and they attempt to load the same DLL, then the second one will failand not be able to execute any native code. As a result of this, youcannot have two bundles that attempt to load the same native code inEclipse. This will also apply if the bundle is updated or restarted,because the old ClassLoader may retain a reference to theDLL whilst the new one starts. This is a Windows-specific problem thatdoesn't appear to manifest itself on other operating systems.
Lastly, whilst Eclipse 2.1 used a combination of directories (ws/os/arch,such as win32/win32/x86/swt1234.dll)for storing native code, this is no longer the preferred way of doingit. Instead, native code should be at the root level of the plugin,probably with a suffix like swt1234-win32x86.dll. Even better, insteadof doing System.loadLibrary(), you can define Bundle-NativeCode:swt1234.dlland the OSGi platform will load it automatically. You can eitherprovide per-os fragments, or split them apart and use a platform filter(such as Eclipse-PlatformFilter: (& (osgi.ws=win32)(osgi.os=win32) (osgi.arch=x86))).
Conclusion
Once you understand how the processes work within the Eclipse bootsequence, and the fact that it utilises many classloaders, some of the ClassNotFoundExceptionsbecome easier to understand. Specifying arguments works on a commandline java invocation because you're specifying the VM onthe command line (and most of the time, only using one ClassLoadertoo). In the Eclipse world, if you want to affect Eclipse's VM, youneed to remember to prefix any arguments with -vmargs,either on the command line or on via the eclipse.ini file.
It's also worth checking when you have ClassNotFoundExceptionswhen exporting or running an application outside of Eclipse's debugger,that you have the dependencies listed in Manifest.MF andnot just in the .classpath (Java Build Path). In fact,you shouldn't need to put any extra entries in the .classpathif you're building a plugin project, because the entries in Manifest.MFare automatically available in the .classpath through the"Plugin Dependencies" classpath container.
Lastly,if you're using native code in an Eclipse application, it's a very goodidea to isolate the native code in its own bundle. Any plugin thatneeds to use that native functionality can then express a dependency onthat bundle, which allows it to be shared by as many bundles as needit. That way, if you have any non-native updates to your bundle, youdon't need to restart Eclipse to see those changes. Of course, if youhave native bundle updates then you have to restart anyway to get thebenefit.
页:
[1]