相信大多数熟悉Java的研发工程师,都知道Java类加载原理:Java中的类是由类加载器采用双亲委派机制进行加载。其中,Java核心库中实现了三种类型的类加载器,它们分别是:引导类加载器BootstrapLoader、扩展类加载器ExtClassloader、应用程序类加载器AppClassloader,它们之间存在父子关系,不过这种关系不是所谓的子类继承父类的关系,而是基于委托之间的父子关系。另外,用户可以通过继承java.lang.ClassLoader实现自定义类加载器。

下面,我们来看看Java核心类库的三种类加载器,示例代码如下:

/**
* Java核心类库的三种类加载器
*
* @author 编程老司机
* @date 2023-06-15
*/
public class JDKClassLoaderMechanismDemo { public static void main(String[] args) { System.out.println("******* Java核心类库的三种类加载器示例 *******");
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
// 由于引导类加载器是在JVM启动时通过C++代码创建的,
// 因此在运行Java代码时可能无法访问它,所以返回null
System.out.println("BootstrapLoader引导类加载器:" + bootstrapLoader);
System.out.println("ExtClassloader扩展类加载器" +
extClassloader.getClass().getName());
System.out.println("AppClassLoader应用程序类加载器:" +
appClassLoader.getClass().getName()); System.out.println();
System.out.println("三种类加载器加载哪些文件:");
System.out.println("BootstrapLoader加载以下文件:");
String filePaths = System.getProperty("sun.boot.class.path");
System.out.println(getPaths(filePaths)); System.out.println();
System.out.println("\nExtClassloader加载以下文件:");
filePaths = System.getProperty("java.ext.dirs");
System.out.println(getPaths(filePaths)); System.out.println();
System.out.println("AppClassLoader加载以下文件:");
filePaths = System.getProperty("java.class.path");
System.out.println(getPaths(filePaths)); System.out.println();
System.out.println("三种类加载器之间的关系:");
ClassLoader classLoader = JDKClassLoaderMechanismDemo.class.
getClassLoader();
ClassLoader parentClassLoader = classLoader.getParent();
ClassLoader parentParentClassLoader = parentClassLoader.getParent();
System.out.println("加载当前类的类加载器:" +
classLoader.getClass().getName());
System.out.println(classLoader.getClass().getName() +
"类加载器的父类加载器:"
+ parentClassLoader.getClass().getName());
System.out.println(parentClassLoader.getClass().getName() +
"类加载器的父类加载器:" + (Objects.isNull(parentParentClassLoader) ?
"null" : parentParentClassLoader.getClass().getName())); } public static String getPaths(String filePaths){
StringJoiner stringJoiner = new StringJoiner("\n");
for (String path: filePaths.split(";")){
stringJoiner.add(path);
}
return stringJoiner.toString();
}
}

上述代码运行结果:

******* Java核心类库的三种类加载器示例  *******

    BootstrapLoader引导类加载器:null
ExtClassloader扩展类加载器sun.misc.Launcher$ExtClassLoader
AppClassLoader应用程序类加载器:sun.misc.Launcher$AppClassLoader 三种类加载器加载哪些文件:
BootstrapLoader加载以下文件:
D:\Program Files\Java\jdk1.8.0_152\jre\lib\resources.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\sunrsasign.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\jsse.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\jce.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\charsets.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\jfr.jar
D:\Program Files\Java\jdk1.8.0_152\jre\classes ExtClassloader加载以下文件:
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext
C:\Windows\Sun\Java\lib\ext AppClassLoader加载以下文件:
D:\Program Files\Java\jdk1.8.0_152\jre\lib\charsets.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\deploy.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\access-bridge-64.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\cldrdata.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\dnsns.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jaccess.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jfxrt.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\localedata.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\nashorn.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunec.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunjce_provider.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunmscapi.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunpkcs11.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\zipfs.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\javaws.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\jce.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\jfr.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\jfxswt.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\jsse.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\management-agent.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\plugin.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\resources.jar
D:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar
D:\workspaces\utils\target\classes
D:\Program Files\apache-maven-3.8.1\myRepo\junit\junit\4.12\junit-4.12.jar
D:\Program Files\JetBrains\IntelliJ IDEA 2020.2\lib\idea_rt.jar 三种类加载器之间的关系:
加载当前类的类加载器:sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader类加载器的父类加载器:sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$ExtClassLoader类加载器的父类加载器:null

通过运行结果我们可以看出三种不同的类加载器加载的类文件是特定的目录和文件,也看出了三种类加载器的关系。

接下来,在剖析Java类加载原理之前,我们先看一段代码:

类加载的示例代码

/**
* 剖析类加载原理Demo
*
* @author 编程老司机
* @date 2023-06-13
*/
public class JDKMechanismDemo { static {
System.out.println("加载main方法所在主类JDKMechanismDemo的静态代码块");
} public JDKMechanismDemo() {
System.out.println("实例化main方法主类JDKMechanismDemo");
} public static void main(String[] args) {
ClassA a = new ClassA(); // new 关键字告诉JVM生成一个ClassA的实例
ClassB b = null; // 只声明,未实例,不会被加载
}
} class ClassA { static {
System.out.println("加载ClassA");
} public ClassA() {
System.out.println("实例化ClassA");
}
} class ClassB { static {
System.out.println("加载类ClassB");
} public ClassB() {
System.out.println("实例化ClassB");
} }

上述代码运行结果:

加载main方法所在主类JDKMechanismDemo的静态代码块
加载ClassA
实例化ClassA

从运行结果我们看到了,虽然ClassB,我们在代码中声明了,但是它并没有像ClassA输出结果,这说明:JVM在启动程序,执行main方法时不会一次性加载所有类,只加载使用到的类,也就是说,虽然我们的应用程序jar包或者war包中包含很多类,但是并不会被全部加载,而是只加载运行应用程序所需要的类。

类加载器的初始化

阅读过《深入剖析创建Java虚拟机的实现方法》文章的读者应该知道在JVM初始化的时候,通过调用ClassLoader::initialize(THREAD)方法将BootstrapLoader引导类加载器进行初始化的。

下面,我们通过源码了解应用程序类加载器的初始化和扩展类加载器的初始化。

结合《JVM源码分析:剖析JavaMain方法中的LoadMainClass的实现》中的内容,我们知道在调用LoadMainClass方法的时候,JVM通过sun.launcher.LauncherHelper类的checkAndLoadMain方法,通过sloader.loadClass(var3)获取到main方法主类,这个sloader对象,通过LauncherHelper源码我们可知,是由下面代码初始化的:

private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();

我们进入ClassLoader.getSystemClassLoader()方法中:

@CallerSensitive
public static ClassLoader getSystemClassLoader() {
// 初始化系统类加载器,实际上是应用程序类加载器,对scl变量赋值
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
// 返回应用程序类加载器
return scl;
}

继续进入initSystemClassLoader()方法:

private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
// scl 赋值,原来是通过sun.misc.Launcher对象获取的
scl = l.getClassLoader();
...省略一些不需要关注的代码
}
sclSet = true;
}
}

我们进入sun.misc.Launcher类:

public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
private static Launcher launcher = new Launcher();
// BootstrapLoader引导类加载器加载的类目录系统属性
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
// 采用单例设计模式,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例
public static Launcher getLauncher() {
return launcher;
} public Launcher() {
Launcher.ExtClassLoader var1;
try {
// 初始化扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader",
var10);
} try {
// 初始化应用程序类加载器,它的父加载器是扩展类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader",
var9);
} Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2)
.newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
} if (var3 == null) {
throw new InternalError("Could not create SecurityManager: "
+ var2);
} System.setSecurityManager(var3);
} } public ClassLoader getClassLoader() {
return this.loader;
}
...省略一些不需要关注的代码
}

在sun.misc.Launcher类中,我们也看到为扩展类加载器和应用程序类加载的类定义:

1、扩展类加载器定义

// 继承URLClassLoader,URLClassLoader继承ClassLoader抽象类
static class ExtClassLoader extends URLClassLoader {
...
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).
initLookupCache(this);
} private static File[] getExtDirs() {
// 扩展类加载器的加载类目录系统属性
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0,
File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3]; for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
} return var1;
}
...省略一些不需要关注的代码
}

2、应用程序类加载器定义

// 继承URLClassLoader,URLClassLoader继承ClassLoader抽象类
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp =
SharedSecrets.getJavaNetAccess().getURLClassPath(this); public static ClassLoader getAppClassLoader(
final ClassLoader var0)
throws IOException {
// 应用程序类加载器加载类目录系统属性
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] :
Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new
PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] :
Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
} AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
...省略一些不需要关注的代码
}

总结:引导类加载器是在JVM初始化的时候调用ClassLoader::initialize(THREAD)方法进行初始化的。扩展类加载器和应用程序类加载器都是由sun.misc.Launcher实例进行初始化的,sun.misc.Launcher是采用单例来保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。

那么类是如何加载的呢?

在《JVM源码分析:深入剖析JavaMain方法中的LoadMainClass的实现》章节提到了,调用Java核心rt.jar包中Java启动辅助类sun.launcher.LauncherHelper的checkAndLoadMain方法,可以获取AppClassLoader应用程序类加载器,然后由AppClassLoader加载main主类,那我们就从这里入手分析类的加载机制,进入checkAndLoadMain方法:

public enum LauncherHelper {
...
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
...
/* var0参数是指输出什么级别的日志:
* var0 = true代表使用标准错误输出流System.err
* var0 = false代表使用标准输出流System.out
* var1参数是指采用哪种模式,有两种模式:
* var1 = 1时,参数var2的值代表的是类名;
* var1 = 2时,参数var2的值代表的是jar包文件名
*/
public static Class<?> checkAndLoadMain(boolean var0, int var1, String var2) {
// 初始化LauncherHelper的输出流
initOutput(var0);
String var3 = null;
switch(var1) {
case 1:
// 类名就是var2
var3 = var2;
break;
case 2:
// 从jar包中获取主类类名
var3 = getMainClassFromJar(var2);
break;
default:
throw new InternalError("" + var1 + ": Unknown launch mode");
} var3 = var3.replace('/', '.');
Class var4 = null; try {
// 此处scloader是应用程序类加载器
var4 = scloader.loadClass(var3);
} catch (ClassNotFoundException | NoClassDefFoundError var8) {
if (System.getProperty("os.name", "").contains("OS X") && Normalizer.isNormalized(var3, Normalizer.Form.NFD)) {
try {
var4 = scloader.loadClass(Normalizer.normalize(var3, Normalizer.Form.NFC));
} catch (ClassNotFoundException | NoClassDefFoundError var7) {
abort(var8, "java.launcher.cls.error1", var3);
}
} else {
abort(var8, "java.launcher.cls.error1", var3);
}
} appClass = var4;
// 下面是JavaFX应用程序获取主类的流程
if (!var4.equals(sun.launcher.LauncherHelper.FXHelper.class) && !sun.launcher.LauncherHelper.FXHelper.doesExtendFXApplication(var4)) {
//校验JavaFX中的主类
validateMainClass(var4);
return var4;
} else {
sun.launcher.LauncherHelper.FXHelper.setFXLaunchParameters(var2, var1);
return sun.launcher.LauncherHelper.FXHelper.class;
}
}
...
}

接下来,我们进入var4 = scloader.loadClass(var3);这行代码中调用的loadClass方法:

public abstract class ClassLoader {
...
// ClassLoader 类加载器构造函数传参是父加载器
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
... public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先检查指定类是否已经加载,该方法会调用native方法findLoadedClass0,它由JVM实现
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { // 没加载,存在父加载器,就从父加载器中加载指定类
// 从这里我们就可以看出,类的加载是采用双亲委派机制
c = parent.loadClass(name, false);
} else {
// 引导类加载器是不存在父加载器,以下方法会调用native方法findBootstrapClass,
// 通过JVM初始化的引导类加载器(也叫JVM内置类加载器)加载指定类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 有可能父加载器或者引导类加载器中都没有指定类,会加载不到
} if (c == null) {
long t1 = System.nanoTime();
// 经过上面加载还未加载到类,就调用findClass方法,findClass方法在
// 抽象类ClassLoader中并未实现,是ClassLoader的子类实现的
c = findClass(name); // ClassLoader记录统计数据
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
// 这行代码的意思是:类加载完成的同时也被解析。该方法会调用native方法
// resolveClass0,它由JVM实现
// 有读者可能就会问了,解析是什么意思?这里就涉及到了类的生命周期了,
// 我给大家普及一下:类的生命周期有:1、加载;2、验证;3、准备;
// 4、解析(或者叫链接);5、初始化;6、使用;7、卸载。所以这里的
// 解析是指的第4步
resolveClass(c);
}
return c;
}
} protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
...
}

通过上面代码的分析,我们知道怎么实现双亲委派机制加载类的,下面用一张图来概括:

另外,我们还知道了,类加载的底层也都是由JVM实现的,对于Java应用程序研发工程师,了解到这个程度,在工作中也够用了。

今天,想必读者们也知道了Java类加载原理。如果读者想深入到JVM源码底层了解类加载是如何实现的,后续如果人数多的话,我会单独写一篇文章进行深入分析。

从源码级剖析Java类加载原理的更多相关文章

  1. 【Java编程实战】Metasploit_Java后门运行原理分析以及实现源码级免杀与JRE精简化

    QQ:3496925334 文章作者:MG1937 CNBLOG博客ID:ALDYS4 未经许可,禁止转载 某日午睡,迷迷糊糊梦到Metasploit里有个Java平台的远控载荷,梦醒后,打开虚拟机, ...

  2. ArrayList源码深度剖析,从最基本的扩容原理,到魔幻的迭代器和fast-fail机制,你想要的这都有!!!

    ArrayList源码深度剖析 本篇文章主要跟大家分析一下ArrayList的源代码.阅读本文你首先得对ArrayList有一些基本的了解,至少使用过它.如果你对ArrayList的一些基本使用还不太 ...

  3. 关于JAVA中源码级注解的编写及使用

    一.注解简介: 1.1.什么是"注解": ​ 在我们编写代码时,一定看到过这样的代码: class Student { private String name; @Override ...

  4. MapReduce的ReduceTask任务的运行源码级分析

    MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...

  5. TaskTracker任务初始化及启动task源码级分析

    在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...

  6. [源码解析] 当 Java Stream 遇见 Flink

    [源码解析] 当 Java Stream 遇见 Flink 目录 [源码解析] 当 Java Stream 遇见 Flink 0x00 摘要 0x01 领域 1.1 Flink 1.2 Java St ...

  7. Redis 源码简洁剖析 03 - Dict Hash 基础

    Redis Hash 源码 Redis Hash 数据结构 Redis rehash 原理 为什么要 rehash? Redis dict 数据结构 Redis rehash 过程 什么时候触发 re ...

  8. ArrayDeque(JDK双端队列)源码深度剖析

    ArrayDeque(JDK双端队列)源码深度剖析 前言 在本篇文章当中主要跟大家介绍JDK给我们提供的一种用数组实现的双端队列,在之前的文章LinkedList源码剖析当中我们已经介绍了一种双端队列 ...

  9. JDK数组阻塞队列源码深入剖析

    JDK数组阻塞队列源码深入剖析 前言 在前面一篇文章从零开始自己动手写阻塞队列当中我们仔细介绍了阻塞队列提供给我们的功能,以及他的实现原理,并且基于谈到的内容我们自己实现了一个低配版的数组阻塞队列.在 ...

  10. MapReduce的MapTask任务的运行源码级分析

    TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...

随机推荐

  1. opencv-python 4.9.2. 轮廓特征

    矩 图像的矩可帮助你计算某些特征,如对象的质心,对象的面积等特征.函数cv.moments()给出了计算的所有矩值的字典. 从这一刻起,你可以提取有用的数据,如面积,质心等.质心由关系给出, $$ C ...

  2. 深入理解 python 虚拟机:字节码灵魂——Code obejct

    深入理解 python 虚拟机:字节码灵魂--Code obejct 在本篇文章当中主要给大家深入介绍在 cpython 当中非常重要的一个数据结构 code object! 在上一篇文章 深入理解 ...

  3. MySQL四种日志binlog/redolog/relaylog/undolog

    优质博文:IT-BLOG-CN 一.binlog binlog记录数据库表结构和表数据变更,比如update/delete/insert/truncate/create,它不会记录select.存储着 ...

  4. PySimpleGU之运行多个窗口

    这是PySimpleGUI继续简单的地方,但是问题空间刚刚进入了"复杂"领域. 如果您希望在事件循环中运行多个窗口,则有两种方法可以执行此操作. 当第二个窗口可见时,第一个窗口不会 ...

  5. [NotePad++]NotePad++实用技巧

    2 应用技巧 2.1 匹配并捕获/截取 截取第1列的数据 截取前 "(.*)", "(.*)", "(.*)"\)\); 截取后: 2.2 ...

  6. day05-SpringCloud Eureka-服务注册与发现02

    SpringCloud Eureka-服务注册与发现02 3.搭建EurekaServer集群-实现负载均衡&故障容错 3.1为什么需要集群EurekaServer? 微服务RPC远程服务调用 ...

  7. 1.使用cookie简单实现单点登录流程

    1.动手 实现了简单使用多系统,单一位置同时登陆,以及注销 主要认证中心流程代码编写在为在sso-login包下的ViewConreoller和LoginController:各系统的用户名显示是写在 ...

  8. Django笔记二十五之数据库函数之日期函数

    本文首发于公众号:Hunter后端 原文链接:Django笔记二十五之数据库函数之日期函数 日期函数主要介绍两个大类,Extract() 和 Trunc() Extract() 函数作用是提取日期,比 ...

  9. springboot整合cas回调地址使用nginx配置出错

    nginx配置后台为 location /apis springboot基础cas回调时访问地址为nginx域名+apis回调.为什么没有成功.页面只回调域名+登录方法路径,而不是域名+apis+登录 ...

  10. SQLite3数据库的介绍和使用(面向业务编程-数据库)

    SQLite3数据库的介绍和使用(面向业务编程-数据库) SQLite3介绍 SQLite是一种用C语言实现的的SQL数据库 它的特点有:轻量级.快速.独立.高可靠性.跨平台 它广泛应用在全世界范围内 ...