微信搜一搜: 全栈小刘,获取文章全套 pdf版
文档网址
https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
虚拟机栈出现的背景
内存中的栈与堆
首先栈是运行时的单位,而堆是存储的单位
虚拟机栈的基本内容
Java虚拟机栈是什么?
Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame), 对应着一次次的Java方法调用,栈是线程私有的
虚拟机栈的生命周期
生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了
虚拟机栈的作用
主管Java程序的运行,它 保存方法的局部变量(8 种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。
栈的特点
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。JVM直接对Java栈的操作只有两个:
对于栈来说不存在垃圾回收问题(栈存在溢出的情况)
栈中可能出现的异常
面试题:栈中可能出现的异常
栈异常演示
public class StackErrorTest {
private static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args);
}
}
设置栈内存的大小
-Xss1024m // 栈内存为 1024MBS
-Xss1024k // 栈内存为 1024KB
public class StackErrorTest {
private static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args);
}
}
栈存储什么?
栈的运行原理
代码示例:
public class StackFrameTest {
public static void main(String[] args) {
StackFrameTest test = new StackFrameTest();
test.method1();
}
public void method1() {
System.out.println("method1()开始执行...");
method2();
System.out.println("method1()执行结束...");
}
public int method2() {
System.out.println("method2()开始执行...");
int i = 10;
int m = (int) method3();
System.out.println("method2()即将结束...");
return i + m;
}
public double method3() {
System.out.println("method3()开始执行...");
double j = 20.0;
System.out.println("method3()即将结束...");
return j;
}
}
method1()开始执行...
method2()开始执行...
method3()开始执行...
method3()即将结束...
method2()即将结束...
method1()执行结束...
public void method1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String method1()开始执行...
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: invokevirtual #8 // Method method2:()I
12: pop
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
16: ldc #9 // String method1()执行结束...
18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
21: return
LineNumberTable:
line 16: 0
line 17: 8
line 18: 13
line 19: 21
LocalVariableTable:
Start Length Slot Name Signature
0 22 0 this Lcom/atguigu/java1/StackFrameTest;
public int method2();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #10 // String method2()开始执行...
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: bipush 10
10: istore_1
11: aload_0
12: invokevirtual #11 // Method method3:()D
15: d2i
16: istore_2
17: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
20: ldc #12 // String method2()即将结束...
22: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: iload_1
26: iload_2
27: iadd
28: ireturn
LineNumberTable:
line 22: 0
line 23: 8
line 24: 11
line 25: 17
line 26: 25
LocalVariableTable:
Start Length Slot Name Signature
0 29 0 this Lcom/atguigu/java1/StackFrameTest;
11 18 1 i I
17 12 2 m I
public double method3();
descriptor: ()D
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String method3()开始执行...
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: ldc2_w #14 // double 20.0d
11: dstore_1
12: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #16 // String method3()即将结束...
17: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: dload_1
21: dreturn
LineNumberTable:
line 30: 0
line 31: 8
line 32: 12
line 33: 20
LocalVariableTable:
Start Length Slot Name Signature
0 22 0 this Lcom/atguigu/java1/StackFrameTest;
12 10 1 j D
栈帧内部结构
每个栈帧中存储着:
并行每个线程下的栈都是私有的,因此每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要由局部变量表 和 操作数栈决定的
认识局部变量表
局部变量表所需的容量大小是在编译期确定下来的
public class LocalVariablesTest {
private int count = 0;
public static void main(String[] args) {
LocalVariablesTest test = new LocalVariablesTest();
int num = 10;
test.test1();
}
public void test1() {
Date date = new Date();
String name1 = "atguigu.com";
test2(date, name1);
System.out.println(date + name1);
}
public String test2(Date dateP, String name2) {
dateP = null;
name2 = "songhongkang";
double weight = 130.5;
char gender = ‘男‘;
return dateP + name2;
}
}
思考:
public static void main(String[] args) {
if(args == null){
LocalVariablesTest test = new LocalVariablesTest();
}
int num = 10;
}
字节码中方法内部结构的剖析
关于 Slot 的理解
Slot 代码示例
this 存放在 index = 0 的位置:
public void test3() {
this.count++;
}
64位的类型(1ong和double)占用两个slot
public String test2(Date dateP, String name2) {
dateP = null;
name2 = "songhongkang";
double weight = 130.5;
char gender = ‘男‘;
return dateP + name2;
}
static 无法调用 this
public static void testStatic(){
LocalVariablesTest test = new LocalVariablesTest();
Date date = new Date();
int count = 10;
System.out.println(count);
}
Slot 的重复利用
栈帧中的局部变量表中的槽位是 可以重用的, 如果一个局部变量过了其作用域,那么在其作用域之后申明新的局部变量变就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。
public void test4() {
int a = 0;
{
int b = 0;
b = a + 1;
}
int c = a + 1;
}
静态变量与局部变量的对比
变量的分类:
实例变量:随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值
局部变量:在使用前,必须要进行显式赋值的!否则,编译不通过,应该是栈中数据弹出后,不会清除上次的值,再次使用时,如果不显示初始化,就会出现脏数据
参数表分配完毕之后,再根据方法体内定义的变量的顺序和作用域分配。
我们知道 类变量表有两次初始化的机会, 第一次是在"准备阶段",执行系统初始化,对类变量设置零值,另一次则是在"初始化"阶段,赋予程序员在代码中定义的初始值。
和类变量初始化不同的是, 局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。
代码示例
补充说明
操作数栈的特点
操作数栈:Operand Stack
代码举例
操作数栈的作用
操作数栈的深度
通过反编译生成的字节码指令查看操作数栈的深度
操作数栈代码追踪
public void testAddOperation() {
byte i = 15;
int j = 8;
int k = i + j;
}
0 bipush 15
2 istore_1
3 bipush 8
5 istore_2
6 iload_1
7 iload_2
8 iadd
9 istore_3
10 return
程序执行流程如下
关于 int j = 8; 的说明
关于调用方法,返回值入操作数栈的说明
public int getSum(){
int m = 10;
int n = 20;
int k = m + n;
return k;
}
public void testGetSum(){
int i = getSum();
int j = 10;
}
++i 与 i++ 的区别
public void add(){
int i1 = 10;
i1++;
int i2 = 10;
++i2;
int i3 = 10;
int i4 = i3++;
int i5 = 10;
int i6 = ++i5;
int i7 = 10;
i7 = i7++;
int i8 = 10;
i8 = ++i8;
int i9 = 10;
int i10 = i9++ + ++i9;
}
0 bipush 10
2 istore_1
3 iinc 1 by 1
6 bipush 10
8 istore_2
9 iinc 2 by 1
12 bipush 10
14 istore_3
15 iload_3
16 iinc 3 by 1
19 istore 4
21 bipush 10
23 istore 5
25 iinc 5 by 1
28 iload 5
30 istore 6
32 bipush 10
34 istore 7
36 iload 7
38 iinc 7 by 1
41 istore 7
43 bipush 10
45 istore 8
47 iinc 8 by 1
50 iload 8
52 istore 8
54 bipush 10
56 istore 9
58 iload 9
60 iinc 9 by 1
63 iinc 9 by 1
66 iload 9
68 iadd
69 istore 10
71 return
i++
int i3 = 10;
int i4 = i3++;
12 bipush 10
14 istore_3
15 iload_3
16 iinc 3 by 1
19 istore 4
++i
int i5 = 10;
int i6 = ++i5;
21 bipush 10
23 istore 5
25 iinc 5 by 1
28 iload 5
30 istore 6
总结:
栈顶缓存技术:Top Of Stack Cashing
动态链接(或指向运行时常量池的方法引用)
动态链接:Dynamic Linking
代码示例
public class DynamicLinkingTest {
int num = 10;
public void methodA(){
System.out.println("methodA()....");
}
public void methodB(){
System.out.println("methodB()....");
methodA();
num++;
}
}
public void methodB();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String methodB()....
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: invokevirtual #7 // Method methodA:()V
12: aload_0
13: dup
14: getfield #2 // Field num:I
17: iconst_1
18: iadd
19: putfield #2 // Field num:I
22: return
LineNumberTable:
line 16: 0
line 18: 8
line 20: 12
line 21: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this Lcom/atguigu/java1/DynamicLinkingTest;
#7 = Methodref #8.#31
#8 = Class #32
:去找 #32#32 = Utf8 com/atguigu/java1/DynamicLinkingTest
DynamicLinkingTest
这个类#31 = NameAndType #19:#13
:去找 #19 和 #13#19 = Utf8 methodA
:方法名为 methodA#13 = Utf8 ()V
:方法没有形参,返回值为 voidConstant pool:
#1 = Methodref #9.#23 // java/lang/Object."<init>":()V
#2 = Fieldref #8.#24 // com/atguigu/java1/DynamicLinkingTest.num:I
#3 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #27 // methodA()....
#5 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = String #30 // methodB()....
#7 = Methodref #8.#31 // com/atguigu/java1/DynamicLinkingTest.methodA:()V
#8 = Class #32 // com/atguigu/java1/DynamicLinkingTest
#9 = Class #33 // java/lang/Object
#10 = Utf8 num
#11 = Utf8 I
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/atguigu/java1/DynamicLinkingTest;
#19 = Utf8 methodA
#20 = Utf8 methodB
#21 = Utf8 SourceFile
#22 = Utf8 DynamicLinkingTest.java
#23 = NameAndType #12:#13 // "<init>":()V
#24 = NameAndType #10:#11 // num:I
#25 = Class #34 // java/lang/System
#26 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#27 = Utf8 methodA()....
#28 = Class #37 // java/io/PrintStream
#29 = NameAndType #38:#39 // println:(Ljava/lang/String;)V
#30 = Utf8 methodB()....
#31 = NameAndType #19:#13 // methodA:()V
#32 = Utf8 com/atguigu/java1/DynamicLinkingTest
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (Ljava/lang/String;)V
</init></init></init>
为什么要用常量池呢?
静态链接机制与动态链接机制
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关
方法的绑定机制
静态链接和动态链接对应的方法的绑定机制为:早期绑定(Early Binding)和晚期绑定(Late Binding)。 绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。
代码示例
class Animal {
public void eat() {
System.out.println("动物进食");
}
}
interface Huntable {
void hunt();
}
class Dog extends Animal implements Huntable {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void hunt() {
System.out.println("捕食耗子,多管闲事");
}
}
class Cat extends Animal implements Huntable {
public Cat() {
super();
}
public Cat(String name) {
this();
}
@Override
public void eat() {
super.eat();
System.out.println("猫吃鱼");
}
@Override
public void hunt() {
System.out.println("捕食耗子,天经地义");
}
}
public class AnimalTest {
public void showAnimal(Animal animal) {
animal.eat();
}
public void showHunt(Huntable h) {
h.hunt();
}
}
多态性与方法绑定机制
虚方法与非虚方法
虚方法与非虚方法的区别
子类对象的多态的使用前提:
虚拟机中调用方法的指令
四条普通指令:
<init></init>
方法、私有及父类方法,解析阶段确定唯一方法版本一条动态调用指令
invokedynamic:动态解析出需要调用的方法,然后执行
区别
代码示例:
class Father {
public Father() {
System.out.println("father的构造器");
}
public static void showStatic(String str) {
System.out.println("father " + str);
}
public final void showFinal() {
System.out.println("father show final");
}
public void showCommon() {
System.out.println("father 普通方法");
}
}
public class Son extends Father {
public Son() {
super();
}
public Son(int age) {
this();
}
public static void showStatic(String str) {
System.out.println("son " + str);
}
private void showPrivate(String str) {
System.out.println("son private" + str);
}
public void show() {
showStatic("atguigu.com");
super.showStatic("good!");
showPrivate("hello!");
showFinal();
super.showCommon();
showCommon();
info();
MethodInterface in = null;
in.methodA();
}
public void info() {
}
public void display(Father f) {
f.showCommon();
}
public static void main(String[] args) {
Son so = new Son();
so.show();
}
}
interface MethodInterface {
void methodA();
}
关于 invokedynamic 指令
代码示例
@FunctionalInterface
interface Func {
public boolean func(String str);
}
public class Lambda {
public void lambda(Func func) {
return;
}
public static void main(String[] args) {
Lambda lambda = new Lambda();
Func func = s -> {
return true;
};
lambda.lambda(func);
lambda.lambda(s -> {
return true;
});
}
}
动态语言和静态语言
Java:String info = "mogu blog"; (Java是静态类型语言的,会先编译就进行类型检查)
JS:var name = "shkstart"; var name = 10; (运行时才进行检查)
方法重写的本质
Java 语言中方法重写的本质:
IllegalAccessError介绍
回看解析阶段
虚方法表
方法返回地址(return address)
方法退出的两种方式
当一个方法开始执行后,只有两种方式可以退出这个方法,
正常退出:
异常退出:
代码举例
public class ReturnAddressTest {
public boolean methodBoolean() {
return false;
}
public byte methodByte() {
return 0;
}
public short methodShort() {
return 0;
}
public char methodChar() {
return ‘a‘;
}
public int methodInt() {
return 0;
}
public long methodLong() {
return 0L;
}
public float methodFloat() {
return 0.0f;
}
public double methodDouble() {
return 0.0;
}
public String methodString() {
return null;
}
public Date methodDate() {
return null;
}
public void methodVoid() {
}
static {
int i = 10;
}
public void method2() {
methodVoid();
try {
method1();
} catch (IOException e) {
e.printStackTrace();
}
}
public void method1() throws IOException {
FileReader fis = new FileReader("atguigu.txt");
char[] cBuffer = new char[1024];
int len;
while ((len = fis.read(cBuffer)) != -1) {
String str = new String(cBuffer, 0, len);
System.out.println(str);
}
fis.close();
}
}
栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如:对程序调试提供支持的信息。
举例栈溢出的情况?(StackOverflowError)
通过 -Xss 设置栈的大小
调整栈大小,就能保证不出现溢出么?
不能保证不溢出
分配的栈内存越大越好么?
不是,一定时间内降低了OOM概率,但是会挤占其它的线程空间,因为整个虚拟机的内存空间是有限的
垃圾回收是否涉及到虚拟机栈?
不会
方法中定义的局部变量是否线程安全?
何为线程安全?
具体问题具体分析:
如果对象是在内部产生,并在内部消亡,没有返回到外部,那么它就是线程安全的,反之则是线程不安全的。
看代码
public class StringBuilderTest {
public static void method1(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
}
public static void method2(StringBuilder sBuilder){
sBuilder.append("a");
sBuilder.append("b");
}
public static StringBuilder method3(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1;
}
public static String method4(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1.toString();
}
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
new Thread(() -> {
s.append("a");
s.append("b");
}).start();
method2(s);
}
}
运行时数据区,哪些部分存在Error和GC?
运行时数据区是否存在Error是否存在GC程序计数器否否虚拟机栈是(SOF)否本地方法栈是否方法区是(OOM)是堆是(OOM)是
你只管学习,我来负责记笔记?? 关注公众号! ,更多笔记,等你来拿,谢谢
原文:https://www.cnblogs.com/spiritmark/p/13783933.html