manifest.mf文件的class-path加载不到_Pinpoint 类加载介绍

本文介绍了Pinpoint作为一个非侵入式的Java APM工具,其类加载原理如何确保安全加载核心类。文章详细探讨了JVM的类加载器,包括Boot Class Loader、Ext Class Loader和App Class Loader,以及双亲委托机制。同时,讨论了Thread Context ClassLoader如何打破双亲委托限制。通过对Pinpoint的类加载器分析,特别是PinpointURLClassLoader和ProfilerPluginClassLoader,阐述了Pinpoint如何加载和初始化组件。最后,文章讲解了Pinpoint如何利用SPI加载插件,并展示了加载过程中类加载器如何工作,帮助读者深入理解类加载机制。
摘要由CSDN通过智能技术生成

Pinpoint是当前APM非侵入采集的代表;Pinpoint实际上是一个非常强大的javaagent,对Pipoint中类加载原理进行理解有助于对整个Pinpoint非侵入式探针有一个全面的系统的认知。

 Classloder 简介


JVM默认class loader

JVM默认有三个class loader, boot class loader, ext class loader 和App class loader,

boot class loader 是所有类加载器的根,在JVM启动时负责加载JVM内核(具体可以通过System.getProperty("sun.boot.class.path")查看具体加载哪些类文件) boot class loader 在系统中是不可获得的, 例如在通过其加载的类Sting.class.getClassLoader()时,可以看到获得的class loader 为null。

但是我们可以通过Java agent的入口参数Instrument接口提供的方法 appendToBootstrapClassLoaderSearch 向 boot class path中添加jar包,这样的这个jar中的类 在被使用的时候就会被boot class loader 加载。

ext class loader 负责加载 JVM扩展类 可以通过 System.getProperty("java.ext.dirs")查看具体加载的jar包 ext class loader的parent是null,实际上是boot class loader。 可以通过ClassLoader.getSystemClassLoader().getParent().getParent()进行验证。

App class loader 负责加载 class path下所有的类,可以通过 System.getProperty("java.class.path")查看具体加载的jar路径 加载路径ClassLoader.getSystemClassLoader() 就是App class loader App class loader的parent是 ext class loader. ClassLoader.getSystemClassLoader().getParent()进行验证。

####Class Loader的双亲委托机制。 直接贴一段java.lang.ClassLoader的代码

 protected Class> loadClass(String name, boolean resolve)

            throws ClassNotFoundException

        {

            synchronized (getClassLoadingLock(name)) {

                // First, check if the class has already been loaded

                Class> c = findLoadedClass(name);

                if (c == null) {

                    long t0 = System.nanoTime();

                    try {

                        if (parent != null) {

                            c = parent.loadClass(name, false);

                        } else {

                            c = findBootstrapClassOrNull(name);

                        }

                    } catch (ClassNotFoundException e) {

                        // ClassNotFoundException thrown if class not found

                        // from the non-null parent class loader

                    }

                    if (c == null) {

                        // If still not found, then invoke findClass in order

                        // to find the class.

                        long t1 = System.nanoTime();

                        c = findClass(name);

                        // this is the defining class loader; record the stats

                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

                        sun.misc.PerfCounter.getFindClasses().increment();

                    }

                }

                if (resolve) {

                    resolveClass(c);

                }

                return c;

            }

        }

从上面代码中很容易看出,首先在系统中查看该类是否已经加载,如果已经加载则直接返回,如果没有加载,则根据parent是否是null由parent加载还是bootstrap classloader加载, 如果没有加载到,则由自己加载。这个就是双亲委托机制。那么这么做的好处是什么呢?

请尝试思考一下,如果不是用双亲委托机制,当一个用户自己定义了一个 java.long.String 类的时候,并放在classpath路径下的时候,如果子classloader优先加载的时候, jvm就会加载用户自定义的java.lang.String, 这里只是举了一个简单的例子,如果某些核心的类被别有用心的人劫持很可能发生意想不到的灾难。

Thread Context classloader 简介


Thread Context classloader打破了双亲委托机制的限制,父classloader可以使用当前线程的classloader加载类,颠覆了父classloader不能使用子classloader或者没有 直接父子关系的classloader中加载类这种情况。在双亲委托机制的下,当 A 类使用了B类的时候, B 类必须在 A类的class loader及classloader parent 的classpath之内; 否则会报 Class Not Found Exception。

这么说可能比较抽象,以SPI (JDBC Driver)为例来进行说明; 这里有必要说明一下SPI(Service Provider Interface), 常见的SPI主要有, JDBC, JCE, JNDI, JAXP, JBI等。这些SPI的接口都是由Java核心库来提供,而这些SPI具体 的实现确是由各个厂商实现,所以当我们在使用的时候,SPI的接口是由Bootstrap classloader来加载,而具体的实现确是由App classloader加载的。

下面以JDBC 来分析一下SPI类的加载过程,看完下面的介绍,相信大家会对classloader有一个新的认识。

    Driver driver = Class.forName("com.mysql.jdbc.Driver").newInstance()

    Connection conn = driver.getConnection("jdbc:mysql://host:port/db");

这是以前没有使用SPI的一个Mysql JDBC链接的经典写法。 那么在Java6之后,我们就只需要把mysql-connector.jar加到classpath中之后,像下面这样来创建JDBC链接

  Connection conn = java.sql.DriverManager.getConnection("jdbc:mysql://host:port/db")

z这个代码是如何加载到Mysql 的Driver的呢?我们就需要从DriverManager这个类入手了。

public class DriverManager {

    // List of registered JDBC drivers

    private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();

    /* Prevent the DriverManager class from being instantiated. */

    private DriverManager(){}

    /**

     * Load the initial JDBC drivers by checking the System property

     * jdbc.properties and then use the {@code ServiceLoader} mechanism

     */

    static {

        loadInitialDrivers();

        println("JDBC DriverManager initialized");

    }

    private static void loadInitialDrivers() {

            String drivers;

            try {

                drivers = AccessController.doPrivileged(new PrivilegedAction() {

                    public String run() {

                        return System.getProperty("jdbc.drivers");

                    }

                });

            } catch

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值