ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java进阶之JVM实战

2021-02-28 17:05:29  阅读:128  来源: 互联网

标签:Java name jar public String JVM java class 进阶


1. 使用自定义Classloader机制,实现xlass的加载

1.1 类加载流程

BootStrap 加载路径

System.getProperty("sun.boot.class.path")

输出结果如下:
xxx/jre/lib/resources.jar: 
xxx/jre/lib/rt.jar: 
xxx/jre/lib/sunrsasign.jar: 
xxx/jre/lib/jsse.jar: 
xxx/jre/lib/jce.jar: 
xxx/jre/lib/charsets.jar: 
xxx/jre/lib/jfr.jar: 
xxx/jre/classes

ExtClassLoader 加载路径

System.getProperty("java.ext.dirs")

输出结果如下:
/Users/xxx/Library/Java/Extensions:
xxx/jre/lib/ext:
/Library/Java/Extensions:
/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:
/usr/lib/java

AppClassLoader 加载路径

System.out.println("自定义类加载路径:");
System.out.println(AXClassLoader.class.getClassLoader());
System.out.println("对应Parent ClassLoader:");
System.out.println(AXClassLoader.getSystemClassLoader().getParent());
System.out.println("对应父类的父类 ClassLoader:");
System.out.println(AXClassLoader.getSystemClassLoader().getParent().getParent());

1.2 Java Resource 路径

private static void printResourcePath() {
    System.out.println("AXClassLoader.class.getResource(\"\")):\n" + AXClassLoader.class.getResource(""));
    System.out.println("AXClassLoader.class.getResource(\"/\")):\n" + AXClassLoader.class.getResource("/"));
    System.out.println("AXClassLoader.class.getClassLoader().getResource(\"\")):\n" + AXClassLoader.class.getClassLoader().getResource(""));
    System.out.println("ClassLoader.getSystemResource(\"))\n" + ClassLoader.getSystemResource(""));
    System.out.println("Thread.currentThread().getContextClassLoader().getResource(\"\")\n" + Thread.currentThread().getContextClassLoader().getResource(""));
}

输出结果:
AXClassLoader.class.getResource("")):
file:/xxx/java-p7-in-action/jvm-base/target/classes/com/holddie/jvm/classloader/v1/

AXClassLoader.class.getResource("/")):
file:/xxx/java-p7-in-action/jvm-base/target/classes/

AXClassLoader.class.getClassLoader().getResource("")):
file:/xxx/java-p7-in-action/jvm-base/target/classes/

ClassLoader.getSystemResource("))
file:/xxx/java-p7-in-action/jvm-base/target/classes/

Thread.currentThread().getContextClassLoader().getResource("")
file:/xxx/java-p7-in-action/jvm-base/target/classes/

1.3 实现 AXClassLoader 定义

public class AXClassLoader extends ClassLoader {

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    try {
      byte[] bytes = Files.readAllBytes(Paths.get(getFileName(name)));
      for (int i = 0; i < bytes.length; i++) {
        bytes[i] = (byte) (255 - bytes[i]);
      }
      return defineClass(name, bytes, 0, bytes.length);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return super.findClass(name);
  }

  private String getFileName(String name) {
    int index = name.lastIndexOf('.');
    if (index == -1) {
      return ClassLoader.getSystemResource("").getPath() + name + ".xlass";
    } else {
      return ClassLoader.getSystemResource("").getPath() + name.substring(index + 1) + ".xlass";
    }
  }
}

1.4 调用加载

AXClassLoader axClassLoader = new AXClassLoader();
try {
    Class<?> hello = axClassLoader.loadClass("Hello");
    if (hello != null) {
        Object obj = hello.newInstance();
        Method method = hello.getDeclaredMethod("hello");
        method.invoke(obj);
    }
} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {
    e.printStackTrace();
}

2. 实现xlass打包的xar(类似class文件打包的jar)的加载

2.1 生成 xar 包

private static void createHelloXar() {
  try {
    XarSink xarSink = new XarSink();
    final XarFileSource fileSource;
    final File fileToCompress = getClasspathResourceAsFile(CLASS_FILE_NAME);
    assert fileToCompress != null;
    fileSource = new XarFileSource(fileToCompress);
    xarSink.addSource(fileSource);
    URL resource = Thread.currentThread().getContextClassLoader().getResource("");
    assert resource != null;
    xarSink.write(FileUtils.openOutputStream(new File(resource.getPath(), XAR_FILE_NAME)));
  } catch (Exception e) {
    e.printStackTrace();
  }
}

2.2 解析 xar 包

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
  try {
    XarSource xar = new FileXarSource(getClasspathResourceAsFile(name));
    XarEntry entry = xar.getEntry(CLASS_FILE_NAME);
    byte[] bytes = entry.getBytes();
    for (int i = 0; i < bytes.length; i++) {
      bytes[i] = (byte) (255 - bytes[i]);
    }
    return defineClass(CLASS_NAME, bytes, 0, bytes.length);
  } catch (IOException | URISyntaxException e) {
    e.printStackTrace();
  }
  return super.findClass(name);
}

2.3 加载 xar 包

/*
 *自定义加载 xar 文件
 */
AXClassLoader axClassLoader = new AXClassLoader();
try {
  Class<?> hello = axClassLoader.loadClass(XAR_FILE_NAME);
  if (hello != null) {
    Object obj = hello.newInstance();
    Method method = hello.getDeclaredMethod(METHOD_NAME);
    method.invoke(obj);
  }
} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {
  e.printStackTrace();
}

3. 基于自定义Classloader实现类的动态加载和卸载

3.1 理论支撑

如何实现类的卸载,需要满足哪些条件:

  • 该类的所有实例对象不可达
  • 该类的Class对象不可达
  • 该类的ClassLoader不可达

使用什么方式监控JVM的加载和卸载过程?

  • -verbose:class :同时追踪类的加载和卸载
  • -XX:+TraceClassLoading:单独跟踪类的加载
  • -XX:+TranceUnloading:单独跟踪累的卸载

类加载器特征

  • 每个ClassLoader都维护了一份自己的名称空间,同一个名称空间里不能出现两个同名的类;
  • 为了实现Java安全沙箱模型的类加载安全机制,Java默认采用了“双亲委派的加载链”结构;

3.2 动态加载

这里我们清楚,类的动态加载,或者说热加载,就是我们程序从一个固定路径读取 class 文件,同样类名称,前后修改方法实现,使用替换手法,就可以立即使用替换类的方法。

public class AXClassLoader extends ClassLoader {
  /**
    * 基础包路径
    */
  private String basePackagePath;
  /**
    * 需要该类加载器直接加载的类文件的基目录
    */
  private String basedir;
  /**
    * 需要由该类加载器直接加载的类名
    */
  private HashSet<Object> dynaclazns;

  public AXClassLoader(String basedir, String[] clazns, String packagePath) {
    super(null);
    this.basePackagePath = packagePath;
    this.basedir = basedir;
    dynaclazns = new HashSet<>();
    loadClassByMe(clazns);
  }

  @SneakyThrows
  private void loadClassByMe(String[] clazns) {
    for (int i = 0; i < clazns.length; i++) {
      String fileNamePath = getFileName(clazns[i]);
      byte[] bytes = Files.readAllBytes(Paths.get(fileNamePath));
      defineClass(this.basePackagePath + "." + clazns[i], bytes, 0, bytes.length);
      dynaclazns.add(clazns[i]);
    }
  }

  private String getFileName(String name) {
    int index = name.lastIndexOf('.');
    if (index == -1) {
      return this.basedir + name + ".class";
    } else {
      return this.basedir + name.substring(index + 1) + ".class";
    }
  }

  @Override
  protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    Class cls = findLoadedClass(name);
    if (!this.dynaclazns.contains(name) && cls == null) {
      cls = getSystemClassLoader().loadClass(name);
    }
    if (cls == null) {
      throw new ClassNotFoundException(name);
    }
    if (resolve) {
      resolveClass(cls);
    }
    return cls;
  }

  public static void main(String[] args) {
    String packagePath = "com.holddie.jvm.classloader.v3";
    new Timer("timer - 1")
      .schedule(
      new TimerTask() {
        @Override
        public void run() {
          try {
            // 每次都创建出一个新的类加载器
            AXClassLoader cl =
              new AXClassLoader(
              ClassLoader.getSystemResource("").getPath()
              + "v3/",
              new String[] {"Foo"},
              packagePath);
            Class cls = cl.loadClass(packagePath + ".Foo");
            Object foo = cls.newInstance();

            Method m = foo.getClass().getMethod("sayHello", new Class[] {});
            m.invoke(foo, new Object[] {});
          } catch (Exception ex) {
            ex.printStackTrace();
          }
        }
      },
      2000,
      3000);
  }
}

对应这里,我们使用 resources 文件夹下的 v3_2 的 Foo.class 文件替换之前文件夹下的 Foo.class 文件。注意我们为什么要使用一个 Schedule 定时加载,主要目的模拟程序在频繁类加载,方便前后两次热替换之后,方法输出结果展示;

3.3 卸载

上述介绍了满足类卸载的三个条件,code show as flow:

public static void main(String[] args) {
  String packagePath = "com.holddie.jvm.classloader.v3";
  new Timer("timer - 1")
    .schedule(
    new TimerTask() {
      @Override
      public void run() {
        try {
          AXClassLoader cl =
            new AXClassLoader(
            ClassLoader.getSystemResource("").getPath()
            + "v3/",
            new String[] {"Foo"},
            packagePath);
          Class cls = cl.loadClass(packagePath + ".Foo");
          Object foo = cls.newInstance();

          Method m = foo.getClass().getMethod("sayHello", new Class[] {});
          m.invoke(foo, new Object[] {});

          foo = null;
          cls = null;
          cl = null;
          System.gc();
          System.out.println("GC over");
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    },
    2000,
    3000);
}

此处注意我们,将对应的引用都置为空,之后手动执行了 System.gc() 方法,对此我们要观察类的加载和卸载需要在程序启动的时候设置JVM参数 -verbose:class 即可看到对应 loading 和 unloading 日志;

4. 基于自定义 Classloader 实现模块化机制:需要设计模块化机制。

主要思想:

  • 已经熟悉基于一个类的加载和卸载,在实际使用原理上大同小异;
  • 一个基础模块,提供抽象定义;
  • 两个实现模块,每个模块内部都有自己 module.properties 文件定义自己模块版本、模块入口等,以作为生成jar包运行使用;
  • 一个运行模块,使用两种模块化加载方式;

4.1 multi-skd 基础模块

定义模块抽象方法

public interface IFun {
  void sayHello(String name);
}

4.2 multi-fz 实现模块

依赖基础模块,fz 自定义实现

public class FzFun implements IFun {
  @Override
  public void sayHello(String name) {
    System.out.println("Hi " + name + ", i am FzFun");
  }
}

模块元数据声明

# 位置在 resources/META-INF/module.properties 
module.appCode=fz
module.jarVersion=1.0
module.fun.location=com.holddie.jvm.FzFun

4.3 multi-tz 实现模块

依赖基础模块,tz 自定义实现类

public class TzFun implements IFun {
  @Override
  public void sayHello(String name) {
    System.out.println("Hi " + name + ", i am TzFun");
  }
}

模块元数据声明

# 位置在 resources/META-INF/module.properties 
module.appCode=tz
module.jarVersion=1.0
module.fun.location=com.holddie.jvm.TzFun

4.4 multi-server 运行动态加载模块

使用两种方式,一种是JDK基于接口的动态代理,一种原生反射实现方法调用,详情在第五小节;

5. 使用 jar 作为模块,实现xar动态加载和卸载,综合应用前面的内容;

我们首先需要把实现模块的两个Jar包,打包OK,为下文加载提供方便,此处我们同时加载两个模块,并且指定了加载jar的名称,也可以使用自定义的目录区分;

5.1 ModuleClassLoader

public class ModuleClassLoader extends URLClassLoader {

  private final List<String> excludePackages;

  public ModuleClassLoader(URL[] urls, List<String> excludePackages) {
    super(urls);
    this.excludePackages = excludePackages;
  }

  @Override
  public Class<?> loadClass(String name) throws ClassNotFoundException {
    if (isExcludePackage(name)) {
      return super.loadClass(name);
    }
    synchronized (getClassLoadingLock(name)) {
      Class cls = findLoadedClass(name);
      if (cls == null) {
        try {
          cls = findClass(name);
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        }
      }
      if (cls != null) {
        return cls;
      }
    }
    return super.loadClass(name);
  }

  private boolean isExcludePackage(String name) {
    if (StringUtils.isEmpty(name)) {
      return false;
    }
    return this.excludePackages.stream().anyMatch(name::startsWith);
  }
}

5.2 DynamicProxy

public class DynamicProxy implements InvocationHandler {

    private Object target;

    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Long start = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        Long end = System.currentTimeMillis();
        String format = String.format("执行: class => %s, 方法=> %s, 参数 => %s, 耗时 => %sms, 执行结果 => %s, 当前路径 => %s",
                target.getClass().getName(),
                method.getName(),
                JSON.toJSON(args),
                end - start,
                JSON.toJSON(result),
                target.getClass().getResource("").getPath());
        System.out.println(format);
        return result;
    }
}

5.3 ModuleJar

public class ModuleJar {
    private String jarVersion;
    private String appCode;
    private String moduleJarUrl;
    private ModuleClassLoader moduleClassLoader;
    private String moduleFunLocation;

    public String getJarVersion() {
        return jarVersion;
    }

    public void setJarVersion(String jarVersion) {
        this.jarVersion = jarVersion;
    }

    public String getAppCode() {
        return appCode;
    }

    public void setAppCode(String appCode) {
        this.appCode = appCode;
    }

    public String getModuleJarUrl() {
        return moduleJarUrl;
    }

    public void setModuleJarUrl(String moduleJarUrl) {
        this.moduleJarUrl = moduleJarUrl;
    }

    public ModuleClassLoader getModuleClassLoader() {
        return moduleClassLoader;
    }

    public void setModuleClassLoader(ModuleClassLoader moduleClassLoader) {
        this.moduleClassLoader = moduleClassLoader;
    }

    public String getModuleFunLocation() {
        return moduleFunLocation;
    }

    public void setModuleFunLocation(String moduleFunLocation) {
        this.moduleFunLocation = moduleFunLocation;
    }
}

5.4 MultiModuleLoaderMain

public class MultiModuleLoaderMain {
  private final static String APP_CODE = "module.appCode";
  private final static String JAR_VERSION = "module.jarVersion";
  private final static String MODULE_FUN_LOCATION = "module.fun.location";

  public static void main(String[] args) {

    HashMap<String, ModuleJar> moduleCache = new HashMap<String, ModuleJar>();
    List<String> excludePackages = Arrays.asList("java", "com.holddie.jvm.sdk");
    List<String> jarUrls = Arrays.asList(
      "file:" + ClassLoader.getSystemResource("").getPath() + "multi-fz-1.0.0-jar-with-dependencies.jar",
      "file:" + ClassLoader.getSystemResource("").getPath() + "multi-tz-1.0.0-jar-with-dependencies.jar");
    jarUrls.forEach(url -> {
      try {
        URL moduleJarUrl = new URL(url);
        ModuleClassLoader moduleClassLoader = new ModuleClassLoader(new URL[]{moduleJarUrl}, excludePackages);
        Properties properties = getProperties(moduleClassLoader.getResourceAsStream("META-INF/module.properties"));
        ModuleJar moduleJar = new ModuleJar();
        moduleJar.setAppCode(properties.getProperty(APP_CODE));
        moduleJar.setJarVersion(properties.getProperty(JAR_VERSION));
        moduleJar.setModuleFunLocation(properties.getProperty(MODULE_FUN_LOCATION));
        moduleJar.setModuleClassLoader(moduleClassLoader);
        moduleJar.setModuleJarUrl(moduleJarUrl.getPath());
        moduleCache.put(moduleJar.getAppCode(), moduleJar);
      } catch (MalformedURLException e) {
        e.printStackTrace();
      }
    });

    ModuleJar fzModuleJar = moduleCache.get("fz");
    ModuleJar tzModuleJar = moduleCache.get("tz");

    try {
      ModuleClassLoader fzModuleJarModuleClassLoader = fzModuleJar.getModuleClassLoader();
      Object fzInstance = fzModuleJarModuleClassLoader.loadClass(fzModuleJar.getModuleFunLocation()).newInstance();
      Method fzSayHello = fzInstance.getClass().getMethod("sayHello", new Class[]{String.class});
      fzSayHello.invoke(fzInstance, new Object[]{"4456"});
      fzSayHello = null;
      fzInstance = null;
      fzModuleJarModuleClassLoader = null;
      fzModuleJar = null;
      moduleCache.put("fz", null);
      System.gc();

      IFun tzFun = (IFun) new DynamicProxy().bind(tzModuleJar.getModuleClassLoader().loadClass(tzModuleJar.getModuleFunLocation()).newInstance());
      tzFun.sayHello("123");
      tzFun.toString();
    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) {
      e.printStackTrace();
    }
    System.out.println("----------------------");
    System.out.println();
  }

  public static Properties getProperties(InputStream inputStream) {
    Properties properties = new Properties();
    try {
      properties.load(inputStream);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return properties;
  }
}

5.6 运行日志

[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.String from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from 
[Loaded java.util.stream.Sink from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.util.stream.MatchOps$BooleanTerminalSink from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.util.function.Supplier from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.util.stream.MatchOps$$Lambda$3/511754216 from java.util.stream.MatchOps]
[Loaded java.util.stream.MatchOps$1MatchSink from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded com.holddie.jvm.sdk.IFun from file:/xxx/java-p7-in-action/jvm-base/modularization/multi-sdk/target/classes/]
[Loaded com.holddie.jvm.FzFun from file:/xxx/java-p7-in-action/jvm-base/modularization/multi-server/target/classes/multi-fz-1.0.0-jar-with-dependencies.jar]

Hi 4456, i am FzFun
 
[Unloading class com.holddie.jvm.FzFun 0x00000007c0086828]
[Loaded java.lang.reflect.InvocationHandler from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded com.holddie.jvm.DynamicProxy from file:/xxx/java-p7-in-action/jvm-base/modularization/multi-server/target/classes/]
[Loaded com.holddie.jvm.TzFun from file:/xxx/java-p7-in-action/jvm-base/modularization/multi-server/target/classes/multi-tz-1.0.0-jar-with-dependencies.jar]
[Loaded java.lang.reflect.Proxy from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.reflect.WeakCache from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
.....
[Loaded java.lang.reflect.WeakCache$CacheValue from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.reflect.UndeclaredThrowableException from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
 
Hi 123, i am TzFun

[Loaded com.alibaba.fastjson.JSONStreamAware from file:/Users/zeyangg/.m2/repository/com/alibaba/fastjson/1.2.4/fastjson-1.2.4.jar]
[Loaded com.alibaba.fastjson.JSONAware from file:/xxx/.m2/repository/com/alibaba/fastjson/1.2.4/fastjson-1.2.4.jar]
[Loaded java.util.HashMap$KeyIterator from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded com.alibaba.fastjson.serializer.ListSerializer from file:/Users/zeyangg/.m2/repository/com/alibaba/fastjson/1.2.4/fastjson-1.2.4.jar]
[Loaded com.alibaba.fastjson.serializer.SerialContext from file:/Users/zeyangg/.m2/repository/com/alibaba/fastjson/1.2.4/fastjson-1.2.4.jar]
执行: class => com.holddie.jvm.TzFun, 方法=> sayHello, 参数 => ["123"], 耗时 => 0ms, 执行结果 => null, 当前路径 => /xxx/java-p7-in-action/jvm-base/modularization/multi-server/target/classes/com/holddie/jvm/
执行: class => com.holddie.jvm.TzFun, 方法=> toString, 参数 => null, 耗时 => 0ms, 执行结果 => com.holddie.jvm.TzFun@6ed3ef1, 当前路径 => /xxx/java-p7-in-action/jvm-base/modularization/multi-server/target/classes/com/holddie/jvm/
----------------------

[Loaded sun.misc.VMSupport from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.util.Hashtable$KeySet from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded sun.nio.cs.ISO_8859_1$Encoder from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]

注意,日志中的一些路径,自己做了一些脱敏使用 /xxx 代替,自己在看的时候,切勿当真以为是 /xxx,主要想表达的是一种数据脱敏;

最终代码参考实现:https://github.com/HoldDie/java-p7-in-action/tree/master/jvm-base/

参考链接

标签:Java,name,jar,public,String,JVM,java,class,进阶
来源: https://www.cnblogs.com/holddie/p/java-jin-jie-zhijvm-shi-zhan.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有