创建一个mian()方法做了哪些事情?
- 创建方法
当我们创建了一个main()方法时,JVM为我们做了哪些事情?只是简单的开辟一个mian线程,执行mian()方法中的方法体吗?
用代码验证一下当前的线程情况
import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.Scanner; public class HelloWorld { public static void main(String[] args) {
System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"---------------main方法执行----------------------"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取线程管理MXBean</span> ThreadMXBean threadMXBean <span class="token operator">=</span> ManagementFactory<span class="token punctuation">.</span><span class="token function">getThreadMXBean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 无需获取同步的monitor和synchronizer信息</span> <span class="token comment">// 仅获取线程和线程堆栈信息</span> ThreadInfo<span class="token punctuation">[</span><span class="token punctuation">]</span> threadInfos <span class="token operator">=</span> threadMXBean<span class="token punctuation">.</span><span class="token function">dumpAllThreads</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"-------------开始打印线程信息------------"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>ThreadInfo threadInfo <span class="token operator">:</span> threadInfos<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 获取线程id和线程名</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"["</span> <span class="token operator">+</span> threadInfo<span class="token punctuation">.</span><span class="token function">getThreadId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"]"</span> <span class="token operator">+</span> <span class="token string">"------->"</span> <span class="token operator">+</span> threadInfo<span class="token punctuation">.</span><span class="token function">getThreadName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"main方法执行完毕"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Scanner scanner <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Scanner</span><span class="token punctuation">(</span>System<span class="token punctuation">.</span>in<span class="token punctuation">)</span><span class="token punctuation">;</span> scanner<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
- 运行结果
---------------main方法执行------------------
-------------开始打印线程信息-----------------
[6]------->Monitor Ctrl-Break
[5]------->Attach Listener
[4]------->Signal Dispatcher
[3]------->Finalizer
[2]------->Reference Handler
[1]------->main
main方法执行完毕
-
结果分析
可见,一个简单的main()方法,JVM还为我们创建了除main线程之外的5个线程.(创建的线程数和JDK版本以及IDE有关,这里是JDK1.7,Idea)
Monitor Ctrl-Break
:使用Idea Run程序会Fork出这个线程,Eclipse不会.
Attach Listener
:负责接收到外部的命令,对该命令进行执行并把结果返回给发送者。
Signal Dispatcher
:当Attach Listener把命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。
Finalizer
:这个线程是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法.有4点需要注意:- 只有当开始一轮垃圾收集时,才会开始调用finalize()方法,并不是所有对象的finalize()方法都会被执行.
- 该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有没有执行完finalize()方法,JVM也会退出.
- JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收.
- JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难
Reference Handler
: JVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
守护线程和非守护线程
-
两种线程
java有两种线程,守护线程和非守护线程(用户线程).- 简单来说,守护线程就像一个守护者一样,在程序运行的时候提供一些通用的服务等.并不需要我们去过多关注.e.g:JVM中的垃圾回收线程就是一个守护线程. 当所有的非守护线程结束时,JVM自动退出,并杀死所有守护线程.
- 非守护线程,也就是用户线程.一般是由用户创建.只要有任何用户线程还在运行,JVM就不会退出.
-
查看线程属性
以刚才的main()方法为例,使用JDK自带的工具查看线程属性- jps (Java Virtual Machine Process Status Tool):用于查看JVM中的进程,命令为:
jps [options] [hostid]
,hostid参数为空默认访问本地的虚拟机.options参数也可为空,常用参数有-l,显示全路径名的进程.
- jps (Java Virtual Machine Process Status Tool):用于查看JVM中的进程,命令为:
$ jps
448336 Launcher
398940
453576 Launcher
454576 HelloWorld
452796 Jps
$ jps -l
448336 org.jetbrains.jps.cmdline.Launcher
398940
451576 sun.tools.jps.Jps
453576 org.jetbrains.jps.cmdline.Launcher
454576 com.cxw.HelloWorld
找到HelloWorld对应的pid,再使用jstack工具即可查看对应线程的堆栈信息
- jstack 是JDK中自带的查看指定线程ID的堆栈信息的工具.
// 命令:jstack pid
$ jstack 454576
- 1
- 2
控制台打印信息如下
// 线程名后带daemon的就是守护线程
"Monitor Ctrl-Break" daemon prio=6 tid=0x000000000e4b8800 nid=0x6f198 runnable [0x000000000fade000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:150)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
- locked <0x00000000d602bed0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:154)
at java.io.BufferedReader.readLine(BufferedReader.java:317)
- locked <0x00000000d602bed0> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:382)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
// 守护线程Attach Listener
"Attach Listener" daemon prio=10 tid=0x000000000e440800 nid=0x6d85c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
// 守护线程Signal Dispatcher
"Signal Dispatcher" daemon prio=10 tid=0x000000000e43d000 nid=0x6f208 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
// 守护线程Finalizer
"Finalizer" daemon prio=8 tid=0x000000000e3b9800 nid=0x6d1d4 in Object.wait() [0x000000000f7df000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5eb57f0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
- locked <0x00000000d5eb57f0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)
// 守护线程Reference Handler
"Reference Handler" daemon prio=10 tid=0x000000000e3b0800 nid=0x6a3a0 in Object.wait() [0x000000000f6df000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5eb5370> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:503)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
- locked <0x00000000d5eb5370> (a java.lang.ref.Reference$Lock)
// 用户线程main
"main" prio=6 tid=0x000000000542e000 nid=0x6de30 runnable [0x000000000370f000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:242)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:273)
at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
- locked <0x00000000d5f07f30> (a java.io.BufferedInputStream)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)
- locked <0x00000000d5fedcd8> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.Reader.read(Reader.java:100)
at java.util.Scanner.readInput(Scanner.java:849)
at java.util.Scanner.next(Scanner.java:1414)
at com.cxw.HelloWorld.main(HelloWorld.java:24)
创建守护线程
- 创建一个守护线程,直接上代码.
public class DaemonThreadDemo {
public class MyDaemon implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程第"+i+"次执行");
}
}
}
/**
* 创建守护线程
* @throws IOException
*/
@Test
public void DaemonThreadTest() throws IOException {
Thread thread = new Thread(new MyDaemon());
// setDaemon(true)设置线程为守护线程
thread.setDaemon(true);
thread.start();
// isDaemon()方法查看当前线程是不是守护线程
if (thread.isDaemon()) {
System.out.println("当前执行的是守护线程");
} else {
System.out.println("当前执行的是非守护线程");
}
System.in.read();
}
}
执行结果
当前执行的是守护线程
线程第0次执行
线程第1次执行
线程第2次执行
线程第3次执行
线程第4次执行
在线程执行前,使用setDaemon()方法,将线程设置为守护线程.
- 守护线程创建的线程还是守护线程
public class DaemonThreadDemo {
public class MyDaemon implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程第" + i + "次执行");
}
Thread subThread = new Thread(new Runnable() {
@Override
public void run() {
// 守护线程中fork出的子线程依然是守护线程
System.out.println("子线程执行,当前线程是否守护线程" + Thread.currentThread().isDaemon());
}
});
subThread.start();
}
}
/**
* 创建守护线程
*
* @throws IOException
*/
@Test
public void DaemonThreadTest() throws IOException {
Thread thread = new Thread(new MyDaemon());
// setDaemon(true)设置线程为守护线程
thread.setDaemon(true);
thread.start();
// isDaemon()方法查看当前线程是不是守护线程
if (thread.isDaemon()) {
System.out.println("当前执行的是守护线程");
} else {
System.out.println("当前执行的是非守护线程");
}
System.in.read();
}
}
执行结果
当前执行的是守护线程
线程第0次执行
线程第1次执行
线程第2次执行
线程第3次执行
线程第4次执行
子线程执行,当前线程是否守护线程true
可以看到,在守护线程中fork出的子线程依然是守护线程.
- 创建一个用户线程作对比
public class DaemonThreadDemo {
@Test
public void ThreadDemo() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行,当前线程是否是守护线程"+Thread.currentThread().isDaemon());
}
});
thread.start();
}
}
执行结果
线程执行,当前线程是否是守护线程false
可以明显看出,在main()方法,以及Junit的Test方法中,没有使用setDaemon()方法设置过的线程和main()方法,Test方法保持相同的线程属性,即非守护线程.
文章参考:
JVM 内部运行线程介绍