2.1 定义
Java Virtual Machine Stacks
(Java虚拟机栈)
不涉及。栈内存无非就是一次次的方法调用产生的栈帧内存,栈帧内存在每一次方法调用后都会被弹出栈,也就是这部分内存会被自动的回收掉,所以并不需要垃圾回收来回收栈内存。
栈内存分配越大越好吗?
不是。
-Xss size
。不指定的话,除了windows系统,默认都是1M,windows系统是依据虚拟内存大小分配。方法内的局部变量是否线程安全?
变量是否是线程安全的,取决于这个变量被多线程共享时,每次运行结果和单线程运行的结果是否是一样的。
**示例代码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 栈帧
2.2.1 局部变量表(Local Variable Table)
static
方法),那么局部变量表中的第0位索引的 Slot 默认是用来传递方法所属对象实例的引用,在方法中可以通过关键字 this
来访问这个隐含的参数。其余参数按照参数表的顺序来排列,占用从1开始的局部变量 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)
iadd
(使用 iadd
指令时,相加的两个元素也必须是 int 型) 在运行的时候将操作数栈中最接近栈顶的两个 int 数值元素出栈相加,然后将相加结果入栈。2.2.3 动态连接(Dynamic Linking)
看看以下代码的 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)。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
调整栈内存大小,结果执行次数就会变小
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
其中thread1
、thread2
、thread3
是我们自己创建的线程,其他都是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();
}
}
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> (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 os_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 os_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_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 (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.java:21)
- waiting to lock <0x00000000e3539580> (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.java:745)
Found 1 deadlock.
Found one Java-level deadlock
可以在底部看到这句话,意思是死锁,根据后面具体信息可知,在代码29行,Thread-1
锁住对象b,在等待对象a的锁,而在代码13行,Thread-0
锁住对象a,在等待对象b的锁,从而造成死锁,程序无反应。
欢迎关注公众号,后续文章更新通知,一起讨论技术问题 。
Original url: Access
Created at: 2019-12-31 12:34:25
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论