java底层学习

时间:2024-08-06 22:35:26

额,马上就要面试了,Java的底层肯定是需要了解的。网上找了找java的底层文章,做个记号。java底层主要是类的加载、连接和初始化。

本文主要分为四个方面:

(1)java底层概述

(2)new和newInstance()方法的区别

(3)深入探讨java的加载机制

(4)一个完整java程序冲Javaxxx.class执行的完整过程

四个部分都是来自网上的资料,四个部分看完,应该对java的底层有些了解了。

下面文字来自《疯狂java讲义》:

1.类的加载、连接和初始化

当系统主动使用某个类,如果该类还未加载到内存中,系统会加载、连接、初始化三个步骤。
1.类的加载将类的Class文件读入内存中,并为之创建一个java.lang.Class对象。
Class文件的来源:
(1)从本地加载class文件
(2)从jar包中加载(系统api)
(3)从网络加载
2.类的连接:
连接阶段负责将类的二进制数据合并到JRE中。
3.类的初始化
类的初始化时机:
(1)创建类的实例:new操作符、反射创建实例、通过反序列化;
(2)调用某个类的静态方法;
(3)访问某个类的静态属性(final属性除外)。

2.new操作符和newInstance()方法的区别

http://lvqingboy-163-com.iteye.com/blog/657599

在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,最主要有什么区别?它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。那么为什么会有两种创建对象方式?这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想。

    Java中工厂模式经常使用newInstance()方法来创建对象,因此从为什么要使用工厂模式上可以找到具体答案。 例如:
    class c = Class.forName(“Example”);
    factory = (ExampleInterface)c.newInstance();

    其中ExampleInterface是Example的接口,可以写成如下形式:
    String className = "Example";
    class c = Class.forName(className);
    factory = (ExampleInterface)c.newInstance();

    进一步可以写成如下形式:
    String className = readfromXMlConfig;//从xml 配置文件中获得字符串
    class c = Class.forName(className);
    factory = (ExampleInterface)c.newInstance();

    上面代码已经不存在Example的类名称,它的优点是,无论Example类怎么变化,上述代码不变,甚至可以更换Example的兄弟类Example2 , Example3 , Example4……,只要他们继承ExampleInterface就可以。

    从JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载javaAPI的那个加载器。

    现在可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。 这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段。

    最后用最简单的描述来区分new关键字和newInstance()方法的区别:
    newInstance: 弱类型。低效率。只能调用无参构造。
    new: 强类型。相对高效。能调用任何public构造。

我的理解:

其实,newInstance()方法是显式加载:

Class 类的 forName (String s)方法把自定义类 TestClass 加载进来,并通过 newInstance ()方法把实例初始化

new操作符是隐式加载:JRE在执行到 new 关键字的时候就会把对应的实例类加载进入内存。

3.深入讨论java的类加载器机制

http://www.blogjava.net/William/archive/2006/08/25/65804.html

Java 语言是一种具有动态性的解释型编程语言,当指定程序运行的时候, Java 虚拟机就将编译生成的 . class 文件按照需求和一定的规则加载进内存,并组织成为一个完整的 Java 应用程序。 Java 语言把每个单独的类 Class 和接口 Implements 编译成单独的一个 . class 文件,这些文件对于 Java 运行环境来说就是一个个可以动态加载的单元。正是因为 Java 的这种特性,我们可以在不重新编译其它代码的情况下,只编译需要修改的单元,并把修改文件编译后的 . class 文件放到 Java 的路径当中,等到下次该 Java 虚拟机器重新激活时,这个逻辑上的 Java 应用程序就会因为加载了新修改的 .class 文件,自己的功能也做了更新,这就是 Java 的动态性。

下面用一个简单的例子让大家对 Java 的动态加载有一个基本的认识:

  1. class TestClassA{
  2. publicvoid method(){
  3. System.out.println("LoadingClassA");
  4. }
  5. }
  6. public class ClassLoaderTest {
  7. publicstatic void main(String args[]){
  8. TestClassAtestClassA = new TestClassA();
  9. testClassA.method();
  10. }
  11. }

编译后输入命令: java -verbose:class ClassLoaderTest ,执行文件。 (java-verbose:class:查看class的加载情况)

输出结构如图 (1)

图( 1 )

从运行结果我们可以看到, JRE ( JavaRuntime Environment )首先加载 ClassLoaderTest文件,然后再加载 TestClassA 文件,从而实现了动态加载。

1. 预先加载与依需求加载

Java 运行环境为了优化系统,提高程序的执行速度,在 JRE 运行的开始会将 Java 运行所需要的基本类采用预先加载( pre-loading )的方法全部加载要内存当中,因为这些单元在 Java 程序运行的过程当中经常要使用的,主要包括 JRE 的 rt.jar 文件里面所有的 .class 文件。

当 java.exe 虚拟机开始运行以后,它会找到安装在机器上的 JRE 环境,然后把控制权交给 JRE , JRE 的类加载器会将 lib 目录下的 rt.jar 基础类别文件库加载进内存,这些文件是 Java 程序执行所必须的,所以系统在开始就将这些文件加载,避免以后的多次 IO 操作,从而提高程序执行效率。

图( 2 )我们可以看到多个基础类被加载, java.lang.Object,java.io.Serializable 等等。

图( 2 )

相对于预先加载,我们在程序中需要使用自己定义的类的时候就要使用依需求加载方法( load-on-demand ),就是在 Java 程序需要用到的时候再加载,以减少内存的消耗,因为 Java 语言的设计初衷就是面向嵌入式领域的。

在这里还有一点需要说明的是, JRE 的依需求加载究竟是在什么时候把类加载进入内部的呢?

我们在定义一个类实例的时候,比如 TestClassA testClassA ,这个时候 testClassA 的值为 null ,也就是说还没有初始化,没有调用 TestClassA 的构造函数,只有当执行 testClassA = new TestClassA() 以后, JRE 才正真把TestClassA 加载进来。

2.隐式加载和显式加载

隐式加载:使用new操作符;

显示加载:使用forname()方法:Class 类的 forName (String s) 方法把自定义类 TestClass 加载进来,并通过 newInstance ()方法把实例初始化

Java 的加载方式分为隐式加载( implicit)和显示加载( explicit ),上面的例子中就是用的隐式加载的方式。所谓隐式加载就是我们在程序中用 new 关键字来定义一个实例变量, JRE 在执行到 new 关键字的时候就会把对应的实例类加载进入内存。隐式加载的方法很常见,用的也很多, JRE 系统在后台自动的帮助用户加载,减少了用户的工作量,也增加了系统的安全性和程序的可读性。

相对于隐式加载的就是我们不经常用到的显示加载。所谓显示加载就是有程序员自己写程序把需要的类加载到内存当中,下面我们看一段程序:

  1. class TestClass{
  2. publicvoid method(){
  3. System.out.println("TestClass-method");
  4. }
  5. }
  6. public class CLTest {
  7. publicstatic void main(String args[]) {
  8. try{
  9. Classc = Class.forName("TestClass");
  10. TestClassobject = (TestClass)c.newInstance();
  11. object.method();
  12. }catch(Exceptione){
  13. e.printStackTrace();
  14. }
  15. }
  16. }

我们通过 Class 类的 forName (String s) 方法把自定义类 TestClass 加载进来,并通过 newInstance ()方法把实例初始化。事实上 Class 类还很多的功能,这里就不细讲了,有兴趣的可以参考 JDK 文档。

Class 的 forName() 方法还有另外一种形式:Class forName(String s, boolean flag, ClassLoader classloader) , s 表示需要加载类的名称,flag 表示在调用该函数加载类的时候是否初始化静态区, classloader 表示加载该类所需的加载器。

forName (String s) 是默认通过ClassLoader.getCallerClassLoader() 调用类加载器的,但是该方法是私有方法,我们无法调用,如果我们想使用 Class forName(String s, boolean flag, ClassLoader classloader) 来加载类的话,就必须要指定类加载器,可以通过如下的方式来实现:

Test test = new Test();//Test 类为自定义的一个测试类;

ClassLoader cl = test.getClass().getClassLoader();

//获取 test 的类装载器;

Class c =Class.forName("TestClass", true, cl);

因为一个类要加载就必需要有加载器,这里我们是通过获取加载 Test 类的加载器 cl 当作加载 TestClass 的类加载器来实现加载的。

3. 自定义类加载机制

之前我们都是调用系统的类加载器来实现加载的,其实我们是可以自己定义类加载器的。利用 Java 提供的 java.NET.URLClassLoader 类就可以实现。下面我们看一段范例:

  1. try{
  2. URLurl = new URL("file:/d:/test/lib/");
  3. URLClassLoaderurlCL = new URLClassLoader(new URL[]{url});
  4. Classc = urlCL.loadClass("TestClassA");
  5. TestClassAobject = (TestClassA)c.newInstance();
  6. object.method();
  7. }catch(Exceptione){
  8. e.printStackTrace();
  9. }

我们通过自定义的类加载器实现了 TestClassA 类的加载并调用 method ()方法。分析一下这个程序:首先定义 URL 指定类加载器从何处加载类, URL 可以指向网际网络上的任何位置,也可以指向我们计算机里的文件系统 ( 包含 JAR 文件 ) 。上述范例当中我们从 file:/d:/test/lib/ 处寻找类;然后定义 URLClassLoader 来加载所需的类,最后即可使用该实例了。

4. 类加载器的阶层体系

讨论了这么多以后,接下来我们仔细研究一下 Java 的类加载器的工作原理:

当执行 java ***.class 的时候, java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类加载器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰写而成,这个 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。这里要请大家注意的是, Launcher$ExtClassLoader.class 与Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。

下面的图形可以表示三者之间的关系:

父类

父类

载入

载入

BootstrapLoader

PARENT

AppClassLoader

PARENT

ExtClassLoader

这三个加载器就构成我们的 Java 类加载体系。他们分别从以下的路径寻找程序所需要的类:

BootstrapLoader :      sun.boot.class.path

ExtClassLoader:                java.ext.dirs

AppClassLoader:               java.class.path

这三个系统参量可以通过 System.getProperty() 函数得到具体对应的路径。大家可以自己编程实现查看具体的路径。

5. 总结

了解 Java 的类加载机制对我们熟练灵活运用 Java 语言,提高程序的运行效率有着非常重要的作用,知其然也要知其所以然,这样才能从整体提高程序的质量。

4.深入java底层

http://bigwhite.blogbus.com/logs/579744.html

在一个朋友的书架上发现王森著的《Java深度历险》一书,看了书的前言了解该书是关于Java底层技术内幕的。怀着好奇心浏览了一下,谈不上有太多收获,但也记下了一些自认为有益的两点。

Java xxx

我们在命令行下敲入:“java xxx”后会发生什么呢?

流程如下:

1.        找到JRE;

2.        找到JVM.dll;

3.        启动JVM,并进行初始化;

4.        产生Bootstrap Loader;

5.        载入ExtClassLoader;(Ext – Extended)

6.        载入AppClassLoader;

7.        加载xxx类。

书中提到Bootstrap Loader、ExtClassLoader和AppClassLoader构成了Java的“类加载器继承体系--class loader hierarchy”,其中BootstrapLoader是由C++编写的,其他两个是由Java写的。之所以成为“继承体系”是因为这三个loader之间是有联系的。Bootstrap Loader负责加载ExtClassLoader,后者ExtClassLoader就将其parent置为Bootstrap Loader。AppClassLoader较为特殊,虽然由Bootstrap载入,但是其parent却置为ExtClassLoader。其原因是为了实现“委托模型”。简述“委托模型”就是当类加载器有加载类的需求时,会先请求其parent使用其搜索路径帮助加载,如果其parent找不到,才使用自己的搜索路径进行加载。如上述所说当ExtClassLoader想载入AppClassLoader类时它首先请求其parent “Bootstrap Loader”帮忙,Bootstrap Loader将AppClassLoader载入后,由于这个载入是ExtClassLoader请求的,所以AppClassLoader的parent还是置为ExtClassLoader而不是Bootstrap Loader。

类的加载流程

类加载的时候遵循一个原则:“类加载器会依类的继承体系从上至下依次加载”。举个例子:“如果C继承了B并实现了接口I,而B有继承自A”,则类加载器在加载C时,加载的次序会是A-> B->I->C,(注:interface会如同class一样被Java编译器编译为独立的.class文件)

其实我对底层的东西并未给与太多的关注,如果在哪个项目中需要我去了解底层的话,我会去很好的学习。

额,顺便看看什么事DLL

http://baike.baidu.com/view/887.htm

动态链接库英文为DLL,是DynamicLink Library 的缩写形式,DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个DLL 副本的内容。DLL 是一个包含可由多个程序同时使用的代码和数据的库。