Java类加载体系

2019-03-27 19:28|来源: 网络

           

早期的java程序员可能只要懂基本语法,还有少数的项目经验就可以找到一份比较好的工作。Java和java社区的发展,更多的人了解它,深入它。现在java程序员了解一些语法我看还远远不够了,对于jvm的了解和深入是非常重要的。网民的增多,网站的刚性需求,很多网站面临高性能,高并发等等一系列的问题。没有深入jvm的java程序员是很难写出高质量高并发的代码(也许一棒子打死所有人了,但我想绝大部分是肯定的)。

Osgi也许你并不陌生,但是他底层的实现机制你可能没去了解过。如果你是个打破砂锅问到底的人,你肯定会想知道osgi是如何做到的。但是你没有了解jvm的类加载体系,你肯定很难理解osgi是如果做到类隔离等一系列的问题。不过想完整理解osgi还需要其他很多方面的知识,但是它基本的机制还是的了解jvm的类加载机制。

Java类库有些包只是定义了一个标准,具体的实现都是由具体的供应商来提供。Java与数据库连接就是一个很好的例子。Java.sql类库只是定义了java与数据库连接的标准,那么与mysql就需要msyql的驱动,oracle就需要oracle的驱动,而java.sql类库是由bootstrap classloader加载,驱动包中的类是由system classloader来加载,不同类加载器加载的类是无法相互认识,所以自然也无法正常提供功能了。jvm又是提供了什么机制让他们交互呢?如果你确实对这些问题毫无头绪的话,那么我觉得你真的要好好理解下jvm类加载体系。

这篇文章主要是介绍下jvm类加载的机制基础知识。关于其他相关涉及,有时间的话,我会单独写文章来介绍。

1 java类加载器


1.1 Bootstrap classloader:sun jdk是用c++实现,所以在代码中你是无法获取此加载器的实例。此加载器主要负责加载$JAVA_HOME/jre/lib/rt.jar。java类中获取结果为null,这里可以用一个例子跑下证明:

public class Test {

        public static void main(String[] arg) throws Exception{

                  ClassLoader classloader = Test.class.getClassLoader();

                  System.out.println(classloader);

                  System.out.println(classloader.getParent());

                  System.out.println(classloader.getParent().getParent());

        }

}

输出结果:

sun.misc.Launcher$AppClassLoader@19821f

sun.misc.Launcher$ExtClassLoader@addbf1

null

最后输出的null就是代表bootstrap classloader。

1.2 Extension classloader:主要加载扩展功能的jar,$JAVA_HOME/jre/lib/ext/*.jar。

1.3 System classloader:加载claspath中的jar包。

1.4自定义 classloader:主要加载你指定的目录中的包和类。

2 双亲委派模型

系统运行时,我们请求加载String类,此时System Classloader自己不找classpath中的包,把请求转发给Extension Classloader,但它也不做查找,又转发给Bootstrap Classloader,但是它发现自己没有parent了。于是他在rt.jar包中找到String类并加载到jvm中提供使用。Jvm为什么要这么实现呢?其实和java安全体系有关。假设jvm不是这么实现,我们自定义一个String类,做一些破坏,那么运行jvm的机器肯定要受到损坏。具体例子:

public class String {

        public static void main(String[] args) {

                  System.out.println("hello world");

        }

}

我们运行自定义String类的时候报错了,说没有main方法,可我们定义的明明有的,嘿嘿,委派机制的缘故最后加载到的是由bootstrap classloader在rt.jar包中的String,那个类是没有main方法,因此报错了。


3 类隔离

jvm中的类是:类加载器+包名+类名。比如:URLClassLoader1,URLClassLoader2分别加载com.test.Test的时候会加载两次,因为每个classloader中的类对于其他classloader来说是隔离的,不认识的。例子:

import java.net.URL;
import java.net.URLClassLoader;

public class CustomClassloaderTest {
   
public static void main(String[] args) throws Exception {
        URL url =
new URL("file:/g:/");
        URLClassLoader ucl =
new URLClassLoader(new URL[]{url});
        Class c = ucl.loadClass("Yang");
        c.newInstance();
        System.out.println(c.getClassLoader());

        URLClassLoader ucl2 =
new URLClassLoader(new URL[]{url});
        Class c2 = ucl2.loadClass("Yang");
        c2.newInstance();
        System.out.println(c2.getClassLoader());
    }
}

大家把Yang类存在g盘下。

public class Yang {
   
static {
        System.out.println("Yang");
    }
}

运行结果:

Yang

java.net.URLClassLoader@c17164

Yang

java.net.URLClassLoader@61de33

看到每次加载Yang类的时候都输出Yang,说明Yang类被加载了两次。

如果你不确信,可以修改下代码,让同一classloader加载Yang类两次

import java.net.URL;
import java.net.URLClassLoader;

public class CustomClassloaderTest {
   
public static void main(String[] args) throws Exception {
        URL url =
new URL("file:/g:/");
        URLClassLoader ucl =
new URLClassLoader(new URL[]{url});
        Class c = ucl.loadClass("Yang");
        c.newInstance();
        System.out.println(c.getClassLoader());

        Class c2 = ucl.loadClass("Yang");
        c2.newInstance();
        System.out.println(c2.getClassLoader());
    }
}

看看输出结果:

Yang

java.net.URLClassLoader@c17164

java.net.URLClassLoader@c17164

结果中只输出了一次Yang。因此可以证明我们最开始说的类隔离。

4 线程上下文类加载器

我们理解了双亲委派模型,那么目前只有由下向上单向寻找类(system->extension->bootstrap)

。我们在最开始的时候说过,java.sql包中的类由bootstrap或extension classloader加载,而mysql驱动包是在classpath中由system来加载,但bootstrap中的类是无法找到system classloader中的类,此时靠线程上下文类加载器来解决。线程上下文类加载器主要就是能让jvm类加载模型具有了向下寻找的可能,bootstrap->extension->system,如果不做任何设置,线程上下文类加载器默认是system classloader。本来这里想写一个例子的,可是有点麻烦,所以下次单独写一篇关于这方面的知识。



相关问答

更多
  • java -Xmx20480M -Xms2048M 第一个参数的 - 是错误的、是全角的,换成英文的短横线
  • MessageFormat提供了手段,生产语言无关的方式连接在一起的消息。使用此构造为最终用户显示的消息。 SimpleDateFormat的是用于格式化和解析语言环境敏感的方式日期的具体类。它允许进行格式化(日期 - >“文本),分析(文本 - >”日期),并归 Calendar类是一个抽象类,它提供了在一个特定的时间和瞬间,如年的日历字段集转换方法,月,DAY_OF_MONTH,小时和等,用于操作日历领域,如获取的日期,在未来一周
  • CLASSPATH 的:后面少了分号(;),试试,我不知道是不是这的问题
  • B.getA()这方法怎么实现的?A是接口,所以直接newInstance是不行的。 你总得有个具体的实现A接口的类吧
  • 这里有一个例子: package foo; public class Test { public static void main(String[] args) { ClassLoader loader = Test.class.getClassLoader(); System.out.println(loader.getResource("foo/Test.class")); } } 这打印出来 file:/C:/Users/Jon/Test/f ...
  • 我想你最好的办法是做以下事情: 一旦您的main方法启动并在结束之前输出一些固定的文本。 将详细输出管道输入到文件中 使用较少或grep的东西可以从main方法中找到两个标签之间加载的类。 有一个类似的问题和一些答案在这里: 有没有办法得到哪个类加载ClassLoader? 你有没有尝试过-verbose:class ? I guess your best bet is to do the following: Output some fixed text once your main method sta ...
  • 我在JRebel(http://www.zeroturnaround.com/jrebel/)方面取得了巨大的成功。 这是一个非常好的产品,可以对Java类的大多数修改实现无缝类重新加载。 没有重新启动应用服务器甚至是所需的应用程序,只需在后台重新加载类。 它有一个免费的30天试用版,所以你可以看看它是否适合你。 (免责声明:我与Zero Turnaround没有任何关系) I've had great success with JRebel (http://www.zeroturnaround.com/j ...
  • import语句与类加载无关 - 它只是允许使用短名称Bar而不是完全限定的foobar.Bar 。 如果foobar.jar和naughty.jar都包含具有完全限定名称的类foobar.Bar ,则classloader将从第一个jar文件中加载具有所需class的类。 The import statement has nothing to do with classloading - it just allows you to use short name Bar instead of the ful ...
  • 添加线 package java.lang 在您的代码之上,然后再次检查。 你会发现你现在得到了预期的结果。 原因是,类名始终仅用作完全限定名,包括包名和类名。 因此,在您的情况下, String是一个与java.lang.String不同的类,因此不会在rt.jar找到 add the line package java.lang on top of your code, and check again. You will notice that you get your expected result ...
  • 这两个可以帮助你: 从onjava.com加载类 从ibm.com/developerworks加载类 these 2 can help u: classloading from onjava.com classloading from ibm.com/developerworks