2.1 定义

Java Virtual Machine Stacks(Java虚拟机栈)

  • Java 虚拟机栈描述的是 Java 方法执行的内存模型,用于存储栈帧,是线程私有的,生命周期随着线程启动而产生,线程结束而消亡
  • 线程启动时会创建虚拟机栈,每个方法在执行时会在虚拟机栈中创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态连接、方法返回地址等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈(压栈)到出栈(弹栈)的过程
  • 每个线程只能有一个活动栈帧,对应着正在执行的那个方法  问题辨析
  1. 垃圾回收是否涉及栈内存?

    不涉及。栈内存无非就是一次次的方法调用产生的栈帧内存,栈帧内存在每一次方法调用后都会被弹出栈,也就是这部分内存会被自动的回收掉,所以并不需要垃圾回收来回收栈内存。

  2. 栈内存分配越大越好吗?

    不是。

    • 栈内存可以在代码运行时通过一个虚拟机参数来指定其大小 -Xss size。不指定的话,除了windows系统,默认都是1M,windows系统是依据虚拟内存大小分配。
    • 栈内存分配的越大,只是能够进行更多次的方法递归调用,并不会增快运行的效率,反而会使得可执行的线程数变少(总内存不变,每个线程的栈内存变大,数量变少)
  3. 方法内的局部变量是否线程安全?

    变量是否是线程安全的,取决于这个变量被多线程共享时,每次运行结果和单线程运行的结果是否是一样的。

    • 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的。
    • 如果局部变量是引用对象,且逃离了方法的作用范围,那么就需要考虑线程安全(基本数据类型不会有这个问题)

    示例代码1

    static void m1() {
      int x=0;
      for(int i=0;i<500;i++){
        x++;
      }
      System.out.println(x);
    }
    

    x这个局部变量是线程安全的。每个线程对应一个栈,然后线程内每次方法调用都会产生一个新的栈帧,所以x变量处于不同线程的栈的栈帧中,互不影响,也就是线程安全的。

    示例代码2

    public static void m1() {
      StringBuilder sb = new StringBuilder();
      sb.append(1);
      sb.append(2);
      sb.append(3);
      System.out.println(sb.toString());
    }
    
    public static void m2(StringBuilder sb) {
      sb.append(1);
      sb.append(2);
      sb.append(3);
      System.out.println(sb.toString());
    }
    
    public static StringBuilder m3() {
      StringBuilder sb = new StringBuilder();
      sb.append(1);
      sb.append(2);
      sb.append(3);
      return sb;
    }
    
    
    • m1方法中sb是线程安全的,因为其是线程内的局部变量。
    • m2方法中sb是线程不安全的,因为其是作为方法的参数传递进来,那么就有可能有其他线程能够访问到这个变量,那么这个变量就是多个线程共享的,可能造成值不一致,也就是线程不安全的。
    • m3方法中sb是线程不安全的,虽然其是线程中的局部变量,但是其作为返回值返回了,那么就有可能被其他线程使用,也就是多线程共享,可能造成值不一致,线程不安全。

2.2 栈帧

  • 栈帧存在于 Java 虚拟机栈中,是 Java 虚拟机栈中的单位元素,每个线程中调用同一个方法或者不同的方法,都会创建不同的栈帧(可以简单理解为,一个线程调用一个方法创建一个栈帧),所以,调用的方法链越多,创建的栈帧越多(例如:递归)。每调用一个新的方法,被调用方法对应的栈帧就会被放到栈顶(入栈),也就是成为新的当前栈帧。当一个方法执行完成退出的时候,此方法对应的栈帧也相应销毁(出栈)。 

  • 2.2.1 局部变量表(Local

2.2.1 局部变量表(Local Variable Table)

  • 每个栈帧中都包含一组称为局部变量表的变量列表,用于存放方法参数和方法内部定义的局部变量。在 Java 程序编译成 Class 文件时,在 Class 文件格式属性表中 Code 属性的 max_locals(局部变量表所需的存储空间,单位是 Slot) 数据项中确定了需要分配的局部变量表的最大容量。

  • 局部变量表的容量以变量槽(Variable Slot)为最小单位,不过 Java 虚拟机规范中并没有明确规定每个 Slot 所占据的内存空间大小,只是有导向性地说明每个 Slot 都应该存放的8种类型: byte、short、int、float、char、boolean、reference(对象引用就是存到这个栈帧中的局部变量表里的,这里的引用指的是局部变量的对象引用,而不是成员变量的引用。成员变量的对象引用是存储在 Java 堆(Heap)中)、returnAddress(虚拟机数据类型,returnAddress 类型的值就是指向特定指令内存地址的指针,JVM支持多线程,每个线程有自己的程序计数器(pc register),而 pc 中的值就是当前指令所在的内存地址,即 returnAddress 类型的数据,当线程执行 native 方法时,pc 中的值为 undefined)类型的数据,这8种类型的数据,都可以使用32位或者更小的空间去存储。Java 虚拟机规范允许 Slot 的长度可以随着处理器、操作系统或者虚拟机的不同而发生变化。对于64位的数据类型,虚拟机会以高位在前的方式为其分配两个连续的 Slot 空间。即 long 和 double 两种类型。做法是将 long 和 double 类型速写分割为32位读写的做法。不过由于局部变量表建立在线程的堆栈上,是线程的私有数据,无论读写两个连续的 Slot 是否是原子操作,都不会引起数据安全问题。

  • Java 虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始到局部变量表最大的 Slot 数量。如果是32位数据类型的数据,索引 n 就表示使用第 n 个 Slot,如果是64位数据类型的变量,则说明要使用第 n 和第 n+1 两个 Slot。

  • 在方法执行过程中,Java 虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程。如果是实例方法(非 static方法),那么局部变量表中的第0位索引的 Slot 默认是用来传递方法所属对象实例的引用,在方法中可以通过关键字 this 来访问这个隐含的参数。其余参数按照参数表的顺序来排列,占用从1开始的局部变量 Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的 Slot。

  • 局部变量表中的 Slot 是可重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码程序计数器的值已经超过了某个变量的作用域,那么这个变量相应的 Slot 就可以交给其他变量去使用,节省栈空间,但也有可能会影响到系统的垃圾收集行为。

  • 局部变量无初始值(实例变量和类变量都会被赋予初始值),类变量有两次赋初始值的过程,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予开发者定义的值。因此即使在初始化阶段开发者没有为类变量赋值也没有关系,类变量仍然具有一个确定的默认值。但局部变量就不一样了,如果一个局部变量定义了但没有赋初始值是不能使用的。

使用一段代码说明一下局部变量表:

// java 代码
public int test() {
    int x = 0;
    int y = 1;
    return x + y;
}

// javac 编译后的字节码,使用 javap -v 查看
public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_1
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: ireturn
      LineNumberTable:
        line 7: 0
        line 8: 2
        line 9: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  this   Lcom/alibaba/uc/TestClass;
            2       6     1     x   I
            4       4     2     y   I

对应上面的解释说明,通过 LocalVariableTable 也可以看出来: Code 属性: stack(int x(1个栈深度)+ int y(1个栈深度))=2, locals(this(1 Slot)+ int x(1 Slot)+ int y(1 Slot))=3, args_size(非 static 方法,this 隐含参数)=1

验证 Slot 复用,运行以下代码时,在 VM 参数中添加 -verbose:gc

public void test() {
   {
      byte[] placeholder = new byte[64 * 1024 * 1024];
   }
   int a = 0; // 当这段代码注释掉时,System.gc() 执行后,也并不会回收这64MB内存。当这段代码执行时,内存被回收了
   System.gc();
}

局部变量表中的 Slot 是否还存在关于 placeholder 数组对象的引用。当 int a = 0; 不执行时,代码虽然已经离开了 placeholder 的作用域,但是后续并没有任何对局部变量表的读写操作,placeholder 原本所占用的 Slot 还没有被其他变量所复用,所以 placeholder 作为 GC Roots(所有 Java 线程当前活跃的栈帧里指向 Java 堆里的对象的引用) 仍然是可达对象。当 int a = 0; 执行时,placeholder 的 Slot 被变量 a 复用,所以 GC 触发时,placeholder 变成了不可达对象,即可被 GC 回收。

2.2.2 操作数栈(Operand Stack)

  • 操作数栈是一个后入先出(Last In First Out)栈,方法的执行操作在操作数栈中完成,每一个字节码指令往操作数栈进行写入和提取的过程,就是入栈和出栈的过程。

  • 同局部变量表一样,操作数栈的最大深度也是Java 程序编译成 Class 文件时被写入到 Class 文件格式属性表的 Code 属性的 max_stacks 数据项中。

  • 操作数栈的每一个元素可以是任意的 Java 数据类型,32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2,在方法执行的任何时候,操作数栈的深度都不会超过在 max_stacks 数据项中设定的最大值(指的是进入操作数栈的 “同一批操作” 的数据类型的栈容量的和)。

  • 当一个方法刚刚执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,通过一些字节码指令从局部变量表或者对象实例字段中复制常量或者变量值到操作数栈中,也提供一些指令向操作数栈中写入和提取值,及结果入栈,也用于存放调用方法需要的参数及接受方法返回的结果。例如,整数加法的字节码指令 iadd(使用 iadd 指令时,相加的两个元素也必须是 int 型) 在运行的时候将操作数栈中最接近栈顶的两个 int 数值元素出栈相加,然后将相加结果入栈。

2.2.3 动态连接(Dynamic Linking)

  • 每个栈帧都包含一个指向**运行时常量池(JVM 运行时数据区域)**中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。

  • <span style='color:red'>在 Class 文件格式的常量池(存储字面量和符号引用)中存有大量的符号引用(1.类的全限定名,2.字段名和描述符,3.方法名和描述符),字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载过程的解析阶段的时候转化为直接引用(指向目标的指针、相对偏移量或者是一个能够直接定位到目标的句柄),这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。</span>

    看看以下代码的 Class 文件格式的常量池:

// java 代码
 public Test test() {
    return new Test();
 }

// 字节码指令
// Class文件的常量池
Constant pool:
   #1 = Methodref          #4.#19         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#20         // com/alibaba/uc/Test.i:I
   #3 = Class              #21            // com/alibaba/uc/Test
   #4 = Class              #22            // java/lang/Object
   #5 = Utf8               i
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/alibaba/uc/Test;
  #14 = Utf8               test
  #15 = Utf8               ()I
  #16 = Utf8               <clinit>
  #17 = Utf8               SourceFile
  #18 = Utf8               Test.java
  #19 = NameAndType        #7:#8          // "<init>":()V
  #20 = NameAndType        #5:#6          // i:I
  #21 = Utf8               com/alibaba/uc/Test
  #22 = Utf8               java/lang/Object

public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: getstatic     #2                  // Field i:I
         3: areturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lcom/alibaba/uc/Test;

从上面字节码指令看出 0: getstatic #2 // Field i:I 这行字节码指令指向 Constant pool 中的 #2,而 #2 中指向了 #3 和 #20 为符号引用,在类加载过程的解析阶段会被转化为直接引用(指向方法区的指针)。

2.2.4 方法返回地址

  • 当一个方法开始执行后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令(例如:areturn),这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。
  • 另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用 athrow 字节码指令产生的异常,只要在本方法的异常处理器表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。
  • 无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的程序计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
  • 方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整程序计数器的值以指向方法调用指令后面的一条指令等。

2.3 栈内存溢出

  • 栈帧过多导致栈内存溢出

    例如递归方法,当方法调用层级过多,产生大量的栈帧,却没有出栈,就会导致栈内存溢出,抛出StackOverflowError异常

    示例代码

    public class Demo {
        private static int count;
    
        public static void main(String[] args) {
            try {
                method1();
            } catch (Throwable e) {
                e.printStackTrace();
                System.out.println(count);
            }
        }
    
        private static void method1() {
            count++;
            method1();
        }
    }
    

    执行结果

    java.lang.StackOverflowError
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    14602
    

    可以看到方法总共执行了14602次就导致了栈溢出,可以在虚拟机运行参数中设置-Xss128k调整栈内存大小,结果执行次数就会变小

  • 如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将抛出一个 OutOfMemoryError 异常

2.4 线程运行诊断

2.4.1 cpu占用过高

示例代码

public class Demo {

    public static void main(String[] args) {
        new Thread(null, () -> {
            System.out.println("1...");
            while(true) {

            }
        }, "thread1").start();

        new Thread(null, () -> {
            System.out.println("2...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread2").start();

        new Thread(null, () -> {
            System.out.println("3...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread3").start();
    }
}

linux上运行此代码

javac Demo.java
nohup java Demo &

查看输出

tail -f nohup.out
1...
2...
3...
1...
2...
3...

查看cpu状况

top

可以看到刚才运行的java程序占用cpu很高,进程号为10526

查询该进程下所有线程的运行状态

top -Hp 10526

可以看到占用cpu最高的线程是10536

使用jstack工具获取10526进程中所有线程运行信息

jstack 10526
2019-12-17 17:24:00
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.11-b03 mixed mode):

"Attach Listener" #12 daemon prio=9 os_prio=0 tid=0x00007f94d0001000 nid=0x294a waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #11 prio=5 os_prio=0 tid=0x00007f94f8008800 nid=0x291f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"thread3" #10 prio=5 os_prio=0 tid=0x00007f94f80e7000 nid=0x292a waiting on condition [0x00007f94fd9e3000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at Demo.lambda$main$2(Demo.java:24)
	at Demo$$Lambda$3/1523554304.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"thread2" #9 prio=5 os_prio=0 tid=0x00007f94f80e5000 nid=0x2929 waiting on condition [0x00007f94fdae4000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at Demo.lambda$main$1(Demo.java:15)
	at Demo$$Lambda$2/1072591677.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"thread1" #8 prio=5 os_prio=0 tid=0x00007f94f80e3800 nid=0x2928 runnable [0x00007f94fdbe5000]
   java.lang.Thread.State: RUNNABLE
	at Demo.lambda$main$0(Demo.java:6)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f94f80a8800 nid=0x2926 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f94f80a5800 nid=0x2925 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f94f80a3000 nid=0x2924 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f94f80a1000 nid=0x2923 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f94f8071800 nid=0x2922 in Object.wait() [0x00007f94fe1eb000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:142)
	- locked <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:158)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f94f806d800 nid=0x2921 in Object.wait() [0x00007f94fe2ec000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3521030> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)
	- locked <0x00000000e3521030> (a java.lang.ref.Reference$Lock)

"VM Thread" os_prio=0 tid=0x00007f94f8068800 nid=0x2920 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f94f80ad800 nid=0x2927 waiting on condition 

JNI global references: 152

其中thread1thread2thread3是我们自己创建的线程,其他都是jvm的线程

获取占用cpu最高的线程号的十六进制

printf '%x\n' 10536
2928

匹配jstack得到的线程信息

"thread1" #8 prio=5 os_prio=0 tid=0x00007f94f80e3800 nid=0x2928 runnable [0x00007f94fdbe5000]
   java.lang.Thread.State: RUNNABLE
	at Demo.lambda$main$0(Demo.java:6)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

最后,匹配代码的第6行,得出占用cpu过高的原因的在线程中无限循环执行导致。

2.4.2 程序执行很长时间没有结果

示例代码

public class Demo {
    static A a = new A();
    static B b = new B();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (a) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (b) {
                synchronized (a) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start(www.shicaiyl.com);
    }
}

class A {

}

class B {

}

linux运行此代码

javac Demo.java
nohup java Demo &

进程号为10633,查看输出时,发现一直没有反应

根据进程号获取所有线程信息

jstack 10633
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.11-b03 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007fc3a8001000 nid=0x299f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007fc3d0008800 nid=0x298a waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #9 prio=5 os_prio=0 tid=0x00007fc3d00dd800 nid=0x2994 waiting for monitor entry [0x00007fc3ad8d2000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at Demo.lambda$main$1(Demo.java:21)
	- waiting to lock <0x00000000e3539580> (www.dajuhezc.cn a A)
	- locked <0x00000000e3539590> (a B)
	at Demo$$Lambda$2/1072591677.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"Thread-0" #8 prio=5 os_prio=0 tid=0x00007fc3d00db800 nid=0x2993 waiting for monitor entry [0x00007fc3ad9d3000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at Demo.lambda$main$0(Demo.java:13)
	- waiting to lock <0x00000000e3539590> (a B)
	- locked <0x00000000e3539580> (a A)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fc3d00a8800 nid=0x2991 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 www.shentuylzc.cn_prio=0 tid=0x00007fc3d00a5800 nid=0x2990 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fc3d00a3000 nid=0x298f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 www.shentuylzc.cn_prio=0 tid=0x00007fc3d00a1000 nid=0x298e runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fc3d0071800 nid=0x298d in Object.wait() [0x00007fc3c05fc000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:142)
	- locked <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:158)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fc3d006d800 nid=0x298c in Object.wait() [0x00007fc3c06fd000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3521030> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)
	- locked <0x00000000e3521030> (a java.lang.ref.Reference$Lock)

"VM Thread" os_prio=0 tid=0x00007fc3d0068800 nid=0x298b runnable 

"VM Periodic Task Thread" os_www.jiuhuaylgw.cn prio=0 tid=0x00007fc3d00ad800 nid=0x2992 waiting on condition 

JNI global references: 151

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007fc3b4003778 (www.huizhonggjpt.cn object 0x00000000e3539580, a A),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fc3b40062c8 (object 0x00000000e3539590, a B),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at Demo.lambda$main$1(Demo.www.qiaoheibpt.com java:21)
	- waiting to lock <0x00000000e3539580> www.feishenbo.cn(a A)
	- locked <0x00000000e3539590> (a B)
	at Demo$$Lambda$2/1072591677.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)
"Thread-0":
	at Demo.lambda$main$0(Demo.java:13)
	- waiting to lock <0x00000000e3539590> (a B)
	- locked <0x00000000e3539580> (a A)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.www.baihua178.cn java:745)

Found 1 deadlock.

Found one Java-level deadlock可以在底部看到这句话,意思是死锁,根据后面具体信息可知,在代码29行,Thread-1锁住对象b,在等待对象a的锁,而在代码13行,Thread-0锁住对象a,在等待对象b的锁,从而造成死锁,程序无反应。

浅谈JVM - 内存结构(二)- 虚拟机栈|凡酷的更多相关文章

  1. 浅谈JVM内存区域划分

    好吧,虽说真的有看过<深入分析Java Web技术内幕>一书,但当时看的时候还是一知半解,稀里糊涂的看完了.本来是打算暑假拿起来再看一遍的,但是早两天一个阿里学长给我做了个小面试,让我颇受 ...

  2. 浅谈JVM内存模型

    JAVA虚拟机在执行JAVA程序的时候,会把它管理的内存分成若干不同的数据区域,每个区域都有各自的用途.目前大致把JVM内存模型划分为五个区域:程序计数器,虚拟机栈,本地方法栈,堆和方法区. 程序计数 ...

  3. 浅谈JVM内存分配与垃圾回收

    大家好,我是微尘,最近又去翻了周志明老师的<深入理解Java虚拟机>这本书.已经看了很多遍了,每次都感觉似乎看懂了,但没过多久就忘了.这次翻了第三章的垃圾收集器与内存分配策略,感觉有了新的 ...

  4. Java 内存结构之虚拟机栈

    2.虚拟机栈 定义:虚拟机栈(Java Virtual Machine Stacks)就是每个线程运行需要的内存空间,栈由一个一个的栈帧(Frame)组成,栈帧就是每个方法运行时需要的内存(方法的参数 ...

  5. JVM学习十二 - (复习)JVM内存结构

    JVM 内存结构 Java 虚拟机的内存空间分为 5 个部分: 程序计数器 Java 虚拟机栈 本地方法栈 堆 方法区 JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代.元 ...

  6. 简单说说 Java 的 JVM 内存结构

    问:简单说说 Java 的 JVM 内存结构分为哪几个部分? 答:JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分,分别解释如下.虚拟机栈:线程私有的,每个方法在执行时会创建一个 ...

  7. java面试-JVM内存结构

    一.JVM内存结构 二.类加载(classLoader)机制 java中的ClassLoader详解 java类加载机制面试题 java类加载机制面试题 虚拟机把描述类的数据从Class文件加载到内存 ...

  8. [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义

    前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine  ,既然是虚拟机, ...

  9. JVM内存结构之堆、栈、方法区以及直接内存、堆和栈区别

    JVM内存结构之堆.栈.方法区以及直接内存.堆和栈区别 一.  理解JVM中堆与栈以及方法区 堆(heap):FIFO(队列优先,先进先出):二级缓存:*JVM中只有一个堆区被所有线程所共享:对象和数 ...

随机推荐

  1. 六十、SAP中的加减乘除等算数运算符

    一.代码和显示如下,很奇怪的事情是,负数的负号居然在数字后面

  2. 【机器学习实战学习笔记(2-2)】决策树python3.6实现及简单应用

    文章目录 1.ID3及C4.5算法基础 1.1 计算香农熵 1.2 按照给定特征划分数据集 1.3 选择最优特征 1.4 多数表决实现 2.基于ID3.C4.5生成算法创建决策树 3.使用决策树进行分 ...

  3. SHELL学习笔记三

    SHELL学习笔记一 SHELL学习笔记二 SHELL学习笔记三 for 命令 读取列表中的复杂值 从变量读取列表 从命令读取值 更改字段分隔符 用通配符读取目录 which 使用多个测试命令 unt ...

  4. Origin中使用CopyPage复制图片到Word后比例失调解决办法

    Origin画图的优势很多,其图形美观易于操作.对我而言,Origin最大的优点就是与Word兼容,在Origin操作界面空白处直接使用右键CopyPage命令,然后在Word中使用粘贴命令即可插入图 ...

  5. SOA--基于银行系统实例分析

    阅读以下关于 Web 系统设计的叙述 [说明] 某银行拟将以分行为主体的银行信息系统,全面整合为由总行统一管理维护的银行信息系统,实现统一的用户账户管理.转账汇款.自助缴费.理财投资.贷款管理.网上支 ...

  6. P1036 跟奥巴马一起编程

    转跳点:

  7. 51nod 1022 石子归并 环形+四边形优化

    1022 石子归并 V2 基准时间限制:1 秒 空间限制:131072 KB 分值: 160 难度:6级算法题  收藏  关注 N堆石子摆成一个环.现要将石子有次序地合并成一堆.规定每次只能选相邻的2 ...

  8. mongodb replication set 主从切换

    今天被问到mongodb副本集主从切换的问题,然后发现没有相关命令的中文文档,这里翻译记录一下 rs.stepDown() rs.stepDown(stepDownSecs, secondaryCat ...

  9. PHP中Cookie与Session的异同以及使用

    Cookie与Session的异同: 一.cookie机制 Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器.IETF RFC 2965 HTTP State Mana ...

  10. my97datepicker实现日期改变立刻触发函数

    <input type="text" class="Wdate" onclick="WdatePicker({onpicking:functio ...