收到好多邀请…泻药泻药。其中有好些感觉是想来吃瓜的。

所以请让我先跑个题,为吃瓜群众献上几个传送门:

private static void newSingleThreadPool() {

ExecutorService executor = Executors.newSingleThreadExecutor();

executor.submit(new Runnable() {

@Override

public void run() {

byte[] bytes = new byte[1024 * 1024 * 4];

System.out.println(Thread.currentThread().getName());

}

});

Reference.reachabilityFence(executor); // HERE }而在Java 8或之前的Java版本里,建议像下面这样显式调用executor.shutdown()其实跟reachabilityFence()的目的是类似的,只是用了更绕弯的方式来达到目的而已:

private static void newSingleThreadPool() {

ExecutorService executor = Executors.newSingleThreadExecutor();

executor.submit(new Runnable() {

@Override

public void run() {

byte[] bytes = new byte[1024 * 1024 * 4];

System.out.println(Thread.currentThread().getName());

}

});

executor.shutdown(); // HERE }

为啥说这两种看起来这么不同的写法实质的目的是一样的呢?且看下文的讲解。

=======================================

参数 / 局部变量的活跃区间

其实这事情很简单:因为一个成员方法里“this”参数的作用域虽然覆盖整个方法,但是其“活跃范围”(liveness)却不一定覆盖整个方法。在方法中的某个位置上,一个不再活跃的参数/局部变量就是就是死变量,GC就不会在乎它的值是多少而照样可以回收它所引用的对象。

先送上Java语言规范的一段:

Chapter 12. Execution

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. For example, a Java compiler or code generator may choose to set a variable or parameter that will no longer be used to null to cause the storage for such an object to be potentially reclaimable sooner.这说的就是一种可能让死变量变得对GC无效(给它们赋值为null只是一种比喻,虽说真要这么实现也可以…),使得tracing GC赖以判断对象生死的“可到达”(reachable)关系变得比源码字面上看要弱——栈帧上的值只有从活的参数 / 局部变量出发才算是根集合的一部分;还在作用域中但已经没有被使用(也就是死了)的参数 / 局部变量则根本没有作用。

请跳俩传送门:

public class Adder {

private int val;

// this a b c public int foo(int a) { // | d | d int b = 42; // | | | | | d int c = a + b; // | | |u/e |u/e | d return this.val + c; // |u/e | | |u/e } // | | | |

// this a b c public int bar(int a) { // |d/e | d int b = 42; // | | | | d int c = a + b; // | |u/e | u | d return b + c; // | | |u/e |u/e } // | | | |

// this newVal public Adder incr() { // | d int newVal = this.val + 1; // | u | d this.val = newVal; // | u |u/e return this; // | u | } // | e |}这里我在注释里标注了参数 / 局部变量的作用域(scope)和活跃区间(live interval)的信息。记法为:每个参数 / 局部变量有两列标注,左边的列所示范围示作用域,右边的列所示范围则为活跃区间。其中活跃区间里用的标记简单说明就是:

d:def,全称definition,变量的“定义”(也就是一个变量获得一次赋值);

u:use,变量的“使用”(也就是变量参与一次运算);

e:end,标记活跃区间的结束;

|:表示本语句没有使用该变量,但该变量还在活跃区间内。d/e表示在得到定义的地方就死掉了,u/e表示这里是最后一次use并且用完就死掉了。上面的incr()方法的表述有点特别,return this; 标记为 u 而不是 u/e,这是为了表示被返回的值不只是return语句的输入,而且要活到return语句之后(要返回给调用方)。

方法的参数都是在方法入口处得到定义的(从调用方传递而来),而普通局部变量则在显式赋值时得到定义。

显然,一个变量的活跃区间总是它的作用域的子集。在它最后一次被使用的位置之后,它就“死掉”了。

“this”作为实例方法的一个隐含参数,它的作用域是覆盖整个实例方法体没错,但它的活跃区间的判断方法与一般的参数 / 局部变量并无差别,同样可以在作用域结束前就死去。

对GC而言,在某个位置上如果“this”参数已经死掉了,“this”就可以不再被看作GC的根集合引用的一部分,于是“this”所引用的对象实例就可以被GC所回收。

作为引用类型的参数,“this”的常见“使用”点有:

实例字段的读写:x = this.val、this.val = x

实例方法的调用(作为隐藏参数传递给被调用方法):this.foo()

方法调用的参数(作为显式参数传递给被调用方法):bar(this)

锁:synchronized (this) { ... }

方法的正常或异常返回:return this、throw this(还有一些像拆箱、String in switch之类的场景也会“使用”引用,不过它们都可以看作上述场景的语法糖,所以就不单独拿出来说)

对号入座,看看自己的某个实例方法里有没有这么一些使用“this”的地方,在最后一个使用点之后,“this”就可以领便当收工了…

如果要被一个带优化的编译器来把某个Java方法编译到机器码的话,还得考虑这个编译器会不会做优化导致上述一些“使用点”被消除。于是优化后的代码中变量的活跃区间会比源码上看起来的作用域要更小。

广大Java程序员普遍觉得实例方法执行的时候其“this”应该一直活着才直观,而如果一个实例方法还在执行的时候其对象的finalize()方法就有可能同时执行是非常不直观的行为。

正因为如此,Java的核心开发团队的大大们也觉得说以后是不是要修改一下上文引用的Java语言规范的描述,让它保证“this”至少在实例方法执行的过程中要一直存活。换句话说,就是让每个实例方法都隐式在方法末尾添加一个对“this”的使用。未来的Java版本会不会真的修改这部分规定,现在还是未知数。但在添加了Reference.reachabilityFence()方法后,关于this活跃区间的规定可能短期内是不会写进规范里了。

上面引用的mechanical-sympathy讨论串里,有位大大给出了这样的一个例子:

ByteBuffer.allocateDirect(4096).put(ByteBuffer.allocate(4096));这个例子也可以触发由于“this”的寿命可以比一个实例方法调用要短而造成的问题。大家有兴趣的话请自己分析一下看看?

(注意:Oracle JDK / OpenJDK的标准库实现中,每个DirectByteBuffer有一个关联的sun.misc.Cleaner对象(基于PhantomReference)来负责清理该buffer背后的native memory。这个Cleaner的作用跟finalizer很相似。)

不过,讲道理,只要一个“this”没有被某种弱引用盯着(finalizer、sun.misc.Cleaner都是基于弱引用的),其实“this”的寿命比实例方法短对于Java应用来说是完全看不出来区别的。

所以大家也不必太担心,该怎么写代码还怎么写,只要留心一下有基于弱引用的清理动作的地方要自己确保持强引用即可——不过当然咯,用别人写的库的时候也很可能不知道人家在底下有没有做基于弱引用的清理动作…

=======================================

可被finalize的对象

带有非空的finalize()方法的类会被JVM特殊处理:当GC发现这样的类的一个实例已经不再被任何活的强引用所引用时,就会把它放入finalizer queue,排队调用其finalize()方法。而当这样的一个实例的fianlize()方法被调用(并且该实例没有被复活)之后,下一次GC就可以把它看作普通对象来清理掉了。

所以一个带finalize()方法的类的实例,从已经失去所有强引用到真正被GC回收,通常要经历两次GC。

这是常识了,只是放在这里帮助同学们回忆一下有这么一回事。

=======================================

再看一组例子来帮助同学们热身。这跟题主最初举例问的问题不完全一致,但是简单一些好理解其性质。然后再去分析完整的例子就比较容易。

本例的关注点在于:从Test.foo()出发,为何能调用A.val()在里面能遇到NullPointerException。

public class Test {

public static int foo() {

return new A().val();

}

public static void main(String[] args) {

for (int i = 0; i < 2000; i++) {

try {

foo();

} catch (NullPointerException npe) {

System.out.println("NPE happened at " + i + "th iteration");

npe.printStackTrace(System.out);

return;

}

}

}

}

class A {

private B b = new B();

public void finalize() {

this.b.clearC();

}

public int val() {

return this.b.val();

}

}

class B {

private C c = new C();

void clearC() {

this.c = null;

}

public int val() {

System.gc(); // the A instance is queued into finalizer queue System.runFinalization(); // A's finalize() is ensured to run return this.c.val(); // now this.c is null }

}

class C {

public int val() {

return 42;

}

}我在我的Mac OS X上跑Oracle JDK8u101,得到的结果是:

$ java Test

NPE happened at 257th iteration

java.lang.NullPointerException

at B.val(Test.java:41)

at A.val(Test.java:27)

at Test.foo(Test.java:3)

at Test.main(Test.java:9)“按道理说”A.val()是个实例方法,它在执行的时候它的“this”肯定是活着的对不对?

——上面我们已经讨论过了,不对。“this”没用了就可以领便当了。此例正是如此。

这个例子有若干种方式可以触发到那个NPE,例如说既可以以Test.foo()为根来JIT编译,也可以以A.val()为根,都可以达到同样的效果。

首先,Test.foo()并没有持续持有A类实例的引用,在new A()之后就直接调用它上面的实例方法,把刚创建出来的A类实例的引用当作隐式“this”参数传递给A.val()了。所以在GC发生的时候,Test.foo()已甩锅——那个A类实例是活是死不关我事。

然后我们看看A.val()是怎么回事。

为了简化讨论,先来看完全不开内联时的情况。给启动参数加上 -XX:CompileCommand=dontinline,*,* 可以禁用所有方法的内联(除了intrinsic以外),然后加上 -XX:-BackgroundCompilation 会禁用HotSpot VM默认开启的后台编译功能——这样触发JIT编译后总是会等到编译结束再继续跑,而不是边继续在解释器里跑程序边等编译。禁用后台编译可以让实验输出结果更加稳定。

$ java -XX:CompileCommand=dontinline,*,* -XX:+PrintCompilation -XX:-BackgroundCompilation Test

...

1230 76 b 3 A::val (8 bytes)

1230 77 b 3 B::val (14 bytes)

1230 78 b 3 java.lang.System::gc (7 bytes)

1231 79 n 0 java.lang.Runtime::gc (native)

1234 80 b 3 java.lang.System::runFinalization (7 bytes)

1234 81 b 3 java.lang.Runtime::runFinalization (4 bytes)

1235 82 n 0 java.lang.Runtime::runFinalization0 (native) (static)

...

1242 97 b 3 A::finalize (8 bytes)

1242 98 b 3 B::clearC (6 bytes)

NPE happened at 256th iteration

java.lang.NullPointerException

at B.val(Test.java:41)

at A.val(Test.java:27)

at Test.foo(Test.java:3)

at Test.main(Test.java:9)可以发现这个实验总是到A.val()被JIT编译后就抛NPE。

还可以更激进地配置参数,只允许A.val()被JIT编译(也不允许A.val()内联任何其它方法),也可以达到同样的效果:

$ java -XX:CompileCommand=compileonly,A,val -XX:+PrintCompilation -XX:-BackgroundCompilation Test

CompilerOracle: compileonly A.val

1038 1 b 3 A::val (8 bytes)

NPE happened at 256th iteration

java.lang.NullPointerException

at B.val(Test.java:41)

at A.val(Test.java:27)

at Test.foo(Test.java:3)

at Test.main(Test.java:9)

显然,A.val()在解释器里跑的时候,这个实验就不发生NPE。

这是因为在HotSpot VM的解释器里跑的时候,“this”参数总是在局部变量表里,而解释器不会对代码做活跃分析(liveness analysis),这个“this”参数会在A.val()解释执行过程中一直被当作是活的。而HotSpot VM的GC在扫描A.val()的解释器栈帧时,借助GenerateOopMap对代码做的分析又不做完整的活跃分析,导致GC把这个“this”看作一个根集合里的活的强引用。

public int val();

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: getfield #4 // Field b:LB;

4: invokevirtual #6 // Method B.val:()I

7: ireturn

LineNumberTable:

line 27: 0

LocalVariableTable:

Start Length Slot Name Signature

0 8 0 this LA;附上 -XX:+TraceNewOopMapGenerationDetailed 的输出,可见“this”(在slot 0)确实在A.val()中从头到为都被解释器对应的GenerateOopMap认为是活的。

Iteration #0 of do_interpretation loop, method:

virtual jint A.val()

0 vars = ( r |slot0) aload_0

stack =

monitors =

1 vars = ( r |slot0) getfield

stack = ( r |slot0)

monitors =

4 vars = ( r |slot0) invokevirtual()I

stack = ( r |line1)

monitors =

7 vars = ( r |slot0) ireturn

stack = ( v |Top)

monitors =

Report result pass for basicblock

0 vars = ( r |slot0) aload_0

stack =

monitors =

1 vars = ( r |slot0) getfield

stack = ( r |slot0)

monitors =

4 vars = ( r |slot0) invokevirtual()I

stack = ( r |line1)

monitors =

7 vars = ( r |slot0) ireturn

stack = ( v |Top)

monitors =

但当 A.val() 被C1编译的时候,C1就会做正常的活跃分析,马上就知道在调用 B.val() 的时候,“this”参数其实已经死掉了,也就是说它不必再被看作是根集合中活着的强引用看待。

让我们看看C1在编译A.val()过程中,优化后的HIR是什么样子的:

__bci__use__tid____instr____________________________________

. 1 2 a2 a1._12 (L) b

. 4 0 a3 null_check(a2)

. 4 1 i4 a2.invokespecial()

B.val()I

. 7 0 i5 ireturn i4用人话说,C1眼中的 A.val() 是这样的:(Java语法伪代码)

public int val() {

B a2 = this.b; // last use of 'this' // 'this' already considered to be a dead variable by now

if (a2 == null) throw new NullPointerException();

int i4 = a2.val(); // GC will happen here return i4;

}

于是在 A.val() 被C1编译后,等到 B.val() 被调用时,GC要扫描这个栈了,问大家哪些引用是活的,栈上的方法们说,在其栈帧中的参数 / 局部变量中:

^ System.gc() - 进VM执行GC

| B.val() - this: B 是活的

| A.val() - 没有任何引用是活的(this: A 已死)

| Test.foo() - 没有任何引用是活的,已甩锅

| Test.main() - 没有任何引用是活的那GC就很高兴的发现A类实例已经没有被任何活引用指向了,就把它放进了finalizer queue。然后 B.val() 再调用个 System.runFinalization() 迫使 A.finalize() 被执行,然后…NPE就来了。

很简单对不对?

--------------------------------

然后再看看默认开内联的时候是什么状况。给启动参数加上-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining,可以看到这个方法会被HotSpot VM的C1编译,其内联树为:

740 72 b 1 A::val (8 bytes)

@ 4 B::val (14 bytes)

@ 0 java.lang.System::gc (7 bytes)

@ 0 java.lang.Runtime::getRuntime (4 bytes)

- @ 3 java.lang.Runtime::gc (0 bytes) native method

@ 3 java.lang.System::runFinalization (7 bytes)

@ 0 java.lang.Runtime::getRuntime (4 bytes)

@ 3 java.lang.Runtime::runFinalization (4 bytes)

- @ 0 java.lang.Runtime::runFinalization0 (0 bytes) native method

@ 10 C::val (3 bytes)可以看到,除了 Runtime.gc() 与 Runtime.runFinalization0() 两个native方法外,从A.val()出发调用的所有方法都被内联了。

经过内联后,A.val() 被C1编译出来的HIR长这样:

__bci__use__tid____instr____________________________________

. 1 2 a2 a1._12 (L) b

. 4 0 a3 null_check(a2)

0 2 a10

. 0 2 a11 a10._96 (L) currentRuntime

. 3 0 a13 null_check(a11)

. 3 0 v14 a11.invokespecial()

java/lang/Runtime.gc()V

. 0 1 a21 a10._96 (L) currentRuntime

. 3 0 a23 null_check(a21)

. 0 0 v26 invokestatic()

java/lang/Runtime.runFinalization0()V

. 7 1 a29 a2._12 (L) c

. 10 0 a30 null_check(a29)

0 1 i33 42

. 7 0 i36 ireturn i33用人话说,这C1 HIR的意思是:

public int val() {

B a2 = this.b; // last use of 'this' // 'this' already considered to be a dead variable by now

if (a2 == null) throw new NullPointerException();

Class a10 = Runtime.class;

Runtime a11 = a10.currentRuntime;

if (a11 == null) throw new NullPointerException();

a11.gc(); // native method, not inlined // GC will happen here

Runtime a21 = a10.currentRuntime;

if (a21 == null) throw new NullPointerException();

a21.runFinalization0(); // native method, not inlined // ensure A's finalize() is run

// Now a2.c is null because A's finalize() has run

C a29 = a2.c;

if (a29 == null) throw new NullPointerException();

int i33 = 42;

return i33;

}同学们试试用前面说的简易分析方法,看看这里 A.val() 里的“this”是不是在调用GC之前就已经死掉了?具体分析过程就留作课后习题吧。

=======================================

题主的例子的分析

有了上面的基础,相信大家也不难自己去分析一下了。

关键点在于:

题主写的 SingleThreadPoolTest.newSingleThreadPool() 并没有有效地持有 Executors.newSingleThreadExecutor() 返回来的 ExecutorService 对象的引用,而是一甩锅,直接调用了 submit() 方法。这就跟上面的 Test.foo() 类似。

Executors.newSingleThreadExecutor() 返回来的是一个带 finalize() 方法的对象,具体来说是一个 Executors$FinalizableDelegatedExecutorService 类的实例。它内部包装着真正负责实现功能的 ThreadPoolExecutor 类的实例。这俩的关系就跟 A 与 B 类的关系类似。

题主调用的 submit() 方法是外层包装的 Executors$FinalizableDelegatedExecutorService 上的版本,具体实现是 Executors$DelegatedExecutorService.submit() 。它就跟上面的 A.val() 与 B.val() 的关系非常相似,在解释器里能保持“this”的存活,但一旦被JIT编译就不能保持“this”的存活。

于是在 Executors$DelegatedExecutorService.submit() 被JIT编译后,即便在它还在执行的时候,GC也可以把外层包装的 Executors$FinalizableDelegatedExecutorService 放进finalizer queue里,进而调用其finalize()方法。这么一来,内层被包装的 ThreadPoolExecutor 就被 shutdown() 了,于是就会触发题主看到的那个 RejectedExecutionException 异常。

就这样而已。本来还想就这个例子详细展开写写分析过程的,但感觉也没有必要了。这里就发一点分析内容吧,C1 编译 SingleThreadPoolTest.newSingleThreadPool() 优化后的内联树,以及HIR的样子:

内联树:

1092 176 b 1 SingleThreadPoolTest::newSingleThreadPool (17 bytes)

@ 0 java.util.concurrent.Executors::newSingleThreadExecutor (28 bytes) @ 18 java.util.concurrent.LinkedBlockingQueue:: (7 bytes)

@ 3 java.util.concurrent.LinkedBlockingQueue:: (94 bytes) callee is too large

@ 21 java.util.concurrent.ThreadPoolExecutor:: (18 bytes) @ 8 java.util.concurrent.Executors::defaultThreadFactory (8 bytes)

@ 4 java.util.concurrent.Executors$DefaultThreadFactory:: (75 bytes) callee is too large @ 14 java.util.concurrent.ThreadPoolExecutor:: (143 bytes) callee is too large @ 24 java.util.concurrent.Executors$FinalizableDelegatedExecutorService:: (6 bytes) @ 2 java.util.concurrent.Executors$DelegatedExecutorService::

(10 bytes)

@ 1 java.util.concurrent.AbstractExecutorService:: (5

bytes)

@ 1 java.lang.Object:: (1 bytes)

@ 7 SingleThreadPoolTest$1:: (5 bytes)

@ 1 java.lang.Object:: (1 bytes)

@ 10 java.util.concurrent.Executors$DelegatedExecutorService::submit (11 bytes)

@ 5 java.util.concurrent.AbstractExecutorService::submit (26 bytes)

@ 15 java.util.concurrent.AbstractExecutorService::newTaskFor (10 bytes)

@ 6 java.util.concurrent.FutureTask:: (19 bytes)

@ 1 java.lang.Object:: (1 bytes)

- @ 7 java.util.concurrent.Executors::callable (22 bytes) callee is too large

- @ 21 java.util.concurrent.ThreadPoolExecutor::execute (132 bytes) callee is too large可见它没有内联 ThreadPoolExecutor.execute() 的调用。

HIR:

_p__bci__use__tid__result____instruction________________________________________ (HIR)

. 0 2 a3 [R177|L] new instance java/util/concurrent/Executors$FinalizableDelegatedExecutorService

. 4 3 a4 [R178|L] new instance java/util/concurrent/ThreadPoolExecutor

8 2 i5 1

10 1 l7 0L

11 1 a9

. 14 2 a10 [R179|L] new instance java/util/concurrent/LinkedBlockingQueue

1 1 i13 2147483647

. 3 0 v14 a10.invokespecial(i13)

java/util/concurrent/LinkedBlockingQueue.(I)V

. 0 2 a20 [R180|L] new instance java/util/concurrent/Executors$DefaultThreadFactory

. 4 0 v21 a20.invokespecial()

java/util/concurrent/Executors$DefaultThreadFactory.()V

11 1 a24

. 14 0 v25 a4.invokespecial(i5, i5, l7, a9, a10, a20, a24)

java/util/concurrent/ThreadPoolExecutor.(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/ThreadFactory;Ljava/util/concurrent/RejectedExecutionHandler;)V

. 0 0 v35 Object.Object_init(a3)

. 6 0 a38 a3._12 := a4 (L) e

. 9 0 v39 membar_storestore

. 3 1 a43 [R183|L] new instance SingleThreadPoolTest$1

1 1 a57 null

. 0 3 a63 [R184|L] new instance java/util/concurrent/FutureTask

. 7 1 a69 [R185|L] invokestatic(a43, a57)

java/util/concurrent/Executors.callable(Ljava/lang/Runnable;Ljava/lang/Object;)Ljava/util/concurrent/Callable;

. 10 0 a70 a63._16 := a69 (L) callable

14 1 i71 0

. 15 0 i72 a63._12 := i71 (I) state

. 21 0 v75 a4.invokespecial(a63)

java/util/concurrent/ThreadPoolExecutor.execute(Ljava/lang/Runnable;)V

. 16 0 v78 return其实也很简单了,有没有?

在最后那个没有被内联的 ThreadPoolExecutor.execute() 的调用点上,这个编译单元只认为一个引用还是活着的,那就是包装传入的Runnable参数的FutureTask实例的引用。所以GC在这里发生的话,外层的 Executors$FinalizableDelegatedExecutorService 就要被finalize了,进而引发题主所关心的那个异常。

就先写这么多。以上~

java gc 例子_Java 中, 为什么一个对象的实例方法在执行完成之前其对象可以被 GC 回收?...相关推荐

  1. java 泛型例子_java中的泛型的一些常见例子

    /** * @author Rollen-Holt 使用泛型 */ class hello { hello(){ } public T getName(){ return name; } public ...

  2. java.equal例子_Java中的== 和equals()方法详解与实例

    Java中的== 和equals()方法: Java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型. byte,short,char,int,long,float,double,boo ...

  3. java 循环new对象_java中new一个对象放在循环体里面与外面的区别

    首先说下问题: 这次在做项目的是出现了一个new对象在循环里面与外面造成的不同影响. 大家可以看到这个new的对象放在不同的位置产生的效果是不一样的. 经过多方查询与验证可以得出结论: * EasyU ...

  4. java 释放一个对象_JAVA中销毁一个对象的方法

    方法一:垃圾回收器 垃圾回收器是Java平台中用的最频繁的一种对象销毁方法.垃圾回收器会全程侦测Java应用程序的运行情况.当反先有些对象成为垃圾时,垃圾回收器就会销毁这些对象,并释放这些对象所占用的 ...

  5. java集合总结_Java中集合总结

    Java数组的长度是固定的,为了使程序能够方便地存储和操作数目不固定的一组数据,JDK类库提供了Java集合,这些集合类都位于java.util包中,但是与数组不同的是,集合中不能存放基本类型数据,而 ...

  6. java 初始化顺序_Java中对象初始化顺序的详细介绍

    前言 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.最近我发现了一个有趣的问题,这个问题的答案乍一看下骗过了我的眼睛.看一下这三个类: package com ...

  7. java synchronized 使用_Java中Synchronized的用法

    synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码 ...

  8. java static用法_Java中static关键字的作用和用法详细介绍

    static表示"全局"或者"静态"的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念. 被static ...

  9. java 遍历对象_java中some(),every()循环遍历,Object.getOwnPropertyNames()遍历对象的属性...

    由于java知识的应用具有一定的广泛性,所以它经常会应用到我们的日常使用当中.那么今天就为大家介绍一下java中some(),every()循环遍历,Object.getOwnPropertyName ...

最新文章

  1. jQuery日期和时间插件(jquery-ui-timepicker-addon.js)中文破解版使用
  2. 【硅谷牛仔】Pinterest CEO--希伯尔曼--成功关键在推广而非技术
  3. leetcode算法题--Wiggle Subsequence
  4. Java删除文件夹和文件
  5. python项目ImportError: Plotly express requires pandas to be installed.解决方案
  6. bigint在java中用什么表示_为什么说开车最能看出一个人的人品和情商?这几条说的太精辟了...
  7. 从网络获取数据显示到TableViewCell容易犯的错
  8. Python+Selenium练习篇之2-利用ID定位元素
  9. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 14丨重新格式化部门表【难度中等】
  10. 深度deepin安装apache tomcat
  11. mysql function_MySQL基础函数——数学函数详解
  12. javascript 控制语句
  13. matlab设置ga算法,matlab遗传算法ga函数
  14. httpsecurity 类方法介绍_java知识学习25-内部类 - 那种意境
  15. wps下一页 很好玩!wps中怎么插入欧姆符号
  16. Swagger使用方法
  17. 最近收集的一些酷炫的UI界面设计
  18. STL源码剖析学习之increment、decrement、dereference实现源码
  19. dll文件怎么编辑(dll文件怎么使用)
  20. Python高效实现滑块验证码自动操纵

热门文章

  1. react router 4
  2. aix shell脚本 运行java_IBM AIX shell脚本启动java程序不成功
  3. 页面前端的水有多深?再议页面开发
  4. P1041 传染病控制
  5. 每日一“酷”之string
  6. 三刺激值计算公式_超实用的数控车床常用计算公式,绝对帅,赶紧收藏了!
  7. gridview自动编号
  8. 安装Windows Server 2008 测试机
  9. 基于html5 Canvas图表库 : ECharts
  10. TIA Portal