搬运 JavaYouth

ClassLoader
类加载子系统:负责从文件系统或者网络中加载class文件。(只负责加载,至于能否运行取决于Execution Engine)
加载类的信息存放于方法区的内存空间。(方法区里面还有运行时常量池信息,)

虚拟机栈

简介

内存中的栈与堆

  1. 首先栈是运行时的单位,而堆是存储的单位。
  2. 即:栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放,放哪里

    Java虚拟机栈是什么?

    Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用,栈是线程私有的。 ```java public class StackTest {

    public static void main(String[] args) {

    1. StackTest test = new StackTest();
    2. test.methodA();

    }

    public void methodA() {

    1. int i = 10;
    2. int j = 20;
    3. methodB();

    }

    public void methodB(){

    1. int k = 30;
    2. int m = 40;

    } }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2909312/1650949919903-c8550a29-04b6-4090-b822-ca442d97aa38.png#clientId=uee783e0c-3cc0-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uc98fea4f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=650&originWidth=978&originalType=url&ratio=1&rotation=0&showTitle=false&size=133482&status=done&style=none&taskId=ub1537131-4bd4-448a-a992-572376334de&title=)
  2. - 虚拟机栈的生命周期
  3. - 生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了
  4. - 虚拟机栈的作用
  5. - 主管Java程序的运行,它保存方法的局部变量(8 种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。
  6. - 局部变量,它是相比于成员变量来说的(或属性)
  7. - 基本数据类型变量 VS 引用类型变量(类、数组、接口)
  8. <a name="L0a2p"></a>
  9. ### 虚拟机栈的特点
  10. - 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
  11. - JVM直接对Java栈的操作只有两个:
  12. - 每个方法执行,伴随着**进栈**(入栈、压栈)
  13. - 执行结束后的**出栈**工作
  14. - 对于栈来说不存在垃圾回收问题
  15. - 栈不需要GC,但是可能存在OOM
  16. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2909312/1650950038056-37173b17-ba02-45e0-adc3-d322bc25b0d8.png#clientId=uee783e0c-3cc0-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u434f9484&margin=%5Bobject%20Object%5D&name=image.png&originHeight=547&originWidth=275&originalType=url&ratio=1&rotation=0&showTitle=false&size=161516&status=done&style=none&taskId=ubcdb5678-32dd-4219-9c29-bfcb3cd5cbb&title=)
  17. <a name="wNGti"></a>
  18. ### 虚拟机栈的异常
  19. **面试题:栈中可能出现的异常?**
  20. - Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
  21. - 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个**StackoverflowError** 异常。
  22. - 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 **OutofMemoryError** 异常。
  23. <a name="xf0YO"></a>
  24. ### 设置栈内存大小
  25. > 我们可以使用参数 **-Xss** 选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。

-Xss1m -Xss1024k -Xss1048576

  1. <a name="Tm4sh"></a>
  2. #### 举例
  3. ```java
  4. public class StackErrorTest {
  5. private static int count = 1;
  6. public static void main(String[] args) {
  7. System.out.println(count);
  8. count++;
  9. main(args);
  10. }
  11. }

没设置参数前
部分输出结果:

  1. 11404
  2. 11405
  3. 11406
  4. Exception in thread "main" java.lang.StackOverflowError
  5. at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)

说明栈在11406这个深度溢出了
设置栈参数之后
image.png
部分输出结果

  1. 2474
  2. 2475
  3. 2476
  4. Exception in thread "main" java.lang.StackOverflowError
  5. at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)

栈的存储单位

栈中存储什么?

  1. 每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在
  2. 在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)。
  3. 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。

    栈运行原理

  4. JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循先进后出(后进先出)原则

  5. 在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的。这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)
  6. 执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
  7. 如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。

image.png

  1. 不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。
  2. 如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。
  3. Java方法有两种返回函数的方式。
    • 一种是正常的函数返回,使用return指令。
    • 另一种是方法执行中出现未捕获处理的异常,以抛出异常的方式结束。
    • 但不管使用哪种方式,都会导致栈帧被弹出。

      栈帧的内部结构

      每个栈帧中存储着:
  • 局部变量表(Local Variables)
  • 操作数栈(Operand Stack)(或表达式栈)
  • 动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
  • 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
  • 一些附加信息

image.png
并行每个线程下的栈都是私有的,因此每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要由局部变量表 和 操作数栈决定的
image.png

局部变量表

认识局部变量表

概念

  1. 局部变量表也被称之为局部变量数组或本地变量表
  2. 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress返回值类型。
  3. 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
  4. 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
  5. 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。
    • 对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。
    • 进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。
  6. 局部变量表中的变量只在当前方法调用中有效。

    • 在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。
    • 当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

      举例

      1. public class LocalVariablesTest {
      2. private int count = 0;
      3. public static void main(String[] args) {
      4. LocalVariablesTest test = new LocalVariablesTest();
      5. int num = 10;
      6. test.test1();
      7. }
      8. //练习:
      9. public static void testStatic(){
      10. LocalVariablesTest test = new LocalVariablesTest();
      11. Date date = new Date();
      12. int count = 10;
      13. System.out.println(count);
      14. //因为this变量不存在于当前方法的局部变量表中!!
      15. // System.out.println(this.count);
      16. }
      17. //关于Slot的使用的理解
      18. public LocalVariablesTest(){
      19. this.count = 1;
      20. }
      21. public void test1() {
      22. Date date = new Date();
      23. String name1 = "atguigu.com";
      24. test2(date, name1);
      25. System.out.println(date + name1);
      26. }
      27. public String test2(Date dateP, String name2) {
      28. dateP = null;
      29. name2 = "songhongkang";
      30. double weight = 130.5;//占据两个slot
      31. char gender = '男';
      32. return dateP + name2;
      33. }
      34. public void test3() {
      35. this.count++;
      36. }
      37. public void test4() {
      38. int a = 0;
      39. {
      40. int b = 0;
      41. b = a + 1;
      42. }
      43. //变量c使用之前已经销毁的变量b占据的slot的位置
      44. int c = a + 1;
      45. }
      46. }