Poison


  • 首页

  • 归档

  • 标签

  • 搜索
close
Poison

Thread.getId()

发表于 2021-10-18

记录该方法是因为我在开发基于 Java Agent 的监控时,根据物理机的线程 id 来匹配 JVM 线程转储中的线程时,发现 JVM 中的线程 id 并不等于物理机上的线程 id,本文简要记录。

首先与 Thread.getId() 相关的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
* Thread ID
*/
private long tid;

/**
* Returns the identifier of this Thread. The thread ID is a positive
* <tt>long</tt> number generated when this thread was created.
* The thread ID is unique and remains unchanged during its lifetime.
* When a thread is terminated, this thread ID may be reused.
*
* @return this thread's ID.
* @since 1.5
*/
public long getId() {
return tid;
}

/* For generating thread ID */
private static long threadSeqNumber;

private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}

/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}

this.name = name;

Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */

/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}

/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}

/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();

/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}

g.addUnstarted();

this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;

/* Set thread ID */
tid = nextThreadID();
}

根据以上源码,可以看出,Java 内部的线程 id 与底层操作系统的线程 id 无关,Java 中在每次创建线程时使用同步方法 nextThreadID 对静态变量 threadSeqNumber 加一的值作为新生成的线程的 id,对于底层平台的线程 id,我们在线程转储中可以看到,其中 nid=0x7dc8 就是使用十六进制表示的底层平台的线程 id。参见:find out the correspondence between the tid/nid of Java threads as shown from jstack/JMX, on HotSpot/Linux · GitHub。

我们利用以上映射关系,实现了基于 top 命令的高 CPU 使用率 JVM 线程定位。

Poison

Thread.yield()

发表于 2021-10-18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();

在 ConcurrentHashMap 的 initTable 方法中,可以看到对该方法的使用,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}

可以看出,若当前线程判断 table 当前时刻已经在被其他线程进行初始化时,则调用 Thread.yield() 提示调度程序当前线程愿意放弃对当前处理器的使用权,以尝试降低对 CPU 的争用。

阅读全文 »
Poison

Thread.State

发表于 2021-10-18

JVM 中的线程可能处于以下状态:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

JVM 中的线程在任意时刻只能处于一种状态。这些状态仅是 JVM 虚拟机中的线程状态,并不反应操作系统的线程状态。

记录这个问题是因为之前在我们的监控服务中经常探测到持续的 RUNNABLE 线程,经过定位,发现是我们自身的用于监控堆转储文件的线程,其实在操作系统中的状态并不是 R,即并不是运行中或位于运行队列中,我们用一个例子来说明,在我们的线程转储中,经常发现如下的线程数据:

1
2
3
4
5
6
"Thread-5" #16 daemon prio=5 os_prio=0 tid=0x00007f4920006800 nid=0x2d runnable [0x00007f49338e9000]
java.lang.Thread.State: RUNNABLE
at sun.nio.fs.LinuxWatchService.poll(Native Method)
at sun.nio.fs.LinuxWatchService.access$600(LinuxWatchService.java:47)
at sun.nio.fs.LinuxWatchService$Poller.run(LinuxWatchService.java:314)
at java.lang.Thread.run(Thread.java:748)

该方法在 JVM 线程转储中一直处于 RUNNABLE 状态,但是,根据 nid=0x2d 我们转换出对应的 Linux 轻量级进程 id:45 通过如下命令查询轻量级进程状态(其中的 1 为 JVM 进程的 id):

1
cat /proc/1/task/45/status | grep 'State:'

可以得到以下输出:

1
State: S (sleeping)

得到的状态为 S,在 Linux 文档中对应的解释为:interruptible sleep (waiting for an event to complete),这也验证了 Java 文档中的说法,JVM 中的线程状态并不反应操作系统中的线程状态。

同理,最常见的 Netty 执行 Epoll 的线程栈帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"NettyClientWorker-4-7" #424 daemon prio=5 os_prio=0 tid=0x00007f28080ba000 nid=0x1b5 runnable [0x00007f27f2341000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x00000000e11e1d90> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0x00000000e11e1e80> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000000e11e1da8> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:756)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:411)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)

其在 JVM 中的线程状态为 RUNNABLE, 在操作系统级别的状态也为 S (sleeping)。

References

Thread.State (Java Platform SE 8 )
ps(1) - Linux manual page
Processes and Threads (The Java™ Tutorials > Essential Java Classes > Concurrency)

Poison

Classloader Hierarchy for Tomcat

发表于 2021-10-14

关于 Tomcat 的类加载器层次,我之前也只是看过文档中的介绍,并未深入理解,直到一次问题排查,对其有了更深入的理解,本文作简要记录。

在一次处理 java.sql.DriverManager 相关的问题时,看到 DriverManager 文档中有以下描述:

Applications no longer need to explicitly load JDBC drivers using Class.forName(). Existing programs which currently load JDBC drivers using Class.forName() will continue to work without modification.

于是果断把一个数据源工具类中的 Class.forName() 方法调用进行了移除。原实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package me.tianshuang.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DataSourceUtil {

public static Connection getConnection(String url, String driverClassName, String username, String password) {
try {
Class.forName(driverClassName);
return DriverManager.getConnection(url, username, password);
} catch (SQLException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

}
阅读全文 »
Poison

Double Brace Initialization

发表于 2021-10-08
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package me.tianshuang;

import java.util.HashMap;
import java.util.Map;

public class DoubleBraceTest {

private static Map<String, String> map = new HashMap<String, String>() {{
put("key1", "value1");
put("key2", "value2");
put("key3", "value3");
}};

}

以上代码使用了双括号初始化,其中外层那一对括号创建了一个匿名内部类,里层的那一对括号是一个实例初始化块。对于上面这一段源码,进行编译后我们可以在目录看到两个 class 文件,分别是 DoubleBraceTest.class 与 DoubleBraceTest$1.class,其中 DoubleBraceTest$1.class 是我们定义的 HashMap 类的匿名子类,其反编译源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package me.tianshuang;

import java.util.HashMap;

final class DoubleBraceTest$1 extends HashMap<String, String> {
DoubleBraceTest$1() {
this.put("key1", "value1");
this.put("key2", "value2");
this.put("key3", "value3");
}
}
阅读全文 »
1…131415…27

131 日志
119 标签
GitHub LeetCode
© 2025 Poison 蜀ICP备16000644号
由 Hexo 强力驱动
主题 - NexT.Mist