《深入理解java 虚拟机*JVM 高级特性与最佳实践读后感》

本文引用代码见下载页:http://www.hzbook.com 

《第一章:java历史》

java 是什么:

1.java是面向对象并实现了"一次编写,到处运行"的跨平台一门语言;
2.它提供了一个相对安全的内存管理和访问机制,避免了绝大部分的内存泄露和指针越界问题;
3.它实现了热点代码检测和运行时编译及优化,使得java应用能随着运行时间的增加而获得更高的性能;
4.java拥有一套完善的应用程序接口;

java 技术体系包括
1.java 程序设计语言
2.各种硬件平台上的java虚拟机
3.class 文件格式
4.java api 类库
5.来自商业机构和开源社区的第三方java类库

JDK 和JRE的区别和联系

java程序设计语言,java 虚拟机,java api 类库这三部分统称为JDK(java development kit)  是用于支持java 程序开发的最小环境。java api 和java 虚拟机两部分统称为JRE(java runtime environment),jre 是支持java程序运行的标准环境。

java技术体系可以分为四个平台:


1.java card :支持一些java小程序applets运行在小内存(如智能卡)设备上的平台。

2.java ME[J2ME]: 支持java程序运行在移动终端(手机,PDA)上的平台,对java api 有所精简,并加入了针对移动终端的支持。

3.javaSE[J2SE]:支持面向桌面级应用(Windows下的应用程序)的java平台,提供了完整的java 核心API。

4.java EE[J2EE]: 支持使用多层架构的企业应用(ERP,CRM)的java平台,除了提供javaSE api 外,还对其做了大量的扩充,并提供了相关的部署支持。

《第二部分:自动内存管理机制》

java 程序员在虚拟机自动内存管理机制的帮助下,不需要为每个new操作去写配对的delete/free 代码,不容易出现内存泄露和内存溢出问题。

运行时数据区域:java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁时间。有的区域随着虚拟机进行的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。

1.程序计数器: 程序计数器是一块较小的内存空间,可以看作是点钱线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。

由于java虚拟机的多线程是通过线程轮流并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。所以,为了线程切换后能够恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我我们称这类内存区域为:线程私有的内存

如果线程正在执行的是一个java方法,这个计数器记录的是正在的虚拟机字节码指令的地址; 如果执行的是native 本地方法,这个计数器值则为空undefined。此内存区域是唯一一个在java虚拟机规范中没有规定任何outOfMemoryError 情况的区域。

2.java 虚拟机栈java虚拟机栈线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口灯。每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表存放了编译器可知的各种基本数据类型:Boolean,byte,char,short,int,float,long,double ,对象引用等。

long 和double 占用2个局部变量空间,其余的都是占用1个。

在java虚拟机规范中,对这个区域规定了两种异常状况

1.如果线程的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError 异常;

2.如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError 异常。

3.本地方法栈本地方法栈与虚拟机栈作用相似,区别不过是虚拟机栈为虚拟机执行java方法[字节码]服务,而本地方法栈则为虚拟机使用到的native方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverFlowError 和 OutOfMemoryError 异常。

4.java堆 heap java堆是java 虚拟机管理内存中最大的一块java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象示例,几乎所有的对象示例都在这里分配内存。[所有的对象实例以及数组都要在堆上分配]。

  java 堆是垃圾收集器管理的主要区域,也称GC 垃圾收集器。由于现在收集器基本都采用分代收集算法,所以java堆中可以细分为:新生代和老年代;

新生代和老年代: Eden 空间,from survivor 空间,to survivor 空间。

根据java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

5.方法区 method area:与java heap 堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据 。java虚拟机对方法区规定:可以不需要连续的内存和可以选择固定大小或者可扩展外,还可以不实现垃圾收集。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。

6.运行时常量池 runtime constant pool: 方法区的一部分。class 文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池 constant pool table,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。 运行时常量池相对于class文件常量池的重要特性是具备动态性。因为运行时常量池是方法区的一部分,所以自然受到方法区内存的限制,当常量池无法再申请到内存时就会抛出OutOfMemoryError 异常

7.直接内存 Direct Memory:从java1.4开始引入了NIO类,引入了一种基于通道与缓冲区Buffer 的I/O 方式它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著的提高性能,因为避免了java堆和native 堆中来回复制数据。

 
 对象的访问定位:建立对象是为了使用对象,我们的java程序需要通过栈上的reference 数据来操作堆上的具体对象。由于reference 类型在java 虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,访问对重的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有:使用句柄和直接指针两种
 
 如果使用句柄访问的话,那么java堆中将会划分出一块内存作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
 
 如果使用直接指针访问,那么java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象地址。
 
 总结: 两种对象访问方式的优势:
 
 1.使用句柄来访问的最大好处就是reference 中存储的是稳定的句柄地址,在对象呗移动时只会改变句柄中的实例数据指针,而reference 本身不需要修改。
 
 2.使用直接指针访问方式的最大好处就是速度更快,他节省了一次指针定位的时间开销,由于对象的访问在java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。

实战:OutOfMemoryError 异常:

在JVM 中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError [OOM]异常的可能。

main 方法,右键--debug configurations

java 堆溢出:java堆用于存储对象实例,只要不断的创建对象,并且保证GC roots 到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

public class HeapOOM {

    static class OOMObject{}

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while(true){
            list.add(new OOMObject());
        }
    }
}

异常堆栈信息:java.lang.OutOfMemoryError Java heap space 解决方法: 先通过内存映像工具[Eclipse Memory-Analyzer]对Dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是内存泄露memory leak 还是 内存溢出 memory overflow。[eclipse 查看:https://blog.csdn.net/qq_35781178/article/details/102902041]

虚拟机栈和本地方法栈溢出:

由于在HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈,所以,-Xoss参数(设置本地方法栈大小)并不起作用,栈容量只能由-Xss 参数设定。关于虚拟机栈和本地方法栈只有两种异常:
1.如果线程请求的栈深度大于虚拟机所允许的最大深度,则抛出StackOverflowError 异常。

2.如果虚拟机在扩展栈时无法申请到足够的内存空间则抛出OutOfMemoryError 异常。


方法区和运行时常量池溢出

string.intern()方法是一个native 方法,作用:如果字符串常量池中已经包含了一个等于此string对象的字符串,则返回代表池中这个字符串的string对象;否则将此string 对象包含的字符串添加到常量池中,并且返回此string 对象的引用。

本地直接内存溢出:DirectMemory 容量通过-XX:MaxDirectMemorySize 指定,如果不指定,则默认与java 堆最大值一样。

《第三章:垃圾收集器与内存分配策略》

垃圾收集器工作要考虑的事情:
1.哪些内存需要回收
2.什么时候回收
3.如何回收


1.引用计数算法: 基本思想:判断对象存活的条件是给对象添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器就减1;任何时候计数器为0 的对象就是不可能再被使用的。引用计数算法的实现简单,判定效率很高。主流虚拟机不是采用引用计数法,因为它没有解决对象之间相互循环引用的问题。

2.可达性分析算法: 在主流的商用程序语言[java,c#]的实现中,都是通过可达性分析来判定对象是否存活的。基本思想:通过一系列的称为GC roots 的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC roots没有任何引用链相连时,则证明此对象是不可用的。

在java 语言中,作为GC roots 的对象包括下面几种:
1.虚拟机栈中引用的对象。
2.方法区中类静态属性引用的对象。
3.方法区中常量引用的对象。
4.本地方法栈中Native 引用的对象。

总结:引用计数算法 和可达性分析算法都是通过对象引用来判断对象是否存活的。

对象引用: 强引用,软引用,弱引用,虚引用。

1.强引用:就是指在程序代码之中普遍存在的,类似Object obj = new Object() 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

2.软引用:用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

3.弱引用:也是用来描述非必须对象的,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

4.虚引用:幻影引用,最弱的一种引用关系。一个对象是否有徐引用的存在,完全不会对其他生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象呗收集器回收时收到一个系统通知。

回收方法区:方法区[Hotspot永久代]可以不要求JVM在方法区实现垃圾收集,而且在方法区中进行垃圾收集的性价比比较低。

判定一个类是否是无用的类的条件
1.该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。

2.加载该类的classLoader已经被回收。

3.该类对应的java.lang.class 对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。

总结:在大量使用反射,动态代理,CGLib 等ByteCode框架,动态生成JSP以及OSGi 这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

垃圾收集算法:
1.标记-清除算法 mark-Sweep:标记-清除算法分为两个阶段,首先标记处搜优需要回收的对象,在标记完成后统一回收所有被标记的对象。

缺点:效率问题,标记和清除两个过程效率都不高;另一个是空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。

2.复制算法copying:复制算法是为了解决效率问题而出现的,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要一动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

注解:现在的商业虚拟机都采用这种手机算法去 回收新生代。JVM将内存分为一块较大的Eden 空间和两个较小的Survivor空间,每次使用Eden 和其中的一块Survivor 。当回收时,将Eden 和Surivor中还存活着的对象一次性的复制到另外一块Survivor 空间上,最后清理掉Eden 和刚才使用过的Surivor空间。HotSpot虚拟机默认Eden 和Surivor 的大小比例是:8:1。

3.标记-整理算法:复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。[建议:标记-清除法]

4.分代收集算法Generational Collection:根据对象存活的周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,根据各个年代的特点采用适合的手机算法:

      1.新生代,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

      2.老年代,因为对象存活率高,没有额外空间对它进行分配担保,必须使用"标记-清除法" 或者"标记整理算法" 进行回收。

垃圾收集器
1.Serial 收集器Serial 收集器是最基本,发展历史最悠久的收集器,曾经是虚拟机新生代收集的唯一选择。它是一个单线程的垃圾收集器,它的特点是:不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

2. ParNew 收集器:Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其他行为包括Serial 收集器可用的所有控制参数[-XX:Survivorratio,-XX:PretenureSizeThreshold,-XX:HandlePromotionFailure等],收集算法,Stop The World,对象分配规则,回收策略等都与Serial收集器完全一样。 

注解:ParNew 收集器是许多运行在Server模式下的JVM 首选的新生代收集器,因为目前只有它能与CMS收集器配合工作。

3.Parallel Scavenge 收集器:Parallel Scavenge 收集器是一个新生代且使用复制算法的多线程收集器。Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量。[吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 / (运行用户代码时间+垃圾收集时间),JVM总运行了100分钟,其中垃圾收集花掉1分钟,那么吞吐量就是99%。]

4.Serial Old 收集器:Serial Old 是Serial 收集器的老年代版本,它的特点是单线程,使用"标记-整理算法"。它的出现在于给client 模式下的虚拟机使用。

5.Parallel Old 收集器:Parallel Old 是Parallel Scavenge收集器
的老年代版本,使用多线程和"标记-整理算法"。

6.CMS收集器 Concurrent Mark Sweep:CMS是一种以获取最短回收停顿时间为目标的收集器。主要体现在互联网或B/S系统的服务端,采用"标记-清除算法"。

CMS 垃圾收集器优点:并发收集,低停顿。
缺点:
1.CMS 收集器对CPU 资源非常敏感。由于并发收集,所以虽然不会导致用户线程停顿,但因为占用了一部分线程而导致应用程序变慢,总吞吐量降低。

2.CMS 收集器无法处理浮动垃圾,可能失败而导致另一次full gc d的产生。[浮动垃圾:由于CMS 并发清理阶段用户线程还在运行着,伴随着程序运行自然就还会有新的垃圾不断产生,这一部分出现在标记过程之后哦,CMS无法再当次收集中处理掉它们,只好留到下一次GC 时再处理掉,这就是浮动垃圾。]

3.还有"标记-清除算法"的会产生大量的空间碎片等特点。

7.G1收集器 Garbage-First: 当今收集器技术发展的最前沿成果之一。G1是一款面向服务端应用的垃圾收集器,特点有:

1.并行与并发:G1 能充分利用多CPU,多核环境下的硬件优势,使用多个CPU 来缩短stop the world 停顿的时间,部分其他收集器原本需要停顿java线程执行的GC 动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

2.分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的旧对象以获取更好的收集效果。

3.空间整合:"标记-整理算法",不会产生空间碎片。

4.可预留的停顿:降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N秒。

G1 动作:
1.初始标记
2.并发标记
3.最终标记
4.筛选回收

对象优先在Eden 分配:一般情况下,对象在新生代Eden区中分配。当Eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

新生代GC [Minor GC]: 发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性,所以Minor GC 非常频繁,一般回收速度也比较快。

老年代GC[Major GC | Full GC]:发生在老年代的GC ,出现了Major GC ,经常会伴随至少一次Major GC。Major GC的速度一般会比Minor GC 慢10倍以上。

大对象直接进入老年代:大对象指的是需要大量连续内存空间的java对象,最典型的是那种很长的字符串以及数组。

长期存活的对象将进入老年代: 一个对象熬过了一次又一次的垃圾回收,超过15次会被晋升到老年代中。

JDK可视化工具:C:\Program Files\Java\jdk1.8.0_73\bin\jconsole.exe
                             C:\Program Files\Java\jdk1.8.0_73\bin\jvisualvm.exe

JConcole: java 监视与管理控制台。它是一种基于JMX 的可视化监视管理工具。它管理部分的功能是针对JMX MBean 进行管理,由于MBean 可以使用代码,中间件服务器的管理控制台或者所有符合JMX规范的软件进行访问,里面包括:概览,内存,线程,类,VM概要,MBean 等信息。

 

 

 

内存监控:内存相当于可视化的jstat 命令,用于监视受收集器管理的虚拟机内存的变化趋势。

线程监控:相当于可视化的jstack 命令,遇到线程停顿时可以使用这个页面进行监控分析。
[停顿的主要原因有:等待外部资源(数据库连接,网络资源,设备资源等),死循环,锁等待(活锁|死锁)]

VisualVM:多合一故障处理工具:性能分析,VisualVM 的性能分析功能甚至比JProfiler,YourKit 等专业收费的profiling 工具都不会逊色,而且它的有点很明显:不需要被监视的程序基于特殊Agent 运行,因此它对应用程序的实际性能的影响很小,使得它可以直接应用在生产环境中。

VisualVM 基于NetBeans 平台开发,因此它具备插件扩展功能的特性,通过插件扩展支持,VisualVM可以做到:

1.显示虚拟机进程以及进程的配置,环境信息(jps,jinfo)

2.监视应用程序的CPU,GC,堆,方法区以及线程的信息(jstat,jstack)

3.dump 以及分析堆转储快照(jmap,jhat)

4.方法级的程序运行性能分析,找出被调用最多,运行时间最长的方法。

5.离线程序快照:手机程序的运行时配置,线程dump,内存dump 等信息建立一个快照,可以将快照发送开发者进行bug反馈。

6.其他plugins 的无限的可能性。

BTrace 动态日志跟踪:BTrace 是一个VisualVM插件,本身也是可以独立运行的程序。它的作用是在不停止目标程序运行的前提下,通过HotSpot虚拟机的HotSwap技术动态加入原本并不存在的调试代码。在实际生产中的程序很有意义:经常遇到程序出现问题,但排查错误的一些必要信息,比如方法参数,返回值等,在开发中并没有打印到日志之中,这时就可以采用BTrace。BTrace安装步骤地址[可以避免入坑]:https://blog.csdn.net/qq_35781178/article/details/102936149

BTrace 使用教程如下[先把项目启动起来哦]:

   

@OnMethod(clazz="com.yonghui.yh.soi.perform.pickcenter.view.service.impl.PickingScreenServiceImpl",
                  method="queryUnderSKUNum",location=@Location(Kind.RETURN))
    public static void func(@Self com.yonghui.yh.soi.perform.pickcenter.view.service.impl.PickingScreenServiceImpl instance,
     String locationCode,@Return String result)
    {
      println("调用堆栈");
      jstack();
      println(strcat("方法参数locationCode",locationCode));
      println(strcat("方法结果result",result));
    }

《第七章:虚拟机类加载机制》

类加载的时机: 加载,验证,准备,解析,初始化,使用,卸载。

1.使用java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

2.当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

3.当虚拟机启动时,用户需要执行一个要执行的朱磊,虚拟机会先初始化这个主类。


加载:
 1.通过一个类的全限定名来获取来获取定义此类的二进制字节流
 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
 3.在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

 双亲委派模型:
  从JVM 来说,只存在李阆中不同的类加载器:一种是启动类加载器,这个类加载器使用C++ 语言实现,是JVM自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由java 语言实现,独立于JVM 外部,并且全都继承自抽象类java.lang.ClassCoader。

volatile 可以理解成java 虚拟机提供的最轻量级的同步机制。当一个变量定义为volatile 之后,它将具备两种特性:第一就是保证此变量对所有线程的可见性,指的是当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量是不能做到。【volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。】

由于volatile 变量只能保证可见性,在不符合以下两条规则的运算场景下,我们仍然要通过加锁来保证原子性:
1.运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
2.变量不需要与其他的状态变量共同参与不变约束。

java 语言提供了volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 关键字本身就包含了禁止指令重排序的语义,而synchronized 则由一个变量在同一时刻只允许一条线程对其进行lock操作这条规则获取的,这条规则决定了持有同一个锁的两个同步块只能串行的进入。

java 线程调度: 线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度和抢占式线程调度。

如果使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己当工作执行完了之后,要主动通知系统切换到另一个线程上。协同式多线程的最大好处就是实现简单。

状态转换: java 语言定义了5种线程状态,在任意一个时间点,一个线程有且只有其中的一种状态: 新建new,运行runable,等待wait,阻塞blocked,结束terminated。

新建new:创建后尚未启动的线程处于此状态。

运行runable:runable 包括了操作系统线程状态中的running 和ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU 为它分配执行 时间。

无限期等待waiting:处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程显示的唤醒。

以下方法会让线程陷入无限期的等待状态:
1.没有设置timeout 参数的object.wait()方法。
2.没有设置timeout参数的Thread.join()方法。
3.lockSupport.park()方法。

限期等待 timed waitting :处于这种状态的线程也不会被分配CPU 执行时间,不过无需等待被其他线程显示的唤醒,在一定时间之后他们会由系统自动唤醒:

1.thread.sleep()方法。
2.设置了timeout 参数的object.wait()方法。
3.locksupport.parknanos()方法。
4.locksupport.parkUntil()方法。

阻塞blocked:线程被阻塞了,阻塞状态与等待状态的区别就是:阻塞状态在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而等待状态则是在等待一算时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。

结束terminated:已终止线程的线程状态,线程已经结束执行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章