【1】类的生命周期

一个类从加载进内存到卸载出内存为止,一共经历7个阶段:

加载—>验证—>准备—>解析—>初始化—>使用—>卸载

其中,类加载包括5个阶段:

加载—>验证—>准备—>解析—>初始化

在类加载的过程中,以下3个过程称为连接:

验证—>准备—>解析

因此,JVM的类加载过程也可以概括为3个过程:

加载—>连接—>初始化

C/C++在运行前需要完成预处理、编译、汇编、连接。而在Java中,类加载(加载、连接、初始化)是在程序运行期间完成的。

在程序运行期间进行类加载会稍微增加程序的开销,但随之会带来更大的好处-----提高程序的灵活性。

Java语言的灵活性体现在它可以在运行期间动态扩展,所谓动态扩展就是在运行期间动态加载和动态连接。


【2】类加载的时机

① 类加载过程中每个步骤的顺序

我们已经知道,类加载的过程包括:加载、连接、初始化。连接又分为:验证、准备、解析。所以说类加载的过程一共分为5步:加载、验证、准备、解析、初始化。

其中加载、验证、准备、初始化的开始顺序是依次进行的,这些步骤开始之后的过程可能会有重叠。

解析过程会发生在初始化过程中。


② 类加载过程中“初始化”开始的时机

JVM规范中只定义了类加载过程中初始化过程开始的时机,加载、连接过程都应该在初始化之前开始(解析除外)。这些过程具体在何时开始,JVM规范并没有定义,不同的虚拟机可以根据具体的需求自定义。

初始化开始的时机

  1. 在运行过程中遇到如下字节码指令时,如果类尚未初始化,那就要进行初始化:new、getstatic、putstatic、invokestatic等。这四个指定对应的Java代码场景是:
  • 通过new创建对象;
  • 读取、设置一个类的静态成员变量(不包括final修饰的静态变量);
  • 调用一个类的静态成员函数,即静态方法;
  1. 使用java.lang.reflect进行反射调用的时候,如果类没有初始化,那就需要初始化。

  2. 当初始化一个类的时候,若其父类尚未初始化,那就先要让其父类初始化,然后再初始化本类。

  3. 当虚拟机启动时,虚拟机会首先初始化带有main方法的类,即主类。


③ 主动引用与被动引用

JVM规范中要求在程序运行过程中,“当且仅当”出现上述4个条件之一的情况才会初始化一个类。如果间接满足上述初始化条件是不会初始化类的。

其中,直接满足上述初始化条件的情况叫做主动引用;间接满足上述初始化过程的情况叫做被动引用。

那么,只有当程序在运行过程中满足主动引用的时候才会初始化一个类,若满足被动引用就不会初始化一个类。


④ 被动引用的场景示例

示例一:

public class Fu{public static String name="柴毛毛";static{System.out.println("父类被初始化!");}
}public class Zi extends Fu{static{System.out.println("子类被初始化!");}
}public TestClass{public void static main(String[] args){System.out.println(Zi.name);}
}

测试结果:

父类被初始化!
柴毛毛

原因分析:

本示例看似满足初始化时机的第一条:当要获取某一个类的静态成员变量的时候如果该类尚未初始化,则对该类进行初始化。

但由于这个静态成员变量属于Fu类,Zi类只是间接调用Fu类中的静态成员变量,因此Zi类调用name属性属于间接引用。而Fu类调用name属性属于直接引用,由于JVM只初始化直接引用的类,因此只有Fu类被初始化。

这里需要注意的是,main方法在其他类中,如果在Zi类中执行main方法,JVM同样会初始化Zi类:

public class Zi extends Fu {static{System.out.println("子类被初始化!");}public static void main(String[] args){System.out.println(Zi.name);}
}

此时输出结果:

父类被初始化!
子类被初始化!
柴毛毛

示例二:

public class A {public static void main(String[] args){Fu[] arr = new Fu[10];}
}

输出结果:为空,并没有输出“父类被初始化!”。

原因分析:

这个过程看似满足初始化时机的第一条:遇到new创建对象时若类没被初始化,则初始化该类。

但现在通过new要创建的是一个数组对象,而非Fu类对象,因此也属于间接引用,不会初始化Fu类。


示例三:

public class Fu {public static final String name="柴毛毛";static{System.out.println("父类被初始化!");}
}public class A {public static void main(String[] args){System.out.println(Fu.name);}
}

输出结果:

柴毛毛

原因分析:

本示例看似满足类初始化时机的第一个条件:获取一个类静态成员变量的时候若类尚未初始化则初始化类。

但是Fu类的静态成员变量被final修饰,它已经是一个常量。

被final修饰的常量在Java代码编译(java源文件编译成class字节码)的过程中就会被放入引用它的class文件的常量池中(这里是A的常量池)。所以程序在运行期间如果需要调用这个常量,直接去当前类的常量池中取,而不需要初始化这个类。

有说法称之为“常量传播优化”。


示例四:访问以下final修饰的static常量会触发类的初始化。

public class LFim {/** 在复杂类型中只有字符串不会触发初始化过程!!*/public static final String STRING="LangShen";//不会触发初始化过程!!,没有经历自动装箱,//字符串是编译期直接保存在常量池中的!!!//获取值时处理方法和它们不一样,它是一个常量表,//一个字符序列对应一个对象来获取!!!/** 以下都会触发初始化过程!!* * 这些初始化指的是类的初始化!!!*/public static final ClassA CLASSA=null;
//自定义类,会触发初始化过程!,当然你就更别说new 一个对象了,new 一个也会触发初始化过程!!public static final Integer INTEGER=45;//会触发初始化过程!!,因为经历了自动装箱public static final Character CHARACTER='X';//会触发初始化过程!!因为经历了自动装箱public static final String STRING2=new String("456");//会触发初始化过程!!,是new 出来的!!!static{System.out.println("初始化静态代码块!!");}
}

⑤ 接口的初始化

接口和类都需要初始化,接口和类的初始化过程基本一样。

不同点在于:类初始化时,如果发现父类尚未被初始化,则先要初始化父类,然后再初始化自己;但接口初始化时,并不要求父接口已经全部初始化,只有程序在运行过程中用到父接口中的东西时,才初始化父接口。


【3】类加载的过程1—加载

通过之前的介绍可知,类加载过程共有5个步骤,分别是:加载、验证、准备、解析、初始化。其中,验证、准备、解析称为连接。下面详细介绍这5个过程JVM所做的工作。

“加载”是类加载过程的第一步。

① 加载的过程

在加载过程中,JVM主要做3件事:

  • 通过一个类的全限定名来获取这个类的二进制字节流,即class文件:
    在程序运行过程中,当要访问一个类时,若发现这个类尚未被加载,并满足类初始化时机的条件时,就根据要被初始化的这个类的全限定名找到该类的二进制字节流,开始加载过程。

  • 将二进制字节流的存储结构转化为特定的数据结构,存储在方法区中;

  • 在内存中创建一个java.lang.Class类型的对象:
    接下来程序在运行过程中所有对该类的访问都通过这个类对象,也就是这个class类型的类对象是提供给外界访问该类的接口。


② 从哪里加载?

JVM规范对于加载过程给予了较大的宽松度。一般二进制字节流都从已经编译好的本地class文件中读取,此外还可以从以下地方读取:

  • 从压缩包中读取
    如:jar、War、Ear等
  • 从其他文件中动态生成
    如:从JSP文件中生成Class类
  • 从数据库中读取
    将二进制字节流存储进数据库中,然后在加载的时候从数据库中读取。有些中间件会这么做,用来实现代码在集群间分发。
  • 从网络中获取
    从网络中获取二进制字节流,典型的就是Applet。

③ 类和数组加载过程的区别

数组也有类型,称为“数组类型”。如:

String[] str = new String[10];

这个数组的数组类型是Ljava.lang.String,而String只是这个数组中元素的类型。

当程序在运行过程中遇到new关键字创建一个数组时,由JVM直接创建数组类。再由类加载器创建数组中的元素类。

而普通类的加载由类加载器完成。既可以使用系统提供的引导类加载器,也可以使用用户自定义的类加载器。


④ 加载过程的注意点

  • JVM规范并未给出类在方法区中存放的数据结构
    类完成加载后,二进制字节流就以特定的数据结构存储在方法区中,但存储的数据结构是由虚拟机自己定义的,JVM规范并没有指定。

  • JVM规范并没有指定Class对象存放的位置
    在二进制字节流以特定格式存储在方法区以后,JVM会创建一个java.lang.Class类型的对象,作为本类的外部接口。既然是对象就应该存放在堆内存中,不过JVM规范并没有给出限制,不同的虚拟机根据自己的需求存放这个对象。HotSpot将Class对象存放在方法区。

  • 加载阶段和连接阶段是交叉的
    通过之前的介绍可知,类加载过程中每个步骤的开始顺序都有严格限制,但每个步骤的结束顺序没有限制。也就是说,类加载过程中,必须按照以下顺序开始:加载、验证、准备、解析、初始化,但结束顺序无所谓。因此由于每个步骤处理时间的长短不一,就会导致有些步骤会出现交叉。


【4】类加载的过程2—验证

验证阶段比较耗时,它非常重要但不一定必要,如果所运行的代码已经被反复使用和验证过,那么可以使用-Xverify:none参数关闭,以缩短类加载时间。

① 验证的目的

验证是为了确保二进制字节流中信息符合虚拟机规范,并没有安全问题。

② 为什么需要验证

虽然Java语言是一门安全语言,它能确保程序员无法访问数组边界以外的内存、避免让一个对象转换成任意类型,避免跳转到不存在的代码行;如果出现这些情况,编译无法通过。也就是说,Java语言的安全性是通过编译器来保证的。

但是我们知道,编译器(如javac)和虚拟机是两个独立的东西,虚拟机只认二进制字节流,它不会管所获得的二进制字节流是哪来的。当然,如果是编译器给它的,那就相对安全。但如果是从其他途径获得的,那么无法确保该二进制字节流是安全的。

通过上文可知,虚拟机规范中没有限制二进制字节流的来源,那么任意来源的二进制字节流虚拟机都能接受,为了防止字节流中有安全问题,因此需要验证。


③ 验证的过程

  • 文件格式验证

这个阶段主要验证输入的二进制字节流是否符合class文件的结构规范。二进制字节流只有通过了本阶段的验证,才会被允许存入到方法区中。

本验证阶段是基于二进制字节流的,而后面的三个验证阶段都是在方法区中进行,并基于类特定的数据结构的。

通过上文可知,加载开始前,二进制字节流还没有进入方法区,而加载完成后,二进制字节流已经存入方法区。而在文件格式验证前,二进制字节流尚未进入方法区,文件格式验证通过之后才进入方法区。

也就是说,加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储在方法区中,继而开始下阶段的验证和创建Class对象等操作。这个过程印证了:加载和验证是交叉进行的。


  • 元数据验证

本阶段对方法区中的字节码描述信息进行语义分析,确保其符合Java语法规范。

  • 字节码验证

本阶段是验证过程的最复杂的一个阶段。本阶段对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件。

  • 符号引用验证

本阶段验证发生在解析阶段,确保解析能正常执行。


【5】类加载的过程3—准备和解析

① 准备

准备阶段完成两件事情:

  • 为已经在方法区中的类中的静态成员变量分配内存
    类的静态成员变量也存储在方法区中(如果是常量,则编译时存放进引用其的class的常量池中,加载时存入方法区的运行时常量池)。

  • 为静态成员变量设置初始值
    初始值为0、false、null等。

示例一:

 public static String name="柴毛毛";

在准备阶段,JVM会在方法区中为name分配内存空间,并赋上初始值null。给name赋上“柴毛毛”是在初始化阶段完成的。

示例二:

 public static final String name="柴毛毛";

final 用于声明属性、方法和类,分别表示属性一旦被分配内存空间就必须初始化并且以后不可变;方法一旦定义必须有实现代码并且子类里不可被覆盖;类一旦定义不能被定义为抽象类或是接口,因为不可被继承。

被final修饰的常量如果有初始值,那么在编译阶段就会将初始值存入constantValue属性(class文件的常量池)中,在准备阶段就将constantValue的值赋给该字段。如果没有初始值而又没有被static修饰的变量,如private final String seasonName;将在类构造函数中初始化。

当这个属性被修饰为final,而非static的时候,它属于类的实例对象的资源,当类被加载进内存的时候这个属性并没有给其分配内存空间,而只是定义了一个变量a,只有当类被实例化的时候这个属性才被分配内存空间,而实例化的时候同时执行了构造函数,所以属性被初始化了,也就符合了当它被分配内存空间的时候就需要初始化,以后不再改变的条件。

如果是同时被static和final修饰的变量,则只有两个地方初始化:

  • 定义的时候赋值;
  • 静态代码块里面赋值

② 解析

解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。如果不执行,符号解析要等到字节码指令使用这个引用时才会进行。

【6】类加载的过程4—初始化

初始化阶段就是执行类构造器clinit()的过程(不是init())。把类中的变量初始化成合适的值。执行静态初始化程序,把静态变量初始化成指定的值

clinit()方法由编译器自动产生,收集类中static{}代码块中类变量赋值语句和类中静态成员变量的赋值语句。此时将会执行静态代码块和静态方法。

在准备阶段,类中静态成员变量已经完成了默认初始化,而在初始化阶段,clinit()方法对静态成员变量进行显示初始化。

初始化过程的注意点

  • clinit()方法中静态成员变量的赋值顺序是根据Java代码中静态成员变量的出现的顺序决定的。

  • 静态代码块能访问出现在静态代码块之前的静态成员变量,无法访问出现在静态代码块之后的成员变量。

  • 静态代码块能给出现在静态代码块之后的静态成员变量赋值。
    因为在准备阶段已经给静态成员变量进行了默认初始化。

public class Test{static{i=0;//给变量赋值可以正常编译通过System.out.print(i);//这句编译器会提示"非法向前引用"}static int i=1;
}
  • 构造函数init()需要显示调用父类构造函数,而类的构造函数clinit()不需要调用父类的类构造函数,因为虚拟机会确保子类的clinit()方法执行前已经执行了父类的clinit()方法。

因此在虚拟机中第一个被执行的clinit()方法的类肯定是java.lang.Object。由于父类的clinit()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。

  • 如果一个类/接口中没有静态代码块,也没有静态成员变量的赋值操作,那么编译器就不会生成clinit()方法。

  • 接口也需要通过clinit()方法为接口中定义的静态成员变量显示初始化。

  • 接口中不能使用静态代码块。

  • 接口在执行clinit()方法前,虚拟机不会确保其父接口的clinit()方法被执行,只有当父接口中的静态成员变量被使用到时才会执行父接口的clinit()方法。

  • 虚拟机会给clinit()方法加锁,因此当多条线程同时执行某一个类的clinit()方法时,只有一个方法会被执行,其他的方法都被阻塞。并且,只要有一个clinit()方法执行完,其它的clinit()方法就不会再被执行。因此,在同一个类加载器下,同一个类只会被初始化一次。


【7】类加载器

① 类与类加载器

  • 类加载器的作用

将class文件加载进JVM的方法区,并在方法区中创建一个java.lang.Class对象作为外界访问这个类的接口。

java.lang.Class对象存放在哪里,JVM规范并未严格研究,具体看厂商实现。HotSpot将java.lang.Class对象存放在方法区。

  • 类与加载器的联系

比较两个类是否相等,只有当这两个类由同一个加载器加载才有意义。否则,即使同一个class文件被不同的类加载器加载,那这两个类必定不同,即通过类的Class对象的equals执行的结果必定为false。

参考博文:ClassLoader隔离特性


② 类加载器种类


JVM 提供如下三种类加载器:

  • 启动类加载器(BootStrap ClassLoader,又称根加载器)
    每次执行 java 命令时都会使用该加载器为虚拟机加载核心类。该加载器是由 native code 实现,而不是 Java 代码,加载类的路径为 <JAVA_HOME>/jre/lib。特别的 <JAVA_HOME>/jre/lib/rt.jar 中包含了 sun.misc.Launcher 类, 而 sun.misc.Launcher$ExtClassLoader 和 sun.misc.Launcher$AppClassLoader 都是 sun.misc.Launcher的内部类,所以拓展类加载器和系统类加载器都是由启动类加载器加载的。

  • 扩展类加载器(Extension ClassLoader)
    用于加载拓展库中的类。拓展库路径为<JAVA_HOME>/jre/lib/ext/。实现类为 sun.misc.Launcher$ExtClassLoader。

  • 应用程序类加载器(System ClassLoader)
    负责加载用户classpath下的class文件,又叫系统加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。实现类为 sun.misc.Launcher$AppClassLoader

此外还有用户自定义类加载器,继承自System ClassLoader。

类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载—双亲委派模型。


③ 类加载图示

连接过程包括验证、准备和解析。


④ 双亲委派模型

  • 工作过程

如果一个类加载器收到了加载类的请求,它首先将请求交由父类加载器加载;若父类加载器加载失败,当前类加载器才会自己加载类。

  • 作用

像java.lang.Object这些存放在rt.jar中的类,无论使用哪个类加载器加载,最终都会委派给最顶端的启动类加载器加载,从而使得不同加载器加载的Object类都是同一个。

  • 原理

双亲委派模型的代码在java.lang.ClassLoader类中的loadClass函数中实现,其逻辑如下:

  • 首先检查类是否被加载;
  • 若未加载,则调用父类加载器的loadClass方法;
  • 若该方法抛出ClassNotFoundException异常,表示父类加载器无法加载,则当前类加载器调用findClass加载类;
  • 若父类加载器可以加载,则直接返回Class对象。

源码示例如下:

 protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

【8】类/对象的静态成员变量与非静态成员变量

① 静态成员变量

通过上面可知,类的静态成员变量在准备阶段进行内存分配并默认初始化,在初始化阶段进行显示初始化。

静态变量在以后的创建对象的时候不再初始化,但是对象可以再次对静态变量进行赋值,该赋值操作将会反映到方法区中类的数据结构中。即,方法区中存储静态变量的值也会随之发生改变。

同样,修改类中静态变量的值会反映到已经创建好的对象上面。

示例如下:

public class ClassA {private int a =10;private static int b=20;public int getA() {return a;}public void setA(int a) {this.a = a;}public static int getB() {return b;}public static void setB(int b) {ClassA.b = b;}
}

测试类如下

public class TestClassA {public static void main(String[] args) throws InterruptedException {// 先实例化一个对象ClassA classA = new ClassA();// 另起一个县城Thread thread = new Thread(new Runnable() {@Overridepublic void run() {//获取实例化对象的静态变量值System.out.println("thread:"+classA.getB());//重新设置实例化对象的静态变量值并获取classA.setB(30);System.out.println("thread:"+classA.getB());// 直接通过类设置类的静态变量值ClassA.setB(50);// 这里,获取实例化对象的静态变量值System.out.println("thread:"+classA.getB());}});thread.start();//再来一个对象ClassA classA2 = new ClassA();Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {// 首先打印对象的静态变量值System.out.println("thread2:"+classA2.getB());// 设置头一个对象的静态变量值classA.setB(40);// 打印类中的静态变量值System.out.println("thread2:"+ClassA.getB());}});thread2.start();}
}

输出结果:

thread:20
thread:30
thread:50
thread2:50
thread2:40

② 非静态成员变量

非静态成员变量只有在实例化对象的时候才会分配内存并赋值,非静态成员变量随对象一起保存在堆中。

每个实例化对象的非静态成员变量只属于自己,值修改也只 针对自己,并不会影响到其他对象或者反映到方法区中类的数据结构上面。

每次实例化对象的时候,非静态变量的值都为最初存放在方法区中的值,如这里private int a =10;。每次新建对象,对象的a都为10。

测试类如下:

public class TestClassA {public static void main(String[] args) throws InterruptedException {ClassA classA = new ClassA();Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread:"+classA.getA());classA.setA(30);System.out.println("thread:"+classA.getA());}});thread.start();ClassA classA2 = new ClassA();Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread2:"+classA2.getA());classA2.setA(40);System.out.println("thread2:"+classA2.getA());}});thread2.start();}
}

输出如下:

thread:10
thread:30
thread2:10
thread2:40

另外,需要注意的是类的非静态成员变量中,其中基本类型(非包装类型)变量的值,即字面量是保存在class文件的常量池中的,在加载的时候会被存放在方法区的运行时常量池。

public class A{private int a=10;private int b=1000;private Integer c=20;private Integer d=2000;//...
}

其中,10,1000都会被放在常量池中;而包装类型20和2000,其中20也放在常量池中,而且是一个Integer(20)对象。2000不在-128-127范围内,是一个新建对象!!!

Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)相关推荐

  1. Java类的加载过程详解 面试高频!!!值得收藏!!!

    受多种情况的影响,又开始看JVM 方面的知识. 1.Java 实在过于内卷,没法不往深了学. 2.面试题问的多,被迫学习. 3.纯粹的好奇. 很喜欢一句话: 八小时内谋生活,八小时外谋发展. 望别日与 ...

  2. 中yeti不能加载_第二十章_类的加载过程详解

    类的加载过程详解 概述 在 Java 中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载 按照 Java 虚拟机规范,从 Class 文件到加载到内 ...

  3. 类加载顺序及加载过程详解

    转自: 类加载顺序及加载过程详解 下文笔者讲述类的加载顺序及加载过程的详解说明,如下所示 java创建对象的方式分为以下四种 new 反射克隆反序列化 class对象获取的方式分享 //没有完成初始化 ...

  4. java类修饰词和内部类详解

    java类修饰词和内部类详解 控制属性: 同一类内     同一包内      子类     所有类 public            可             可         可       ...

  5. RSA加解密过程详解

    RSA加解密过程详解 RSA加密是一种非对称加密,由一对密钥来完成加解密过程,分别为公钥和私钥. RSA的加密过程如下: (1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留.公钥为公开的,任何 ...

  6. 类的加载过程详解:加载、验证、准备、解析、初始化

    想要弄明白的知识点: 类加载的过程,加载.验证.准备.解析.初始化.每个部分详细描述. 加载阶段读入.class文件,class文件时二进制吗,为什么需要使用二进制的方式? 验证过程是防止什么问题?验 ...

  7. jboss之启动加载过程详解(-)

    今天看了看jboss的boot.log和server.log日志,结合自己的理解和其他的资料,现对jboss的启动和加载过程做出如下总结: 本文以JBoss Application Server 4. ...

  8. Java类加载及对象创建过程详解

    类加载过程 类加载的五个过程:加载.验证.准备.解析.初始化. 加载 在加载阶段,虚拟机主要完成三件事: 通过一个类的全限定名来获取定义此类的二进制字节流. 将这个字节流所代表的静态存储结构转化为方法 ...

  9. java类的完整生命周期详解

    目录 一.概述 二.过程一:Loading(加载)阶段 1.加载完成的操作 (1)加载的理解 (2)加载完成的操作 2.二进制流的获取方式 3.类模型与Class实例的位置 (1)类模型的位置 (2) ...

最新文章

  1. 二进制与十进制的小数位怎么转?
  2. java hellowordk_Rhythmk 一步一步学 JAVA(4):Spring3 MVC 之 Hello Word
  3. git拉取远程分支并创建本地分支
  4. 做网页前端遇到的一些问题
  5. 深入浅出JVM-内存模型
  6. windows7怎么升级10_还有一个月微软就停止支持Win 7系统了,再不升级等同裸奔
  7. Spring Security Oauth2 (一) 整体流程介绍
  8. 将桌面文件映射至E盘
  9. 12C RAC中的一个数据库实例自动crash并报ORA-27157、ORA-27300等错误
  10. osg加载osgb数据_PCM点云数据处理软件功能使用第十七弹
  11. Python笔记 之 矩阵元素选取
  12. jeesite图片上传并显示
  13. 数字逻辑课程设计(一):数字时钟——logisam模拟实现
  14. mongodb lbs java_MongoDB开发LBS应用
  15. 程序员的这些尴尬瞬间,你经历过吗?
  16. 前端遮罩层实现_前端制作遮罩与蒙版
  17. 运维老鸟分享linux运维发展路线规划
  18. linux 文件查找帮助命令 , 查看网络链接信息, 历史命令
  19. 【远程连接控制】WinRM和SSH
  20. 使用设计模式出任CEO迎娶白富美(1)--毕业即失业

热门文章

  1. android百度地图路线查询,Android百度地图——路线规划搜索
  2. Mysql8.0下载(网盘云盘)
  3. oswatch的安装和使用
  4. Cannot checkout from svn: svn: E155000: ‘XXX‘ is alrea
  5. Android开发笔记(一百六十二)蓝牙设备的连接与配对
  6. red hat linux 9.0下载地址集合,Red Hat Linux 9.0 iso最新下载地址
  7. A065_运行前端_跨域_列表_删除
  8. python c++混合编程文档缩减版笔记 -2
  9. vb.net 简单取摄像头图片_【图片】大车监控如何安装?_大车监控吧
  10. Springboot美妆网站的设计与实现毕业设计-附源码211539