`

class卸载、热替换和Tomcat的热部署的分析[转]

 
阅读更多

class卸载、热替换和Tomcat的热部署的分析

     这篇文章主要是分析Tomcat中关于热部署和JSP更新替换的原理,在此之前先介绍class的热替换和class的卸载的原理。

一 class的热替换
ClassLoader中重要的方法
loadClass
      ClassLoader.loadClass(...) 是ClassLoader的入口点。当一个类没有指明用什么加载器加载的时候,JVM默认采用AppClassLoader加载器加载没有加载过的class,调用的方法的入口就是loadClass(...)。如果一个class被自定义的ClassLoader加载,那么JVM也会调用这个自定义的ClassLoader.loadClass(...)方法来加载class内部引用的一些别的class文件。重载这个方法,能实现自定义加载class的方式,抛弃双亲委托机制,但是即使不采用双亲委托机制,比如java.lang包中的相关类还是不能自定义一个同名的类来代替,主要因为JVM解析、验证class的时候,会进行相关判断。
 
defineClass
      系统自带的ClassLoader,默认加载程序的是AppClassLoader,ClassLoader加载一个class,最终调用的是defineClass(...)方法,这时候就在想是否可以重复调用defineClass(...)方法加载同一个类(或者修改过),最后发现调用多次的话会有相关错误:
...
java.lang.LinkageError 
attempted duplicate class definition
...
所以一个class被一个ClassLoader实例加载过的话,就不能再被这个ClassLoader实例再次加载(这里的加载指的是,调用了defileClass(...)放方法,重新加载字节码、解析、验证。)。而系统默认的AppClassLoader加载器,他们内部会缓存加载过的class,重新加载的话,就直接取缓存。所与对于热加载的话,只能重新创建一个ClassLoader,然后再去加载已经被加载过的class文件。

下面看一个class热加载的例子:
代码:HotSwapURLClassLoader自定义classloader,实现热替换的关键
  1 package testjvm.testclassloader;
  2 
  3 import java.io.File;
  4 import java.io.FileNotFoundException;
  5 import java.net.MalformedURLException;
  6 import java.net.URL;
  7 import java.net.URLClassLoader;
  8 import java.util.HashMap;
  9 import java.util.Map;
 10 
 11 /**
 12  * 只要功能是重新加载更改过的.class文件,达到热替换的作用
 13  * @author banana
 14  */
 15 public class HotSwapURLClassLoader extends URLClassLoader {
 16     //缓存加载class文件的最后最新修改时间
 17     public static Map<String,Long> cacheLastModifyTimeMap = new HashMap<String,Long>();
 18     //工程class类所在的路径
 19     public static String projectClassPath = "D:/Ecpworkspace/ZJob-Note/bin/";
 20     //所有的测试的类都在同一个包下
 21     public static String packagePath = "testjvm/testclassloader/";
 22     
 23     private static HotSwapURLClassLoader hcl = new HotSwapURLClassLoader();
 24     
 25     public HotSwapURLClassLoader() {
 26         //设置ClassLoader加载的路径
 27         super(getMyURLs());
 28     }
 29     
 30     public static HotSwapURLClassLoader  getClassLoader(){
 31         return hcl;
 32     } 
 33 
 34     private static  URL[] getMyURLs(){
 35         URL url = null;
 36         try {
 37             url = new File(projectClassPath).toURI().toURL();
 38         } catch (MalformedURLException e) {
 39             e.printStackTrace();
 40         }
 41         return new URL[] { url };
 42     }
 43     
 44     /**
 45      * 重写loadClass,不采用双亲委托机制("java."开头的类还是会由系统默认ClassLoader加载)
 46      */
 47     @Override
 48     public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
 49         Class clazz = null;
 50         //查看HotSwapURLClassLoader实例缓存下,是否已经加载过class
 51         //不同的HotSwapURLClassLoader实例是不共享缓存的
 52         clazz = findLoadedClass(name);
 53         if (clazz != null ) {
 54             if (resolve){
 55                 resolveClass(clazz);
 56             }
 57             //如果class类被修改过,则重新加载
 58             if (isModify(name)) {
 59                 hcl = new HotSwapURLClassLoader();
 60                 clazz = customLoad(name, hcl);
 61             }
 62             return (clazz);
 63         }
 64 
 65         //如果类的包名为"java."开始,则有系统默认加载器AppClassLoader加载
 66         if(name.startsWith("java.")){
 67             try {
 68                 //得到系统默认的加载cl,即AppClassLoader
 69                 ClassLoader system = ClassLoader.getSystemClassLoader();
 70                 clazz = system.loadClass(name);
 71                 if (clazz != null) {
 72                     if (resolve)
 73                         resolveClass(clazz);
 74                     return (clazz);
 75                 }
 76             } catch (ClassNotFoundException e) {
 77                 // Ignore
 78             }
 79         }
 80         
 81         return customLoad(name,this);
 82     }
 83 
 84     public Class load(String name) throws Exception{
 85         return loadClass(name);
 86     }
 87 
 88     /**
 89      * 自定义加载
 90      * @param name
 91      * @param cl 
 92      * @return
 93      * @throws ClassNotFoundException
 94      */
 95     public Class customLoad(String name,ClassLoader cl) throws ClassNotFoundException {
 96         return customLoad(name, false,cl);
 97     }
 98 
 99     /**
100      * 自定义加载
101      * @param name
102      * @param resolve
103      * @return
104      * @throws ClassNotFoundException
105      */
106     public Class customLoad(String name, boolean resolve,ClassLoader cl)
107             throws ClassNotFoundException {
108         //findClass()调用的是URLClassLoader里面重载了ClassLoader的findClass()方法
109         Class clazz = ((HotSwapURLClassLoader)cl).findClass(name);
110         if (resolve)
111             ((HotSwapURLClassLoader)cl).resolveClass(clazz);
112         //缓存加载class文件的最后修改时间
113         long lastModifyTime = getClassLastModifyTime(name);
114         cacheLastModifyTimeMap.put(name,lastModifyTime);
115         return clazz;
116     }
117     
118     public Class<?> loadClass(String name) throws ClassNotFoundException {
119         return loadClass(name,false);
120     }
121     
122     @Override
123     protected Class<?> findClass(String name) throws ClassNotFoundException {
124         // TODO Auto-generated method stub
125         return super.findClass(name);
126     }
127     
128     /**
129      * @param name
130      * @return .class文件最新的修改时间
131      */
132     private long getClassLastModifyTime(String name){
133         String path = getClassCompletePath(name);
134         File file = new File(path);
135         if(!file.exists()){
136             throw new RuntimeException(new FileNotFoundException(name));
137         }
138         return file.lastModified();
139     }
140     
141     /**
142      * 判断这个文件跟上次比是否修改过
143      * @param name
144      * @return
145      */
146     private boolean isModify(String name){
147         long lastmodify = getClassLastModifyTime(name);
148         long previousModifyTime = cacheLastModifyTimeMap.get(name);
149         if(lastmodify>previousModifyTime){
150             return true;
151         }
152         return false;
153     }
154     
155     /**
156      * @param name
157      * @return .class文件的完整路径 (e.g. E:/A.class)
158      */
159     private String getClassCompletePath(String name){
160         String simpleName = name.substring(name.lastIndexOf(".")+1);
161         return projectClassPath+packagePath+simpleName+".class";
162     }
163     
164 }
165 

代码:Hot被用来修改的类
1 package testjvm.testclassloader;
2 
3 public class Hot {
4     public void hot(){
5         System.out.println(" version 1 : "+this.getClass().getClassLoader());
6     }
7 }
8 

代码:TestHotSwap测试类
 1 package testjvm.testclassloader;
 2 
 3 import java.lang.reflect.Method;
 4 
 5 public class TestHotSwap {
 6 
 7     public static void main(String[] args) throws Exception {
 8         //开启线程,如果class文件有修改,就热替换
 9         Thread t = new Thread(new MonitorHotSwap());
10         t.start();
11     }
12 }
13 
14 class MonitorHotSwap implements Runnable {
15     // Hot就是用于修改,用来测试热加载
16     private String className = "testjvm.testclassloader.Hot";
17     private Class hotClazz = null;
18     private HotSwapURLClassLoader hotSwapCL = null;
19 
20     @Override
21     public void run() {
22         try {
23             while (true) {
24                 initLoad();
25                 Object hot = hotClazz.newInstance();
26                 Method m = hotClazz.getMethod("hot");
27                 m.invoke(hot, null); //打印出相关信息
28                 // 每隔10秒重新加载一次
29                 Thread.sleep(10000);
30             }
31         } catch (Exception e) {
32             e.printStackTrace();
33         }
34     }
35 
36     /**
37      * 加载class
38      */
39     void initLoad() throws Exception {
40         hotSwapCL = HotSwapURLClassLoader.getClassLoader();
41         // 如果Hot类被修改了,那么会重新加载,hotClass也会返回新的
42         hotClazz = hotSwapCL.loadClass(className);
43     }
44 }

     在测试类运行的时候,修改Hot.class文件 
Hot.class
原来第五行:System.out.println(" version 1 : "+this.getClass().getClassLoader());
改后第五行:System.out.println(" version 2 : "+this.getClass().getClassLoader());
   
输出
 version 1 : testjvm.testclassloader.HotSwapURLClassLoader@610f7612
 version 1 : testjvm.testclassloader.HotSwapURLClassLoader@610f7612
 version 2 : testjvm.testclassloader.HotSwapURLClassLoader@45e4d960
 version 2 : testjvm.testclassloader.HotSwapURLClassLoader@45e4d960
     所以HotSwapURLClassLoader是重加载了Hot类 。注意上面,其实当加载修改后的Hot时,HotSwapURLClassLoader实例跟加载没修改Hot的HotSwapURLClassLoader不是同一个。
图:HotSwapURLClassLoader加载情况

     总结:上述类热加载,需要自定义ClassLoader,并且只能重新实例化ClassLoader实例,利用新的ClassLoader实例才能重新加载之前被加载过的class。并且程序需要模块化,才能利用这种热加载方式。

二 class卸载
      在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space区域。如果加载的class文件很多,那么可能导致PermGen space区域空间溢出。引起:java.lang.OutOfMemoryErrorPermGen space.  对于有些Class我们可能只需要使用一次,就不再需要了,也可能我们修改了class文件,我们需要重新加载 newclass,那么oldclass就不再需要了。那么JVM怎么样才能卸载Class呢。

      JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):

   - 该类所有的实例都已经被GC。
   - 加载该类的ClassLoader实例已经被GC。
   - 该类的java.lang.Class对象没有在任何地方被引用。


     GC的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的。 

例子:
代码:SimpleURLClassLoader,一个简单的自定义classloader
  1 package testjvm.testclassloader;
  2 
  3 import java.io.File;
  4 import java.net.MalformedURLException;
  5 import java.net.URL;
  6 import java.net.URLClassLoader;
  7 
  8 public class SimpleURLClassLoader extends URLClassLoader {
  9     //工程class类所在的路径
 10     public static String projectClassPath = "E:/IDE/work_place/ZJob-Note/bin/";
 11     //所有的测试的类都在同一个包下
 12     public static String packagePath = "testjvm/testclassloader/";
 13     
 14     public SimpleURLClassLoader() {
 15         //设置ClassLoader加载的路径
 16         super(getMyURLs());
 17     }
 18     
 19     private static  URL[] getMyURLs(){
 20         URL url = null;
 21         try {
 22             url = new File(projectClassPath).toURI().toURL();
 23         } catch (MalformedURLException e) {
 24             e.printStackTrace();
 25         }
 26         return new URL[] { url };
 27     }
 28     
 29     public Class load(String name) throws Exception{
 30         return loadClass(name);
 31     }
 32 
 33     public Class<?> loadClass(String name) throws ClassNotFoundException {
 34         return loadClass(name,false);
 35     }
 36     
 37     /**
 38      * 重写loadClass,不采用双亲委托机制("java."开头的类还是会由系统默认ClassLoader加载)
 39      */
 40     @Override
 41     public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
 42         Class clazz = null;
 43         //查看HotSwapURLClassLoader实例缓存下,是否已经加载过class
 44         clazz = findLoadedClass(name);
 45         if (clazz != null ) {
 46             if (resolve){
 47                 resolveClass(clazz);
 48             }
 49             return (clazz);
 50         }
 51 
 52         //如果类的包名为"java."开始,则有系统默认加载器AppClassLoader加载
 53         if(name.startsWith("java.")){
 54             try {
 55                 //得到系统默认的加载cl,即AppClassLoader
 56                 ClassLoader system = ClassLoader.getSystemClassLoader();
 57                 clazz = system.loadClass(name);
 58                 if (clazz != null) {
 59                     if (resolve)
 60                         resolveClass(clazz);
 61                     return (clazz);
 62                 }
 63             } catch (ClassNotFoundException e) {
 64                 // Ignore
 65             }
 66         }
 67         
 68         return customLoad(name,this);
 69     }
 70 
 71     /**
 72      * 自定义加载
 73      * @param name
 74      * @param cl 
 75      * @return
 76      * @throws ClassNotFoundException
 77      */
 78     public Class customLoad(String name,ClassLoader cl) throws ClassNotFoundException {
 79         return customLoad(name, false,cl);
 80     }
 81 
 82     /**
 83      * 自定义加载
 84      * @param name
 85      * @param resolve
 86      * @return
 87      * @throws ClassNotFoundException
 88      */
 89     public Class customLoad(String name, boolean resolve,ClassLoader cl)
 90             throws ClassNotFoundException {
 91         //findClass()调用的是URLClassLoader里面重载了ClassLoader的findClass()方法
 92         Class clazz = ((SimpleURLClassLoader)cl).findClass(name);
 93         if (resolve)
 94             ((SimpleURLClassLoader)cl).resolveClass(clazz);
 95         return clazz;
 96     }
 97     
 98     @Override
 99     protected Class<?> findClass(String name) throws ClassNotFoundException {
100         return super.findClass(name);
101     }
102 }
103 

代码:A 
1 public class A {  
2 //  public static final Level CUSTOMLEVEL = new Level("test", 550) {}; // 内部类
3 }
代码:TestClassUnload,测试类
 1 package testjvm.testclassloader;
 2 
 3 public class TestClassUnLoad {
 4 
 5     public static void main(String[] args) throws Exception {
 6         SimpleURLClassLoader loader = new SimpleURLClassLoader();
 7         // 用自定义的加载器加载A
 8         Class clazzA = loader.load("testjvm.testclassloader.A");
 9         Object a = clazzA.newInstance();
10         // 清除相关引用
11         a = null;
12         clazzA = null;
13         loader = null;
14         // 执行一次gc垃圾回收
15         System.gc();
16         System.out.println("GC over");
17     }
18 }
19 

      运行的时候配置VM参数: -verbose:class;用于查看class的加载与卸载情况。如果用的是Eclipse,在Run Configurations中配置此参数即可。
图:Run Configurations配置    


输出结果
.....
[Loaded java.net.URI$Parser from E:\java\jdk1.7.0_03\jre\lib\rt.jar]
[Loaded testjvm.testclassloader.A from file:/E:/IDE/work_place/ZJob-Note/bin/]
[Unloading class testjvm.testclassloader.A]
GC over
[Loaded sun.misc.Cleaner from E:\java\jdk1.7.0_03\jre\lib\rt.jar]
[Loaded java.lang.Shutdown from E:\java\jdk1.7.0_03\jre\lib\rt.jar]
......
      上面输出结果中的确A.class被加载了,然后A.class又被卸载了。这个例子中说明了,即便是class加载进了内存,也是可以被释放的。

图:程序运行中,引用没清楚前,内存中情况

图:垃圾回收后,程序没结束前,内存中情况
 

    1、有启动类加载器加载的类型在整个运行期间是不可能被卸载的(jvm和jls规范).
    2、被系统类加载器和标准扩展类加载器加载的类型在运行期间不太可能被卸载,因为系统类加载器实例或者标准扩展类的实例基本上在整个运行期间总能直接或者间接的访问的到,其达到unreachable的可能性极小.(当然,在虚拟机快退出的时候可以,因为不管ClassLoader实例或者Class(java.lang.Class)实例也都是在堆中存在,同样遵循垃圾收集的规则).
    3、被开发者自定义的类加载器实例加载的类型只有在很简单的上下文环境中才能被卸载,而且一般还要借助于强制调用虚拟机的垃圾收集功能才可以做到.可以预想,稍微复杂点的应用场景中(尤其很多时候,用户在开发自定义类加载器实例的时候采用缓存的策略以提高系统性能),被加载的类型在运行期间也是几乎不太可能被卸载的(至少卸载的时间是不确定的).
      综合以上三点, 一个已经加载的类型被卸载的几率很小至少被卸载的时间是不确定的.同时,我们可以看的出来,开发者在开发代码时候,不应该对虚拟机的类型卸载做任何假设的前提下来实现系统中的特定功能.
       
三 Tomcat中关于类的加载与卸载
        Tomcat中与其说有热加载,还不如说是热部署来的准确些。因为对于一个应用,其中class文件被修改过,那么Tomcat会先卸载这个应用(Context),然后重新加载这个应用,其中关键就在于自定义ClassLoader的应用。这里有篇文章很好的介绍了tomcat中对于ClassLoader的应用,请点击here

Tomcat启动的时候,ClassLoader加载的流程:
1 Tomcat启动的时候,用system classloader即AppClassLoader加载{catalina.home}/bin里面的jar包,也就是tomcat启动相关的jar包。
2 Tomcat启动类Bootstrap中有3个classloader属性,catalinaLoader、commonLoader、sharedLoader在Tomcat7中默认他们初始化都为同一个StandardClassLoader实例。具体的也可以在{catalina.home}/bin/bootstrap.jar包中的catalina.properites中进行配置。
3 StandardClassLoader加载{catalina.home}/lib下面的所有Tomcat用到的jar包。
4 一个Context容器,代表了一个app应用。Context-->WebappLoader-->WebClassLoader。并且Thread.contextClassLoader=WebClassLoader。应用程序中的jsp文件、class类、lib/*.jar包,都是WebClassLoader加载的。

Tomcat加载资源的概况图:



当Jsp文件修改的时候,Tomcat更新步骤:
1 但访问1.jsp的时候,1.jsp的包装类JspServletWrapper会去比较1.jsp文件最新修改时间和上次的修改时间,以此判断1.jsp是否修改过。
2 1.jsp修改过的话,那么jspservletWrapper会清除相关引用,包括1.jsp编译后的servlet实例和加载这个servlet的JasperLoader实例。
3 重新创建一个JasperLoader实例,重新加载修改过后的1.jsp,重新生成一个Servlet实例。
4 返回修改后的1.jsp内容给用户。
图:Jsp清除引用和资源


当app下面的class文件修改的时候,Tomcat更新步骤:
1 Context容器会有专门线程监控app下面的类的修改情况。
2 如果发现有类被修改了。那么调用Context.reload()。清楚一系列相关的引用和资源。
3 然后创新创建一个WebClassLoader实例,重新加载app下面需要的class。
图:Context清除引用和资源 

     在一个有一定规模的应用中,如果文件修改多次,重启多次的话,java.lang.OutOfMemoryErrorPermGen space这个错误的的出现非常频繁。主要就是因为每次重启重新加载大量的class,超过了PermGen space设置的大小。两种情况可能导致PermGen space溢出。一、GC(Garbage Collection)在主程序运行期对PermGen space没有进行清理(GC的不可控行),二、重启之前WebClassLoader加载的class在别的地方还存在着引用。这里有篇很好的文章介绍了class内存泄露-here


参考:
http://blog.csdn.net/runanli/article/details/2972361(关于Class类加载器 内存泄漏问题的探讨)
http://www.blogjava.net/zhuxing/archive/2008/07/24/217285.html(Java虚拟机类型卸载和类型更新解析)
http://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/(Java 类的热替换 —— 概念、设计与实现)
http://www.iteye.com/topic/136427(classloader体系结构)
分享到:
评论

相关推荐

    setuptools-0.6b3-py2.4.egg

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Java项目之jspm充电桩综合管理系统(源码 + 说明文档)

    Java项目之jspm充电桩综合管理系统(源码 + 说明文档) 2 系统开发环境 4 2.1 Java技术 4 2.2 JSP技术 4 2.3 B/S模式 4 2.4 MyEclipse环境配置 5 2.5 MySQL环境配置 5 2.6 SSM框架 6 3 系统分析 7 3.1 系统可行性分析 7 3.1.1 经济可行性 7 3.1.2 技术可行性 7 3.1.3 运行可行性 7 3.2 系统现状分析 7 3.3 功能需求分析 8 3.4 系统设计规则与运行环境 9 3.5系统流程分析 9 3.5.1操作流程 9 3.5.2添加信息流程 10 3.5.3删除信息流程 11 4 系统设计 12 4.1 系统设计主要功能 12 4.2 数据库设计 13 4.2.1 数据库设计规范 13 4.2.2 E-R图 13 4.2.3 数据表 14 5 系统实现 24 5.1系统功能模块 24 5.2后台功能模块 26 5.2.1管理员功能 26 5.2.2用户功能 30 6 系统测试 32 6.1 功能测试 32 6.2 可用性测试 32 6.3 维护测试 33 6.4 性能测试 33

    基于JSP药品进货销售库存管理系统源码.zip

    这个是一个JSP药品进货销售库存管理系统,管理员角色包含以下功能:管理员登录,进货管理,销售管理,库存管理,员工管理,客户管理,供应商管理,修改密码等功能。 本项目实现的最终作用是基于JSP药品进货销售库存管理系统 分为1个角色 第1个角色为管理员角色,实现了如下功能: - 供应商管理 - 修改密码 - 员工管理 - 客户管理 - 库存管理 - 管理员登录 - 进货管理 - 销售管理

    基于JSP商品销售管理系统源码.zip

    这个是一个JSP商品销售管理系统,管理员角色包含以下功能:管理员登录,管理员首页,用户管理,供应商管理,商品管理,入库管理,出库管理,系统公告管理,管理员信息修改等功能。用户角色包含以下功能:用户注册,用户登录,供应商管理,商品管理,入库管理,出库管理,系统公告查看,个人信息修改等功能。 本项目实现的最终作用是基于JSP商品销售管理系统 分为2个角色 第1个角色为管理员角色,实现了如下功能: - 供应商管理 - 入库管理 - 出库管理 - 商品管理 - 用户管理 - 管理员信息修改 - 管理员登录 - 管理员首页 - 系统公告管理 第2个角色为用户角色,实现了如下功能: - 个人信息修改 - 供应商管理 - 入库管理 - 出库管理 - 商品管理 - 用户注册 - 用户登录 - 系统公告查看

    什么是mysql以及学习了解mysql的意义是什么

    mysql

    setuptools-38.4.1-py2.py3-none-any.whl

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    setuptools-8.0-py2.py3-none-any.whl

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    setuptools-41.0.0-py2.py3-none-any.whl

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    setuptools-12.0.1.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    setuptools-11.2-py2.py3-none-any.whl

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip

    基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip基于Pyotrch的深度学习物体分类可视化系统源码+预训练模型+详细训练教程.zip

    Windows系统安装VMware虚拟机的教程

    附件是Windows系统安装VMware虚拟机的教程,文件绿色安全,请大家放心下载,仅供交流学习使用,无任何商业目的!

    setuptools-8.0.2-py2.py3-none-any.whl

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    setuptools-16.0-py2.py3-none-any.whl

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    定制化的shell脚本.zip

    shell脚本 科技Lion 的 Shell 脚本工具是一款全能脚本工具箱,专为 VPS 监控、测试和管理而设计。无论您是初学者还是经验丰富的用户,该工具都能为您提供便捷的解决方案。集成了独创的 Docker 管理功能,让您轻松管理容器化应用;LNMP建站解决方案 能帮助您快速搭建网站,站点优化,防御,备份还原迁移一应俱全;并且整合了各类系统工具面板的安装及使用,使系统维护变得更加简单。我们的目标是成为全网最优秀的 VPS 一键脚本工具,为用户提供高效、便捷的科技支持

    AC-课程网盘链接提取码下载 .txt

    AC-课程网盘链接提取码下载 .txt

    深度学习大作业Python基于LSTM自动写诗源代码+详细文档+PPT,数据集采用chinese-poetry

    深度学习大作业Python基于LSTM自动写诗源代码+详细文档+PPT,数据集采用chinese-poetry

    setuptools-18.3.1.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    AI-课程网盘链接提取码下载 .txt

    AI-课程网盘链接提取码下载 .txt

    setuptools-15.0.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

Global site tag (gtag.js) - Google Analytics