目录

前提: 运行环境

1. 类加载的过程

1.1 类加载器初始化的过程

1.2 类加载的过程

1.3 类的懒加载

2. jvm核心类加载器

3. 双亲委派机制

4. 自定义类加载器

5. tomcat类加载机制

运行环境:

我是在mac上操作的. 先找到mac的java地址. 从~/.bash_profile中可以看到

java的home目录是: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home

一. 类加载的过程

1.1 类加载器初始化的过程

假如现在有一个java类 com.lxl.jvm.Math类, 里面有一个main方法,

package com.lxl.jvm;public classMath {public static int initData = 666;public static User user = newUser();public intcompute() {int a = 1;int b = 2;int c = (a + b) * 10;returnc;

}public static voidmain(String[] args) {

Math math= newMath();

math.compute();

}

}

这个方法很简单, 通常我们直接执行main方法就ok, 可以运行程序了, 那么点击运行main方法, 整个过程是如何被加载的呢? 为什么点击执行main, 就能得到结果呢?

先来看看答题的类加载流程, 如下图:

备注:

1. windows上的java启动程序是java.exe, mac下,就是java

2. c语言部分,我们做了解, java部门是需要掌握的部分.

第一步: java调用底层的jvm.dll文件创建java虚拟机(这一步由C++实现) . 这里java是c++写的代码, 调用的jvm.dll也是c++底层的一个函数. 通过调用jvm.dll文件, 会创建java虚拟机. java虚拟机的启动都是c++程序实现的. dll文件相当于java的jar包. 开启虚拟机以后,会做哪些操作呢? 看第二步

第二步:在启动虚拟机的过程中, 会创建一个引导类加载器的实例. 这个引导类的加载器是C语言实现的. 然后jvm虚拟机就启动起来了. jvm启动起来了,记下来干嘛呢?

第三步: 接下来,C++语言会调用java的启动程序.刚刚只是创建了java虚拟机, java虚拟机里面还有很多启动程序. 其中有一个程序叫做Launcher. 类全称是sun.misc.Launcher. 通过启动这个java类, 会由这个类引导加载器加载并创建很多其他的类加载器. 而这些加载器才是真正启动并加载磁盘上的字节码文件.

第四步:真正的去加载本地磁盘的字节码文件,然后启动执行main方法.

第五步:main方法执行完毕, 引导类加载器会发起一个c++调用, 销毁JVM

以上就是启动一个main方法, 这个类加载的全部过程

下面, 我们重点来看一下, 我们的类com.lxl.Math是怎么被加载到java虚拟机里面去的?

1.2 类加载的过程

上面的com.lxl.jvm.Math类最终会生成clas字节码文件. 字节码文件是怎么被加载器加载到JVM虚拟机的呢?

类加载有五步:加载, 验证, 准备, 解析, 初始化. 那么这五步都是干什么的呢?我们来看一下

我们的类在哪里呢? 在磁盘里(比如: target文件夹下的class文件), 我们先要将class类加载到内存中. 加载到内存区域以后, 不是简简单单的转换成二进制字节码文件,他会经过一系列的过程. 比如: 验证, 准备, 解析, 初始化等. 把这一些列的信息转变成内元信息, 放到内存里面去. 我们来看看具体的过程

第一步: 加载.  将class类加载到java虚拟机的内存里去, 在加载到内存之前, 会有一些列的操作, 第一个是验证字节码, 比如:打开一个字节码文件

打眼一看, 感觉像是乱码, 实际上不是的. 其实,这里面每个字符串都有对应的含义. 那么文件里面的内容我们能不能替换呢?当然不能, 一旦替换, 就不能执行成功了. 所以, 第一步:验证, 验证什么呢?

验证字节码加载是否正确: 格式是否正确. 验证, 验证的就是里面的内容, 是否符合java虚拟机的规范. 验证完了, 接下来是准备. 准备干什么呢? 比如我们的类Math, 他首先会给Math里的静态变量赋值一个初始值. 比如我们Math里有两个静态遍历

public static int initData = 666;public static User user = new User();

在准备的过程中, 就会给这两个变量赋初始值, 这个初始值并不是真实的值, 比如initData的初始值是0. 如果是boolean类型, 就赋值为false.  也就是说, 准备阶段赋的值是jvm固定的, 不是我们定义的值.如果一个final的常量, 比如public static final int name="zhangsan", 那么他在初始化的时候, 是直接赋初始值"zhangsan"的. 这里只是给静态变量赋初始值

记下来说说解析的过程. 解析的过程略微复杂, 解析是将"符号引用"转变为直接引用.

什么是符号引用呢?

比如我们的程序中的main方法. 写法是固定的, 我们就可以将main当成一个符号. 比如上面的initData, int, static, 我们都可以将其称之为符号, java虚拟机内部有个专业名词,把他叫做符号. 这些符号被加载到内存里都会对应一个地址. 将"符号引用"转变为直接引用, 指的就是, 将main, initData, int等这些符号转变为对应的内存地址. 这个地址就是代码的直接引用. 根据直接引用的值,我们就可以知道代码在什么位置.然后拿到代码去真正的运行.

将符号引用转变为"内存地址", 这种有一个专业名词, 叫静态链接. 上面的解析过程就相当于静态链接的过程. 类加载期间,完成了符号到内存地址的转换. 有静态链接, 那么与之对应的还有动态链接.

什么是动态链接呢?

public static voidmain(String[] args) {

Math math= newMath();

math.compute();

}

比如:上面这段代码, 只有当我运行到math.compute()这句话的时候, 才回去加载compute()这个方法. 也就是说, 在加载的时候, 我不一定会把compute()这个方法解析成内存地址. 只有当运行到这行代买的时候, 才会解析.

我们来看看汇编代码

javap -v Math.class

Classfile /Users/luoxiaoli/Downloads/workspace/project-all/target/classes/com/lxl/jvm/Math.classLast modified2020-6-27; size 777bytes

MD5 checksum a6834302dc2bf4e93011df4c0b774158

Compiledfrom "Math.java"

public classcom.lxl.jvm.Math

minor version:0major version:52flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #9.#35 //java/lang/Object."":()V

#2 = Class #36 //com/lxl/jvm/Math

#3 = Methodref #2.#35 //com/lxl/jvm/Math."":()V

#4 = Methodref #2.#37 //com/lxl/jvm/Math.compute:()I

#5 = Fieldref #2.#38 //com/lxl/jvm/Math.initData:I

#6 = Class #39 //com/lxl/jvm/User

#7 = Methodref #6.#35 //com/lxl/jvm/User."":()V

#8 = Fieldref #2.#40 //com/lxl/jvm/Math.user:Lcom/lxl/jvm/User;

#9 = Class #41 //java/lang/Object

#10 =Utf8 initData

#11 =Utf8 I

#12 =Utf8 user

#13 = Utf8 Lcom/lxl/jvm/User;

#14 = Utf8 #15 =Utf8 ()V

#16 =Utf8 Code

#17 =Utf8 LineNumberTable

#18 =Utf8 LocalVariableTable

#19 = Utf8 this#20 = Utf8 Lcom/lxl/jvm/Math;

#21 =Utf8 compute

#22 =Utf8 ()I

#23 =Utf8 a

#24 =Utf8 b

#25 =Utf8 c

#26 =Utf8 main

#27 = Utf8 ([Ljava/lang/String;)V

#28 =Utf8 args

#29 = Utf8 [Ljava/lang/String;

#30 =Utf8 math

#31 =Utf8 MethodParameters

#32 = Utf8 #33 =Utf8 SourceFile

#34 =Utf8 Math.java

#35 = NameAndType #14:#15 //"":()V

#36 = Utf8 com/lxl/jvm/Math

#37 = NameAndType #21:#22 //compute:()I

#38 = NameAndType #10:#11 //initData:I

#39 = Utf8 com/lxl/jvm/User

#40 = NameAndType #12:#13 //user:Lcom/lxl/jvm/User;

#41 = Utf8 java/lang/Object

{public static intinitData;

descriptor: I

flags: ACC_PUBLIC, ACC_STATICpublic staticcom.lxl.jvm.User user;

descriptor: Lcom/lxl/jvm/User;

flags: ACC_PUBLIC, ACC_STATICpubliccom.lxl.jvm.Math();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_01: invokespecial #1 //Method java/lang/Object."":()V

4: returnLineNumberTable:

line3: 0LocalVariableTable:

Start Length Slot Name Signature0 5 0 this Lcom/lxl/jvm/Math;public intcompute();

descriptor: ()I

flags: ACC_PUBLIC

Code:

stack=2, locals=4, args_size=1

0: iconst_11: istore_12: iconst_23: istore_24: iload_15: iload_26: iadd7: bipush 10

9: imul10: istore_311: iload_312: ireturn

LineNumberTable:

line8: 0line9: 2line10: 4line11: 11LocalVariableTable:

Start Length Slot Name Signature0 13 0 this Lcom/lxl/jvm/Math;2 11 1a I4 9 2b I11 2 3c Ipublic static voidmain(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=2, args_size=1

0: new #2 //class com/lxl/jvm/Math

3: dup4: invokespecial #3 //Method "":()V

7: astore_18: aload_19: invokevirtual #4 //Method compute:()I

12: pop13: returnLineNumberTable:

line15: 0line16: 8line17: 13LocalVariableTable:

Start Length Slot Name Signature0 14 0 args [Ljava/lang/String;8 6 1 math Lcom/lxl/jvm/Math;

MethodParameters:

Name Flags

argsstatic{};

descriptor: ()V

flags: ACC_STATIC

Code:

stack=2, locals=0, args_size=0

0: sipush 666

3: putstatic #5 //Field initData:I

6: new #6 //class com/lxl/jvm/User

9: dup10: invokespecial #7 //Method com/lxl/jvm/User."":()V

13: putstatic #8 //Field user:Lcom/lxl/jvm/User;

16: returnLineNumberTable:

line4: 0line5: 6}

SourceFile:"Math.java"

使用这个指令, 就可以查看Math的二进制文件. 其实这个文件,就是上面那个二进制代码文件.

看看这里面有什么东西?

类的名称, 大小,修改时间, 大版本,小版本, 访问修饰符等等

Last modified 2020-6-27; size 777bytes

MD5 checksum a6834302dc2bf4e93011df4c0b774158

Compiledfrom "Math.java"

public classcom.lxl.jvm.Math

minor version:0major version:52

还有一个Constant pool 常量池. 这个常量池里面有很多东西. 我们重点看中间哪一行. 第一列表示一个常量的标志符, 这个标识符可能在其他地方会用到.  第二列就表示常量内容.

Constant pool:

#1 = Methodref #9.#35 //java/lang/Object."":()V

#2 = Class #36 //com/lxl/jvm/Math

#3 = Methodref #2.#35 //com/lxl/jvm/Math."":()V

#4 = Methodref #2.#37 //com/lxl/jvm/Math.compute:()I

#5 = Fieldref #2.#38 //com/lxl/jvm/Math.initData:I

#6 = Class #39 //com/lxl/jvm/User

#7 = Methodref #6.#35 //com/lxl/jvm/User."":()V

#8 = Fieldref #2.#40 //com/lxl/jvm/Math.user:Lcom/lxl/jvm/User;

#9 = Class #41 //java/lang/Object

#10 =Utf8 initData

#11 =Utf8 I

#12 =Utf8 user

#13 = Utf8 Lcom/lxl/jvm/User;

#14 = Utf8 #15 =Utf8 ()V

#16 =Utf8 Code

#17 =Utf8 LineNumberTable

#18 =Utf8 LocalVariableTable

#19 = Utf8 this#20 = Utf8 Lcom/lxl/jvm/Math;

#21 = Utf8 compute

这些标识符在后面都会被用到, 比如main方法

public static voidmain(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=2, args_size=1

0: new #2 //class com/lxl/jvm/Math

3: dup4: invokespecial#3 //Method "":()V

7: astore_18: aload_19: invokevirtual #4 //Method compute:()I

12: pop13: returnLineNumberTable:

line15: 0line16: 8line17: 13LocalVariableTable:

Start Length Slot Name Signature0 14 0 args [Ljava/lang/String;8 6 1 math Lcom/lxl/jvm/Math;

MethodParameters:

Name Flags

args

这里面就用到了#2 #3 #4 ,这都是标识符的引用.

第一句: new了一个Math(). 我们看看汇编怎么写的?

0: new #2 //class com/lxl/jvm/Math

new + #2. #2是什么呢? 去常量池里看, #2代表的就是Math类

#2 = Class #36 //com/lxl/jvm/Math

这里要说的还是math.compute()这个方法, 不是在类加载的时候就被加载到内存中去了, 而是运行main方法的时候, 执行到这行代码才被加载进去, 这个过程叫做动态链接.

类加载的时候, 我们可以把"解析"理解为静态加载的过程. 一般像静态方法(例如main方法), 获取其他不变的静态方法会被直接加载到内存中, 因为考虑到性能, 他们加载完以后就不会变了, 就直接将其转变为在内存中的代码位置.

而像math.compute()方法, 在加载过程中可能会变的方法(比如compute是个多态,有多个实现), 那么在初始化加载的时候, 我们不会到他会调用谁, 只有到运行时才能知道代码的实现, 所以在运行的时候在动态的去查询他在内存中的位置, 这个过程就是动态加载

下一个步骤: 初始化: 对类的静态变量初始化为指定的值. 执行静态代码块. 比如代码

public static int initData = 666;

在准备阶段将其赋值为0, 而在初始化阶段, 会将其赋值为设定的666

2. 验证

3. 准备

4. 解析

5. 初始化

1.3 类的懒加载

类被加载到方法区中以后,主要包含:运行时常量池, 类型信息, 字段信息, 方法信息, 类加载器的引用, 对应class实例的引用等信息.

什么意思呢? 就是说, 当一个类被加载到内存, 这个类的常量,有常量名, 类型, 域信息等; 方法有方法名, 返回值类型, 参数类型, 方法作用域等符号信息都会被加载放入不同的区域.

注意: 如果主类在运行中用到其他类,会逐步加载这些类, 也就是说懒加载. 用到的时候才加载.

package com.lxl.jvm;public classTestDynamicLoad {static{

System.out.println("********Dynamic load class**************");

}public static void main(String[] args) {

new A();

System.out.println("*********load test*****************");

B b = null; // 这里的b不会被加载, 除非new B();}

}classA{static{

System.out.println("********load A**************");

}publicA(){

System.out.println("********initial A**************");

}

}classB{static{

System.out.println("********load B**************");

}publicB(){

System.out.println("********initial B**************");

}

}

这里定义了两个类A和B, 当使用到哪一个的时候, 那个类才会被加载, 比如:main方法中, B没有被用到, 所以, 他不会被加载到内存中.

运行结果

********Dynamic load class**************

********load A**************

********initial A**************

*********load test*****************

总结几点如下:

1. 静态代码块在构造方法之前执行

2. 没有被真正使用的类不会被加载

二. jvm核心类加载器

类主要通过类加载器来加载, java里面有如下几种类加载器

1. 引导类加载器:负责加载支撑JVM运行的, 位于jre目录的lib目录下的核心类. 比如:rt.jar, charset.jar等,

2. 扩展类加载器: 负责加载支撑JVM运行的, 位于jre目录的lib/ext扩展目录中的jar包

3. 应用程序类加载器: 负责加载classPath路径下的类包, 主要加载自己写的类

4. 自定义类加载器: 负责加载用户自定义路径下的类包

引导类加载器是由C++帮我们实现的, 然后c++语言会通过一个Launcher类将扩展类加载器(ExtClassLoader)和应用程序类加载器(AppClassLoader)构造出来, 并且把他们之间的关系构建好.

例1:

package com.lxl.jvm;

import sun.misc.Launcher;

import java.net.URL;public classTestJDKClassLoader {public static voidmain(String[] args) {/**

* 第一个: String 是jdk自身自带的类, 所以, 他的类加载器是引导类加载器

* 第二个: 加密类的classloader, 这是jdk扩展包的一个类

* 第三个: 是我们当前自己定义的类, 会被应用类加载器加载*/System.out.println(String.class.getClassLoader());

System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());

System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());

}

}

我们来看这个简单的代码, 运行结果:

nullsun.misc.Launcher$ExtClassLoader

sun.misc.Launcher$AppClassLoader

第一个: String 是jdk自身自带的类, 所以, 他的类加载器是引导类加载器

第二个: 加密类的classloader, 这是jdk扩展包的一个类, jdk扩展包里面使用的是extClassLoader类加载器加载的

第三个: 是我们当前自己定义的类, 会被AppClassLoader应用程序加载器加载.

我们看到ExtClassLoader和AppClassLoader都是Launcher类的一部分. 那Launcher类是什么东西呢?

上面有提到, Launcher类是jvm启动的时候由C++调用启动的一个类.

那么,第一个bootstrap引导类加载器, 那引导类加载器返回的为什么是null呢?

因为bootstrap引导类加载器, 他不是java的对象, 他是c++生成的对象, 所以这里是看不到的

例2: ExtClassLoader和AppClassLoader是怎么生成的?

从这个图中我们可以看出,C++调用java创建JVM启动器, 其中一个启动器是Launcher,他实际是调用了sun.misc.Launcher的getLauncher()方法. 那我们就从这个方法入手看看到底是如何运行的?

我们看到Lanucher.java类是在核心的rt.jar包里的

我们看到getLauncher()类直接返回了launcher. 而launcher是一个静态对象变量, 这是一个单例模式

C++通过getLauncher()-->直接返回了lanucher对象, 而launcher对象是在构建类的时候就已经初始化好了. 那么,初始化的时候做了哪些操作呢?接下来看看他的构造方法.

在构造方法里, 首先定义了一个ExtClassLoader. 这是一个扩展类加载器, 扩展类加载器调用的是getExtClassLoader(). 接下来看一看getExtClassLoader这个方法做了什么?

在这里, 判断当前对象是否初始化过, 如果没有, 那么就创建一个ExtClassLoader()对象, 看看createExtClassLoader()这个方法做了什么事呢?

doPrivileged是一个权限校验的操作, 我们可以先不用管, 直接看最后一句, return new Launcher.ExtClassLoader(var1). 直接new了一个ExtClassLoader, 其中参数是var1, 代表的是ext扩展目录下的文件.

在ExtClassLoader(File[] var1)这个方法中,  这里第一步就是调用了父类的super构造方法. 而ExtClassLoader继承了谁呢? 我么你可以看到他继承了URLClassLoader.

而URLClassLoader是干什么用的呢? 其实联想一下大概能够猜数来, 这里有一些文件路径, 通过文件路径加载class类.

我们继续看调用的super(parent), 我们继续往下走, 就会看到一下内容

这里设置了ExtClassLoader的parent是谁? 注意看的话,我们发现, ExtClassLoader的parent类是null.

这就是传递过来的parent类加载器, 那么这里的parent类加载器为什么是null呢? 因为, ExtClassLoader的父类加载器是谁呢? 他是Bootstrap ClassLoader. 而BootStrap ClassLoader是C++的类加载器, 我们不能直接调用它, 所以, 设置为null.

其实, ExtClassLoader在初始化阶段就是调用了ExtClassLoader方法, 初始化了ExtClassLoader类

接下来,我们回到Launcher的构造方法, 看看Launcher接下来又做了什么?

可以看到, 接下来调了AppClassLoader的getAppClassLoader(var1), 这个方法. 需要注意一下的是var1这个参数. var1是谁呢? 向上看, 可以看到var1是ExtClassLoader.

这是AppClassLoader, 应用程序类加载器, 这个类是加载我们自己定义的类的类加载器. 他也是继承自URLClassLoader.

我们来看看getAppClassLoader(final ClassLoader var0)方法. 这个方法的参数就是上面传递过来的ExtClassLoader

这里第一句话就是获取当前项目的class 文件路径, 然后将其转换为URL. 并调用了Launcher.AppClassLoader(var1x, var0), 其中var1x是class类所在的路径集合, var0是扩展的类加载器ExtClassLoader, 接下来, 我们进入到这个方法里看一看

AppClassLoader直接调用了其父类的构造方法, 参数是class类路径集合, 和ExtClassLoader

最后, 我们看到, 将ExtClassLoader传递给了parent变量. 这是定义在ClassLoader中的属性, 而ClassLoader类是所有类加载器的父类. 因此, 我们也可以看到AppClassLoader的父类加载器是ExtClassLoader

同时, 我们也看到了, C++在启动JVM的时候, 调用了Launcher启动类, 这个启动类同时加载了ExtClassLoader和AppClassLoader.

public static voidmain(String[] args) {

ClassLoader appClassLoader=ClassLoader.getSystemClassLoader();

ClassLoader extClassLoader=appClassLoader.getParent();

ClassLoader bootstrapClassLoad=extClassLoader.getParent();

System.out.println("bootstrap class loader:" +bootstrapClassLoad);

System.out.println("ext class loader" +extClassLoader);

System.out.println("app class loader"+appClassLoader);

}

通过这个demo, 我们也可以看出, appClassLoader的父类是extClassLoader, extClassLoader的父类是bootstrapClassLoader

输出结果:

bootstrap class loader: nullextclassloader sun.misc.Launcher$ExtClassLoader@2a84aee7

appclass loader sun.misc.Launcher$AppClassLoader@18b4aac2

jdk包含java语言核心的类_1.1 jvm核心类加载器--jdk源码剖析相关推荐

  1. java 线程加载类_java JVM-线程上下类加载器

    public class One { public static void main(String[] args) throws Exception { ClassLoader loader=One. ...

  2. java类描述_java笔记2:Java语言中的类和对象的描述

    在Java 语言中,除8 个基本数据类型值之外, 一切都是对象,而对象就是面向对象程序设计的中心. 对象是人们要进行研究的任何事物,从最简单的整数到复杂的飞机等均可看作对象,它不仅能表示具体的事物,还 ...

  3. jdbc是java语言编写的类和接口_JDBC——Java语言连接数据库的标准

    JDBC概述 API JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Jav ...

  4. java对象与类ppt,JAVA语言对象与类.ppt

    JAVA语言对象与类.ppt 第三章 对象与类,3.1 类与对象 3.2 定义类 3.3 成员变量的作用域 3.4 方法重载 3.5 创建对象 3.6 构造方法 3.7 this关键字 3.8 初始化 ...

  5. Java快速入门学习笔记7 | Java语言中的类与对象

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

  6. 基于JAVA计算机类专业考研交流学习平台计算机毕业设计源码+数据库+lw文档+系统+部署

    基于JAVA计算机类专业考研交流学习平台计算机毕业设计源码+数据库+lw文档+系统+部署 基于JAVA计算机类专业考研交流学习平台计算机毕业设计源码+数据库+lw文档+系统+部署 本源码技术栈: 项目 ...

  7. java毕业设计——基于java+图像处理技术的医学图像处理系统设计与实现(毕业论文+程序源码)——医学图像处理系统

    基于java+图像处理技术的医学图像处理系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+图像处理技术的医学图像处理系统设计与实现,文章末尾附有本毕业设计的论文和源码下载地址哦 ...

  8. java毕业设计——基于java+Java awt+swing的愤怒的小鸟游戏设计与实现(毕业论文+程序源码)——愤怒的小鸟游戏

    基于java+Java awt+swing的愤怒的小鸟游戏设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+Java awt+swing的愤怒的小鸟游戏设计与实现,文章末尾附有本毕 ...

  9. 【java毕业设计】基于java+swing+GUI的连连看游戏设计与实现(毕业论文+程序源码)——连连看游戏

    基于java+swing+GUI的连连看游戏设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+swing+GUI的连连看游戏设计与实现,文章末尾附有本毕业设计的论文和源码下载地址哦 ...

最新文章

  1. java显示本地磁盘所有盘符,显示桌面路径
  2. 在当当买了python怎么下载源代码-Python爬取当当、京东、亚马逊图书信息代码实例...
  3. 30 ArcGIS 许可管理器常见问题(持续更新中……)
  4. java xml 合并_Java中合并XML文档的设计与实现
  5. ppython_Python pcom包_程序模块 - PyPI - Python中文网
  6. matlab 曲线拟合求导,基于matlab曲线拟合的数据预测分析
  7. 百度地图Key的设置方法
  8. 卷积神经网络-感受野的定义
  9. 红皮书--SQL语句
  10. 怎样给Spark传递函数—怎样让你的Spark应用更高效更健壮
  11. VS2010轻松学习C#-从零到深入-天轰穿.NET4趣味编程视频教程
  12. MT【276】正切的半角公式
  13. 微信公众号配置token
  14. LibUSB-Win32程序介绍
  15. CODEVS P2833 奇怪的梦境
  16. python人脸头发身体部位识别人数统计
  17. 创新实训1 小组分工以及项目部署
  18. jmeter阶梯式加压(逐渐加压和降压)
  19. 记录一个<h265的Http-Flv的流无法播放> 的完整的bug解决思路方案
  20. 西门子V90伺服DI/DO口的接线及参数定义

热门文章

  1. CryEngine新引擎正式开放源代码,但仅限开发游戏
  2. 分布式文件系统MFS(moosefs)实现存储共享(一)
  3. 杭电 2015 偶数求和
  4. Foundation框架集合 ---- NSArray和NSMutableArray
  5. 在nginx中配置如何防止直接用ip访问服务器web server及server_name特性讲解
  6. 二月十大病毒排行 加花加密最危险
  7. 奥特曼系列服务器芝庞顿,奥特曼:黑田笔下的怪兽,芝庞顿引人注目,五帝王造型诠释霸气...
  8. redis 备份导出rdb_Redis持久化知识点—RDB+AOF ,你了解多少
  9. python描述符详解_Python描述符 (descriptor) 详解
  10. js html 拼接,JavaScript concat() 方法