JAVA 中的内存泄漏

JAVA 编程中的内存泄漏,从泄漏的内存位置角度可以分为两种:JVM 中 Java Heap 的内存泄漏;JVM 内存中 native memory 的内存泄漏。

Java Heap 的内存泄漏

Java 对象存储在 JVM 进程空间中的 Java Heap 中,Java Heap 可以在 JVM 运行过程中动态变化。如果 Java 对象越来越多,占据 Java Heap 的空间也越来越大,JVM 会在运行时扩充 Java Heap 的容量。如果 Java Heap 容量扩充到上限,并且在 GC 后仍然没有足够空间分配新的 Java 对象,便会抛出 out of memory 异常,导致 JVM 进程崩溃。

Java Heap 中 out of memory 异常的出现有两种原因——①程序过于庞大,致使过多 Java 对象的同时存在;②程序编写的错误导致 Java Heap 内存泄漏。

多种原因可能导致 Java Heap 内存泄漏。JNI 编程错误也可能导致 Java Heap 的内存泄漏。

JVM 中 native memory 的内存泄漏

从操作系统角度看,JVM 在运行时和其它进程没有本质区别。在系统级别上,它们具有同样的调度机制,同样的内存分配方式,同样的内存格局。

JVM 进程空间中,Java Heap 以外的内存空间称为 JVM 的 native memory。进程的很多资源都是存储在 JVM 的 native memory 中,例如载入的代码映像,线程的堆栈,线程的管理控制块,JVM 的静态数据、全局数据等等。也包括 JNI 程序中 native code 分配到的资源。

在 JVM 运行中,多数进程资源从 native memory 中动态分配。当越来越多的资源在 native memory 中分配,占据越来越多 native memory 空间并且达到 native memory 上限时,JVM 会抛出异常,使 JVM 进程异常退出。而此时 Java Heap 往往还没有达到上限。

多种原因可能导致 JVM 的 native memory 内存泄漏。例如 JVM 在运行中过多的线程被创建,并且在同时运行。JVM 为线程分配的资源就可能耗尽 native memory 的容量。

JNI 编程错误也可能导致 native memory 的内存泄漏。对这个话题的讨论是本文的重点。

 

JNI 编程中明显的内存泄漏

JNI 编程实现了 native code 和 Java 程序的交互,因此 JNI 代码编程既遵循 native code 编程语言的编程规则,同时也遵守 JNI 编程的文档规范。在内存管理方面,native code 编程语言本身的内存管理机制依然要遵循,同时也要考虑 JNI 编程的内存管理。

本章简单概括 JNI 编程中显而易见的内存泄漏。从 native code 编程语言自身的内存管理,和 JNI 规范附加的内存管理两方面进行阐述。

Native Code 本身的内存泄漏

JNI 编程首先是一门具体的编程语言,或者 C 语言,或者 C++,或者汇编,或者其它 native 的编程语言。每门编程语言环境都实现了自身的内存管理机制。因此,JNI 程序开发者要遵循 native 语言本身的内存管理机制,避免造成内存泄漏。以 C 语言为例,当用 malloc() 在进程堆中动态分配内存时,JNI 程序在使用完后,应当调用 free() 将内存释放。总之,所有在 native 语言编程中应当注意的内存泄漏规则,在 JNI 编程中依然适应。

Native 语言本身引入的内存泄漏会造成 native memory 的内存,严重情况下会造成 native memory 的 out of memory。

Global Reference 引入的内存泄漏

JNI 编程还要同时遵循 JNI 的规范标准,JVM 附加了 JNI 编程特有的内存管理机制。

JNI 中的 Local Reference 只在 native method 执行时存在,当 native method 执行完后自动失效。这种自动失效,使得对 Local Reference 的使用相对简单,native method 执行完后,它们所引用的 Java 对象的 reference count 会相应减 1。不会造成 Java Heap 中 Java 对象的内存泄漏。

而 Global Reference 对 Java 对象的引用一直有效,因此它们引用的 Java 对象会一直存在 Java Heap 中。程序员在使用 Global Reference 时,需要仔细维护对 Global Reference 的使用。如果一定要使用 Global Reference,务必确保在不用的时候删除。就像在 C 语言中,调用 malloc() 动态分配一块内存之后,调用 free() 释放一样。否则,Global Reference 引用的 Java 对象将永远停留在 Java Heap 中,造成 Java Heap 的内存泄漏。

 

JNI 编程中潜在的内存泄漏——对 LocalReference 的深入理解

Local Reference 在 native method 执行完成后,会自动被释放,似乎不会造成任何的内存泄漏。但这是错误的。对 Local Reference 的理解不够,会造成潜在的内存泄漏。

本章重点阐述 Local Reference 使用不当可能引发的内存泄漏。引入两个错误实例,也是 JNI 程序员容易忽视的错误;在此基础上介绍 Local Reference 表,对比 native method 中的局部变量和 JNI Local Reference 的不同,使读者深入理解 JNI Local Reference 的实质;最后为 JNI 程序员提出应该如何正确合理使用 JNI Local Reference,以避免内存泄漏。

错误实例 1

在某些情况下,我们可能需要在 native method 里面创建大量的 JNI Local Reference。这样可能导致 native memory 的内存泄漏,如果在 native method 返回之前 native memory 已经被用光,就会导致 native memory 的 out of memory。

在代码清单 1 里,我们循环执行 count 次,JNI function NewStringUTF() 在每次循环中从 Java Heap 中创建一个 String 对象,str 是 Java Heap 传给 JNI native method 的 Local Reference,每次循环中新创建的 String 对象覆盖上次循环中 str 的内容。str 似乎一直在引用到一个 String 对象。整个运行过程中,我们看似只创建一个 Local Reference。

执行代码清单 1 的程序,第一部分为 Java 代码,nativeMethod(int i) 中,输入参数设定循环的次数。第二部分为 JNI 代码,用 C 语言实现了 nativeMethod(int i)。

清单 1. Local Reference 引发内存泄漏
 Java 代码部分
class TestLocalReference {
private native void nativeMethod(int i);
public static void main(String args[]) {
TestLocalReference c = new TestLocalReference();
//call the jni native method
c.nativeMethod(1000000);
}
static {
//load the jni library
System.loadLibrary("StaticMethodCall");
}
} JNI 代码,nativeMethod(int i) 的 C 语言实现
#include<stdio.h>
#include<jni.h>
#include"TestLocalReference.h"
JNIEXPORT void JNICALL Java_TestLocalReference_nativeMethod
(JNIEnv * env, jobject obj, jint count)
{
jint i = 0;
jstring str; for(; i<count; i++)
str = (*env)->NewStringUTF(env, "0");
}
运行结果
JVMCI161: FATAL ERROR in native method: Out of memory when expanding
local ref table beyond capacity
at TestLocalReference.nativeMethod(Native Method)
at TestLocalReference.main(TestLocalReference.java:9)

运行结果证明,JVM 运行异常终止,原因是创建了过多的 Local Reference,从而导致 out of memory。实际上,nativeMethod 在运行中创建了越来越多的 JNI Local Reference,而不是看似的始终只有一个。过多的 Local Reference,导致了 JNI 内部的 JNI Local Reference 表内存溢出。

错误实例 2

实例 2 是实例 1 的变种,Java 代码未作修改,但是 nativeMethod(int i) 的 C 语言实现稍作修改。在 JNI 的 native method 中实现的 utility 函数中创建 Java 的 String 对象。utility 函数只建立一个 String 对象,返回给调用函数,但是 utility 函数对调用者的使用情况是未知的,每个函数都可能调用它,并且同一函数可能调用它多次。在实例 2 中,nativeMethod 在循环中调用 count 次,utility 函数在创建一个 String 对象后即返回,并且会有一个退栈过程,似乎所创建的 Local Reference 会在退栈时被删除掉,所以应该不会有很多 Local Reference 被创建。实际运行结果并非如此。

清单 2. Local Reference 引发内存泄漏
Java 代码部分参考实例 1,未做任何修改。

 JNI 代码,nativeMethod(int i) 的 C 语言实现
#include<stdio.h>
#include<jni.h>
#include"TestLocalReference.h"
jstring CreateStringUTF(JNIEnv * env)
{
return (*env)->NewStringUTF(env, "0");
}
JNIEXPORT void JNICALL Java_TestLocalReference_nativeMethod
(JNIEnv * env, jobject obj, jint count)
{
jint i = 0;
for(; i<count; i++)
{
str = CreateStringUTF(env);
}
}
运行结果
JVMCI161: FATAL ERROR in native method: Out of memory when expanding local ref
table beyond capacity
at TestLocalReference.nativeMethod(Native Method)
at TestLocalReference.main(TestLocalReference.java:9)

运行结果证明,实例 2 的结果与实例 1 的完全相同。过多的 Local Reference 被创建,仍然导致了 JNI 内部的 JNI Local Reference 表内存溢出。实际上,在 utility 函数 CreateStringUTF(JNIEnv * env)

执行完成后的退栈过程中,创建的 Local Reference 并没有像 native code 中的局部变量那样被删除,而是继续在 Local Reference 表中存在,并且有效。Local Reference 和局部变量有着本质的区别。

Local Reference 深层解析

Java JNI 的文档规范只描述了 JNI Local Reference 是什么(存在的目的),以及应该怎么使用 Local Reference(开放的接口规范)。但是对 Java 虚拟机中 JNI Local Reference 的实现并没有约束,不同的 Java 虚拟机有不同的实现机制。这样的好处是,不依赖于具体的 JVM 实现,有好的可移植性;并且开发简单,规定了“应该怎么做、怎么用”。但是弊端是初级开发者往往看不到本质,“不知道为什么这样做”。对 Local Reference 没有深层的理解,就会在编程过程中无意识的犯错。

Local Reference 和 Local Reference 表

理解 Local Reference 表的存在是理解 JNI Local Reference 的关键。

JNI Local Reference 的生命期是在 native method 的执行期(从 Java 程序切换到 native code 环境时开始创建,或者在 native method 执行时调用 JNI function 创建),在 native method 执行完毕切换回 Java 程序时,所有 JNI Local Reference 被删除,生命期结束(调用 JNI function 可以提前结束其生命期)。

实际上,每当线程从 Java 环境切换到 native code 上下文时(J2N),JVM 会分配一块内存,创建一个 Local Reference 表,这个表用来存放本次 native method 执行中创建的所有的 Local Reference。每当在 native code 中引用到一个 Java 对象时,JVM 就会在这个表中创建一个 Local Reference。比如,实例 1 中我们调用 NewStringUTF() 在 Java Heap 中创建一个 String 对象后,在 Local Reference 表中就会相应新增一个 Local Reference。

图 1. Local Reference 表、Local Reference 和 Java 对象的关系

图 1 中:

⑴运行 native method 的线程的堆栈记录着 Local Reference 表的内存位置(指针 p)。

⑵ Local Reference 表中存放 JNI Local Reference,实现 Local Reference 到 Java 对象的映射。

⑶ native method 代码间接访问 Java 对象(java obj1,java obj2)。通过指针 p 定位相应的 Local Reference 的位置,然后通过相应的 Local Reference 映射到 Java 对象。

⑷当 native method 引用一个 Java 对象时,会在 Local Reference 表中创建一个新 Local Reference。在 Local Reference 结构中写入内容,实现 Local Reference 到 Java 对象的映射。

⑸ native method 调用 DeleteLocalRef() 释放某个 JNI Local Reference 时,首先通过指针 p 定位相应的 Local Reference 在 Local Ref 表中的位置,然后从 Local Ref 表中删除该 Local Reference,也就取消了对相应 Java 对象的引用(Ref count 减 1)。

⑹当越来越多的 Local Reference 被创建,这些 Local Reference 会在 Local Ref 表中占据越来越多内存。当 Local Reference 太多以至于 Local Ref 表的空间被用光,JVM 会抛出异常,从而导致 JVM 的崩溃。

Local Ref 不是 native code 的局部变量

很多人会误将 JNI 中的 Local Reference 理解为 Native Code 的局部变量。这是错误的。

Native Code 的局部变量和 Local Reference 是完全不同的,区别可以总结为:

⑴局部变量存储在线程堆栈中,而 Local Reference 存储在 Local Ref 表中。

⑵局部变量在函数退栈后被删除,而 Local Reference 在调用 DeleteLocalRef() 后才会从 Local Ref 表中删除,并且失效,或者在整个 Native Method 执行结束后被删除。

⑶可以在代码中直接访问局部变量,而 Local Reference 的内容无法在代码中直接访问,必须通过 JNI function 间接访问。JNI function 实现了对 Local Reference 的间接访问,JNI function 的内部实现依赖于具体 JVM。

代码清单 1 中 str = (*env)->NewStringUTF(env, "0");

str 是 jstring 类型的局部变量。Local Ref 表中会新创建一个 Local Reference,引用到 NewStringUTF(env, "0") 在 Java Heap 中新建的 String 对象。如图 2 所示:

图 2. str 间接引用 string 对象

图 2 中,str 是局部变量,在 native method 堆栈中。Local Ref3 是新创建的 Local Reference,在 Local Ref 表中,引用新创建的 String 对象。JNI 通过 str 和指针 p 间接定位 Local Ref3,但 p 和 Local Ref3 对 JNI 程序员不可见。

Local Reference 导致内存泄漏

在以上论述基础上,我们通过分析错误实例 1 和实例 2,来分析 Local Reference 可能导致的内存泄漏,加深对 Local Reference 的深层理解。

分析错误实例 1:

局部变量 str 在每次循环中都被重新赋值,间接指向最新创建的 Local Reference,前面创建的 Local Reference 一直保留在 Local Ref 表中。

在实例 1 执行完第 i 次循环后,内存布局如图 3:

图 3. 执行 i 次循环后的内存布局

继续执行完第 i+1 次循环后,内存布局发生变化,如图 4:

图 4. 执行 i+1 次循环后的内存布局

图 4 中,局部变量 str 被赋新值,间接指向了 Local Ref i+1。在 native method 运行过程中,我们已经无法释放 Local Ref i 占用的内存,以及 Local Ref i 所引用的第 i 个 string 对象所占据的 Java Heap 内存。所以,native memory 中 Local Ref i 被泄漏,Java Heap 中创建的第 i 个 string 对象被泄漏了。

也就是说在循环中,前面创建的所有 i 个 Local Reference 都泄漏了 native memory 的内存,创建的所有 i 个 string 对象都泄漏了 Java Heap 的内存。

直到 native memory 执行完毕,返回到 Java 程序时(N2J),这些泄漏的内存才会被释放,但是 Local Reference 表所分配到的内存往往很小,在很多情况下 N2J 之前可能已经引发严重内存泄漏,导致 Local Reference 表的内存耗尽,使 JVM 崩溃,例如错误实例 1。

分析错误实例 2:

实例 2 与实例 1 相似,虽然每次循环中调用工具函数 CreateStringUTF(env) 来创建对象,但是在 CreateStringUTF(env) 返回退栈过程中,只是局部变量被删除,而每次调用创建的 Local Reference 仍然存在 Local Ref 表中,并且有效引用到每个新创建的 string 对象。str 局部变量在每次循环中被赋新值。

这样的内存泄漏是潜在的,但是这样的错误在 JNI 程序员编程过程中却经常出现。通常情况,在触发 out of memory 之前,native method 已经执行完毕,切换回 Java 环境,所有 Local Reference 被删除,问题也就没有显露出来。但是某些情况下就会引发 out of memory,导致实例 1 和实例 2 中的 JVM 崩溃。

控制 Local Reference 生命期

因此,在 JNI 编程时,正确控制 JNI Local Reference 的生命期。如果需要创建过多的 Local Reference,那么在对被引用的 Java 对象操作结束后,需要调用 JNI function(如 DeleteLocalRef()),及时将 JNI Local Reference 从 Local Ref 表中删除,以避免潜在的内存泄漏。


常见问题:

一、jni调用java对象

  JNI提供的功能之一是在本地代码中使用Java对象。包括:创建一个java类对象和通过函数传递一个java对象。创建一个java类对象,首先需要得到得到使用FindClass/GetObjectClass函数得到该类,然后使用GetMethodID方法得到该类的方法id,然后调用该函数。 Java 和 Native 代码之间函数调用时,如果是简单类型,也就是内置类型,比如 int, char 等是值传递(pass by value),而其它 Java 对象都是引用传递(pass by reference),这些对象引用由 JVM 传给 Native 代码。

  在本地方法中调用Java对象的方法的步骤:

  1)获取你需要访问的Java对象的类

    FindClass通过传java中完整的类名来查找java的class

    GetObjectClass通过传入jni中的一个java的引用来获取该引用的类型。

    他们之间的区别是,前者要求你必须知道完整的类名,后者要求在Jni有一个类的引用。

  2)获取MethodID,调用方法

    GetMethodID 得到一个实例的方法的ID

    GetStaticMethodID 得到一个静态方法的ID

  3)获取对象的属性

    GetFieldID 得到一个实例的域的ID

    GetStaticFieldID 得到一个静态的域的ID

    JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。

二、jni中引用的java对象的生命周期

  Java对象做为引用被传递到本地方法中,所有这些Java对象的引用都有一个共同的父类型jobject(相当于java中的 Object类是所有类的父类一样)。 这些对象引用都有其生命周期。在JNI中对Java对象的引用根据生命周期分为:全局引用,局部引用、弱全局引用

  1、Local Reference 本地引用,

    函数调用时传入jobject或者jni函数创建的jobejct,都是本地引用.

    其特点就是一旦JNI层函数返回,jobject就被垃圾回收掉,所以需要注意其生命周期。可以强制调用DeleteLocalRef进行立即回收。

    jstring pathStr = env->NewStringUTF(path)

    ....

    env->DeleteLocalRef(pathStr);

  2、Global Reference 全局引用 ,这种对象如不主动释放,它永远都不会被垃圾回收

    创建: env->NewGlobalRef(obj);

    释放: env->DeleteGlobalRef(obj)

    若要在某个 Native 代码返回后,还希望能继续使用 JVM 提供的参数, 或者是过程中调用 JNI 函数的返回值(比如 g_mid), 则将该对象设为 global       reference,以后只能使用这个 global reference;若不是一个 jobject,则无需这么做。

  3、Weak Global Reference 弱全局引用

    一种特殊的 Global Reference ,在运行过程中可能被垃圾回收掉,所以使用时请务必注意其生命周期及随时可能被垃圾回收掉,比如内存不足时。

    使用前可以利用JNIEnv的 IsSameObject 进行判定它是否被回收

    env->IsSameObject(obj1,obj2);

三、本地线程中调用java对象

  问题1:

    JNIEnv是一个线程相关的变量

    JNIEnv 对于每个 thread 而言是唯一的

    JNIEnv *env指针不可以为多个线程共用

解决办法:

  但是java虚拟机的JavaVM指针是整个jvm公用的,我们可以通过JavaVM来得到当前线程的JNIEnv指针.

  可以使用javaAttachThread保证取得当前线程的Jni环境变量

  static JavaVM *gs_jvm=NULL;
  gs_jvm->AttachCurrentThread((void **)&env, NULL);//附加当前线程到一个Java虚拟机
  jclass cls = env->GetObjectClass(gs_object);
  jfieldID fieldPtr = env->GetFieldID(cls,"value","I");

问题2:

  不能直接保存一个线程中的jobject指针到全局变量中,然后在另外一个线程中使用它。

解决办法:

  用env->NewGlobalRef创建一个全局变量,将传入的obj(局部变量)保存到全局变量中,其他线程可以使用这个全局变量来操纵这个java对象

  注意:若不是一个 jobject,则不需要这么做。如:

  jclass 是由 jobject public 继承而来的子类,所以它当然是一个 jobject,需要创建一个 global reference 以便日后使用。

  而 jmethodID/jfieldID 与 jobject 没有继承关系,它不是一个 jobject,只是个整数,所以不存在被释放与否的问题,可保存后直接使用。

static jobject gs_object=NULL;
JNIEXPORT void JNICALL Java_Test_setEnev(JNIEnv *env, jobject obj)
{
  env->GetJavaVM(&gs_jvm); //保存到全局变量中JVM   //直接赋值obj到全局变量是不行的,应该调用以下函数:
  gs_object=env->NewGlobalRef(obj);
}

参考资料

学习

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

在 JNI 编程中避免内存泄漏的更多相关文章

  1. 在 JNI 编程中避免内存泄漏与崩溃

    JNI 编程简介 JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中 ...

  2. 在 POSIX 线程编程中避免内存泄漏

    检测和避免 POSIX 线程内存泄漏的技巧 POSIX 线程(pthread)编程定义了一套标准的 C 编程语言类型.函数和常量 — 且 pthreads 提供了一种强大的线程管理工具.要充分使用 p ...

  3. 解析Java的JNI编程中的对象引用与内存泄漏问题

    JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中调用 native ...

  4. Java中关于内存泄漏出现的原因以及如何避免内存泄漏

    转账自:http://blog.csdn.net/wtt945482445/article/details/52483944 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静 ...

  5. 关于Hash集合以及Java中的内存泄漏

    <学习笔记>关于Hash集合以及Java中的内存泄漏 标签: 学习笔记内存泄露hash 2015-10-11 21:26 58人阅读 评论(0) 收藏 举报  分类: 学习笔记(5)  版 ...

  6. 系统剖析Android中的内存泄漏

    [转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...

  7. [转载]Java应用程序中的内存泄漏及内存管理

    近期发现测试的项目中有JAVA内存泄露的现象.虽然JAVA有垃圾回收的机制,但是如果不及时释放引用就会发生内存泄露现象.在实际工作中我们使用Jprofiler调用java自带的 jmap来做检测还是很 ...

  8. JavaScript 中的内存泄漏

    JavaScript 中的内存泄漏 JavaScript 是一种垃圾收集式语言,这就是说,内存是根据对象的创建分配给该对象的,并会在没有对该对象的引用时由浏览器收回.JavaScript 的垃圾收集机 ...

  9. Java中的内存泄漏问题

    今天来谈谈Java语言中的内存泄漏问题,可能还有人不知道什么是内存泄漏,先来说下内存泄漏的概念. 内存泄漏:比较正式的说法是,不再使用的对象,却不能被Java垃圾回收机回收.用我的话来说,就是Java ...

随机推荐

  1. jdk RSA算法类使用

    package com.security.rsa; import java.security.Key;import java.security.KeyFactory;import java.secur ...

  2. C# 用正则表达式替换字符串中所有特殊字符

    descriptionXML = Regex.Replace(ToDBC(descriptionXML.ToUpper().Replace((char)32, ' ').Replace((char)1 ...

  3. 黄聪:WordPress 的 Hook 机制与原理(add_action、add_filter)

    稍有接触过 WordPress 主题或插件制作修改的朋友,对 WordPress 的Hook机制应该不陌生,但通常刚接触WordPress Hook 的新手,对其运作原理可能会有点混乱或模糊.本文针对 ...

  4. 关于c语言中qsort函数的一点心得

    今天写c时无意间用到了排序,便想着使用c语言标准库中提供的排序函数,即qsort函数(c++stl中提供了sort函数用于排序),首先是介绍qsort函数的一些基本用法(以下内容转自: http:// ...

  5. Flash AS 响应双击事件MouseEvent.DOUBLE_CLICK

    没想到在WinForm简简单单的一个问题,在AS里会成为一个坑. 我遇到的这个问题是由于Loader没有设置doubleClickEnabled=true而导致的. 因此出现这个问题,请思考是否由于此 ...

  6. cf519D . A and B and Interesting Substrings 数据结构map

    题意: 已知26个小写字母有各自的权值(正,负,或0) 现在给出一个字符串,长度<=1e5 问这个字符串有多少个子串满足: 开头的字母和结尾的字母一样 字符串除了开头和结尾的字母外,其余的字母的 ...

  7. linux命令(8)kill命令

    kill命令: Linux中的kill命令用来终止指定的进程(terminate a process)的运行,是Linux下进程管理的常用命令.通常,终止一个前台进程可以使用Ctrl+C键,但是,对于 ...

  8. Javascript生成GUID

    GUID(全球唯一标识)是微软使用的一个术语,由一个特定的算法,给某一个实体,如Word文档,创建一个唯一的标识,GUID值就是这个唯一的标识码.除了.Net有专门的方法生成外,JS也可以生成GUID ...

  9. 深入研究java.lang.ThreadLocal类(转)

    引用:http://lavasoft.blog.51cto.com/62575/51926/ 一.概述   ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并 ...

  10. embed标签的使用(在网页中播放各种音频视频的插件的使用)

    播放器插件使用说明: 代码:< EMBED src=“music.mid”autostart=“true”loop=“2”width=“80”height=“30”> src:音乐文件的路 ...