• Eclipse a tale of two VMs (and many classloaders) (转载)


    When starting off with Eclipse plugin development or rich client platform
    development, you're more than likely to run into issues like
    ClassNotFoundException or problems with the Java command line and
    properties like java.endorsed.dirs.


    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.


    Eclipse Boot Process


    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
    osgi.configuration.area and osgi.instance.area.) 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.)


    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. for i in
    lib/*.jar
    ), 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.


    To solve this issue (and others), Eclipse doesn't use a shell script to boot
    itself. Instead, it uses a native executable (eclipse.exe or
    eclipse), to parse the command-line arguments (like
    -vmargs) 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 in a new process (the platform VM), and hands the rest of
    the booting process over.


    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 System.exit()
    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
    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 into the platform VM. The -vmargs flag is used to pass
    any subsequent arguments as VM arguments rather than normal arguments.


    All of this means that when launching Eclipse via startup.jar
    the arguments and extra options are effectively ignored by the actual Eclipse
    runtime instance. You may think that:

    eclipse -Xmx1024m 

    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 eclipse
    launcher. The native launcher deals with some arguments directly; but otherwise,
    they're passed on verbatim to the Java launcher
    org.eclipse.core.launcher.Main where the real work is done. The
    arguments that the native launcher explicitly deals with are:



    -vmargs
    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 C:\Program Files\Eclipse in them. This is available
    from the eclipse.vmargs system property, but it cannot be changed
    once the VM is started.
    -vm
    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
    eclipse.vm system property, but it cannot be changed once the VM is
    started.

    To run Eclipse with a larger memory area, you need to pass the
    -vmargs to the launcher, followed by the options that you want:

    eclipse arg1 arg2 arg3 -vmargs -Xmx1024m
    

    this instructs the launcher to create the platform VM, passing in arguments
    arg1 arg2 arg3 and setting up the platform VM with
    -Xmx1024m. However, we could equally have had a file called
    eclipse.ini:

    arg1
    arg2
    arg3
    -vmargs
    -Xmx1024m

    which would have the same effect. The launcher looks for
    application.ini and eclipse.ini to derive these
    parameters, if your executable 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 the boot process. The Main 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 OSGi platform, which reads which plugins to start from the
    osgi.bundles entry from the config.ini, and applies
    product branding from -product and OS/Language specific settings
    from -os and -nl. Once that's running, the Eclipse
    platform task over which locates and loads the bundle associated with the
    -application or -feature arguments. 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 use the
    EclipseStarter
    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 WebStartMain
    that can be used to kick off an Eclipse install via Java WebStart (although with
    some
    limitations
    ).


    There's a full list of the arguments and when they are interpreted in the Eclipse
    help pages
    under "runtime
    options
    ", and can 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 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
    ClassLoader. Its job is to turn a sequence of bytes into a
    java.lang.Class. Where those bytes 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) have long used
    ClassLoaders 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 ClassLoader also provides another key benefit; it
    separates out classes with the same name. Every class in Java has an associated
    ClassLoader (and you can find out which 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 loaded into a VM
    at once, provided that they have two separate ClassLoaders 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 ClassLoader, 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.


    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.


    To do this, each bundle (OSGi) or plugin (Eclipse) has its own
    ClassLoader. When a bundle is started, it gets given its own
    ClassLoader for its lifetime. When it's stopped and restarted, a
    new ClassLoader is created. This allows the new bundle to have a
    different set of classes than the previous version.


    The OSGi platform translates the Manifest.MF present in every
    (OSGi-enabled) plugin into a hierarchy of ClassLoaders. 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. org.eclipse.ui) will
    result in the same class being loaded, because it comes from
    org.eclipse.ui's ClassLoader.


    This causes confusion for new developers to the Eclipse platform. It doesn't
    matter what's in the CLASSPATH environment variable, or what's
    specified on the -classpath argument, because pretty much every
    ClassLoader only sees what its Manifest.MF 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 .internal.
    packages work; whilst they exist in the bundles, and can be loaded by the
    bundle's ClassLoader, the Eclipse class loading mechanism hides any
    package that's not explicitly mentioned in the Export-Package
    header in the Manifest.MF. It also doesn't help that the PDE
    runtime only consults the entries in the .classpath when
    running/debugging inside Eclipse, and it's only when you try to export the
    product that the Manifest.MF is consulted, often resulting in
    errors. A ClassNotFoundException during product/plugin exporting is
    almost always down to the fact that it's mentioned in the
    .classpath (Java Build path) but not the
    Manifest.MF.


    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
    log4j and generator tools such as JibX that both
    supply services and consume code. Because Eclipse uses a partitioned class
    loading mechanism, the ClassLoader of the
    org.apache.log4j can't see the classes defined in
    org.example.myplugin, since it's a separate
    ClassLoader. As a result, log4j can't use your
    supplied class for logging, and complains.


    This is the purpose of the buddy class loading 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 Eclipse-BuddyPolicy entry)
    takes one of:



    dependent
    search the dependent's classloaders
    global
    search the global packages exported via Export-Package
    app
    search the application's classloader
    ext
    search the extension's classloader
    boot
    search the boot's classloader
    registered
    search the registered buddies classloaders






    Buddy classloading diagram
    Figure 1. Buddy classloader
    diagram



    Whilst all of these are generally fine, if you want to use a tool like
    log4j then it generally boils down to:

    # log4j Manifest.MF
    Bundle-Name: org.apache.log4j
    Bundle-Version: 1.2.13
    ...
    Eclipse-BuddyPolicy: registered

    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:

    # myplugin Manifest.MF
    Bundle-Name: org.example.myplugin
    Bundle-Version: 1.0.0
    Requires-Bundle: org.apache.log4j,...
    ...
    Eclipse-RegisterBuddy: org.apache.log4j

    This now makes the org.apache.log4j and
    org.example.myplugin buddies. Of course, our
    org.example.myplugin always depended on
    org.apache.log4j before, but now we've added the extra hook back
    that allows the log4j to see our classes as well. In essence, it's
    like adding Requires-Bundle: org.example.myplugin to the
    org.apache.log4j 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
    Eclipse-BuddyPolicy: registered into the definition, now any future
    bundle can plug in 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 someone might need
    it.


    Native code and classloaders


    As well as resolving classes, the ClassLoader is also
    responsible 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 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.


    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 java.library.path or the contents of a
    packed Jar file), once the operating system only knows about the
    PATH or LD_LIBRARY_PATH environment variables
    (depending on your operating system). So if you have a.dll which
    depends on b.dll, and you do 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.dll and gives up. Instead, if you did
    System.loadLibrary("b"); System.loadLibrary("a"); then the DLL is
    available in memory for a.dll 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.


    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 ClassLoader. If you have two ClassLoaders,
    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
    ClassLoader 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.


    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 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
    System.loadLibrary(), you can define Bundle-NativeCode:
    swt1234.dll
    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 Eclipse-PlatformFilter: (& (osgi.ws=win32) (osgi.os=win32)
    (osgi.arch=x86))
    ).


    Conclusion


    Once you understand how the processes work within the Eclipse boot sequence,
    and the fact that it utilises many classloaders, some of the
    ClassNotFoundExceptions become easier to understand. Specifying
    arguments works on a command line java invocation because you're
    specifying the VM on the command line (and most of the time, only using one
    ClassLoader too). In the Eclipse world, if you want to affect
    Eclipse's VM, you need 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 ClassNotFoundExceptions
    when exporting or running an application outside of Eclipse's debugger, that you
    have the dependencies listed in Manifest.MF and not just in the
    .classpath (Java Build Path). In fact, you shouldn't need to put
    any extra entries in the .classpath if you're building a plugin
    project, because the entries in Manifest.MF are 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
    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.

    转载自:

    http://www.eclipsezone.com/articles/eclipse-vms/

    http://www.eclipsezone.com/eclipse/forums/t111987.html

  • 相关阅读:
    MySQL
    MySQL -数据库备份
    MySQL
    MySQL
    MySQL
    MySQL
    MySQL
    MySQL
    MySQL
    53端口反弹shell
  • 原文地址:https://www.cnblogs.com/wuhenke/p/2412692.html
Copyright © 2020-2023  润新知