内存池与JVM内存模型

基础

在这里插入图片描述

class文件
  • 硬盘上的.class文件
class content
  • 类加载器将硬盘上的.class文件读入内存中的那一块内存区域
Class对象

在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。

每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。

Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:

加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。

链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。

初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。

在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。
有三种获得Class对象的方式:

  1. Class.forName(“类的全限定名”)
  2. 实例对象.getClass()
  3. 类名.class (类字面常量)
对象

对类的实例化 Test_22 obj = new Test_22();

JVM内存模型

方法区

介绍
《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。所以,方法区看作是一块独立于Java堆的内存空间。

  • 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
  • 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
  • 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutofMemoryError:PermGen
    space (8前)或者 java.lang.OutofMemoryError:Metaspace(8以及以后)
  • 关闭JVM就会释放这个区域的内存。
  • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。

设置方法区内存大小

  • 元数据区大小可以使用参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定,替代上述原有的两个参数。
  • 默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即没有限制。
  • 与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常OutOfMemoryError:Metaspace
  • -XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的XX:MetaspaceSize值为21MB。这就是初始的高水位线,一旦触及这个水位线,Full
    GC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活)然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。
  • 如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full
    GC多次调用。为了避免频繁地GC,建议将-XX:MetaspaceSize设置为一个相对较高的值。

方法区所存储的内容
1、类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM在方法区中存储以下类型信息:

  • 这个类型的完整有效名称(全名=包名.类名)
  • 这个类型直接父类的完整有效名(对于interface或是java.lang.object,都没有父类)
  • 这个类型的修饰符(public,abstract,final的某个子集)
  • 这个类型直接接口的一个有序列表

2、域信息
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected, static, final, volatile, transient的某个子集)

3、方法信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:

  1. 方法名称
  2. 方法的返回类型(或void)
  3. 方法参数的数量和类型(按顺序)
  4. 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的一个子集)
  5. 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
  6. 异常表(abstract和native方法除外):每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

4、静态变量

  • non-final的类变量
    static静态变量:加载时准备阶段(赋默认值)、初始化阶段赋给定值
  • 全局常量
    ​ static final:编译时(准备阶段)赋给定值

5、运行时常量池

常量池
在这里插入图片描述

  • 一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是常量池表(Constant Pool
    Table),包括各种字面量和对类型、域和方法的符号引用。
  • 一个java源文件中的类、接口,编译后产生一个字节码文件。而Java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池

运行时常量池

  • 运行时常量池(Runtime Constant Pool)是方法区的一部分。
  • 常量池表(Constant Pool Table)是class文件的一部分。
  • 运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池。
  • JVM为每个已加载的类型(类或接口)都维护一个常量池。
  • 运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。
  • 运行时常量池,相对于class文件常量池的另一重要特征是:具备动态性。
  • 当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛OutOfMemoryError异常。
public class Test extends HashMap implements Serializable {
    private String name = "";
    private int x = 1;
    public Test(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        Test haha = new Test(null);
        int nameLength = haha.getNameLength();
        System.out.println(nameLength);
    }

    public int getNameLength() {
        int y = 0;
        try {
            y = name.length();
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
            e.printStackTrace();
        }
        return y;
    }
} 
Classfile /D:/ideaFiles/Algorithm/out/production/Algorithm/com/lx/mySort/Test.class
  Last modified 2020-8-12; size 1145 bytes
  MD5 checksum 8f9825153f3fa6f2042785c0df59703b
  Compiled from "Test.java"
//类信息
public class com.lx.mySort.Test extends java.util.HashMap implements java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #15.#44        // java/util/HashMap."<init>":()V
   #2 = String             #45            //
 ...
{
//域信息
  private java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

  private int x;
    descriptor: I
    flags: ACC_PRIVATE

//方法信息
  ...
  public int getNameLength();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: aload_0
         3: getfield      #3                  // Field name:Ljava/lang/String;
         6: invokevirtual #10                 // Method java/lang/String.length:()I
         9: istore_1
        10: goto          26
        13: astore_2
        14: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: ldc           #12                 // String 空指针异常
        19: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: aload_2
        23: invokevirtual #14                 // Method java/lang/NullPointerException.printStackTrace:()V
        26: iload_1
        27: ireturn
//异常表
      Exception table:
         from    to  target type
             2    10    13   Class java/lang/NullPointerException
      LineNumberTable:
        line 26: 0
        line 28: 2
        line 32: 10
        line 29: 13
        line 30: 14
        line 31: 22
        line 33: 26
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           14      12     2     e   Ljava/lang/NullPointerException;
            0      28     0  this   Lcom/lx/mySort/Test;
            2      26     1     y   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 13
          locals = [ class com/lx/mySort/Test, int ]
          stack = [ class java/lang/NullPointerException ]
        frame_type = 12 /* same */
}
SourceFile: "Test.java"

 
 

演进过程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

虚拟机栈
  1. Java虚拟机栈也是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)
  2. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;当前大部分JVM都可以动态扩展,只不过JVM规范也允许固定长度的虚拟机栈)
  3. Java虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧。

栈针

  • 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的java虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程

局部变量表

  • 局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。并且在Java编译为Class文件时,就已经确定了该方法所需要分配的局部变量表的最大容量。
  • 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)「String是引用类型」,对象引用(reference类型) 和 returnAddress类型(它指向了一条字节码指令的地址)
  • 注意:
      很多人说:基本数据和对象引用存储在栈中。
      当然这种说法虽然是正确的,但是很不严谨,只能说这种说法针对的是局部变量。
      局部变量存储在局部变量表中,随着线程而生,线程而灭。并且线程间数据不共享。
      但是,如果是成员变量,或者定义在方法外对象的引用,它们存储在堆中。
      因为在堆中,是线程共享数据的,并且栈帧里的命名就已经清楚的划分了界限 : 局部变量表!
      
    操作数栈
  • 操作数栈也常被称为操作栈,它是一个后入先出(Last In First Out,LIFO)
    栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到Code属性的max_stacks数据项之中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为l,64位数据类型所占的栈容量为2在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。
  • 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
  • 另外,在概念模型中,两个栈帧作为虚拟机栈的元素,相互之间是完全独立的。但是大多数虚拟机的实现里都会做一些优化处理,令两个栈帧出现一部分重登。让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样在进行方法调用时就可以共用一部分数据,而无须进行额外的参数复制传递了。

动态连接 直接地址

  • 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存有大最的符号引用,字节码中的方法调用指令就以常最池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。

返回地址 回复现场

  • 当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者,是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为正常完成出口(Normal
    Method Invocation Completion)。

  • 另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt
    Method Invocation Completion) 。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。

  • 无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。

  • 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。

在这里插入图片描述
JVM运行main方法,内部是如何运行的
1、创建运行main方法需要的栈帧
2、将main方法的操作数栈指针赋值给线程的属性:操作数栈
3、将main方法的局部表指针赋值给给线程的属性:局部表指针
VM运行add方法,内部是怎么做的
1、创建add的方法的栈帧
2、在add方法的栈帧中保存main方法的字节码的下一行程序计数器(18)
3、线程的局部表开始指针(main的)保存至add方法的栈帧
4、线程的操作数栈开始指针(main的)保存至add方法的栈帧
5、将add方法的局部表指针赋值给线程的局部表指针
6、将add方法的操作数栈指针赋值给线程的操作数栈指针

程序计数器

在这里插入图片描述

堆区

在这里插入图片描述

  • JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old
    Generation),非堆内存就一个永久代(Permanent Generation)。
  • 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
  • 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
  • 非堆内存用途:(实现)永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。

在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
元空间有注意有两个参数:

MetaspaceSize :初始化元空间大小,控制发生GC阈值
MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存

说到对象的内存分配,大的层面来说,就是在堆中给对象分配内存,不过也并不是所有的对象和数组都在堆上分配内存空间,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。
逃逸分析:
目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
栈上分配:
栈上分配就是把⽅法中的变量和对象分配到栈上,⽅法执⾏完后⾃动销毁,⽽不需要垃圾回收的介⼊,从⽽提⾼系统性能

-XX:+DoEscapeAnalysis开启逃逸分析(jdk1.8默认开启,其它版本未测试)
-XX:-DoEscapeAnalysis 关闭逃逸分析

大对象直接分配到老年代
大对象是指需要大量连续内存空间的对象,比如很大的字符串或者数组。

主要的原因就是如果放到新生代的话,假如这个对象存活时间挺久的,那每一次MinorGC就会存在对象在Eden区及两个Survivor区之间发生大量的内存复制,造成大量的性能损耗。而且造成新生代可用空间严重不足,然后频繁GC,也会造成性能损耗。
虚拟机提供了一个参数,令大于这个设置值的对象直接在老年代分配。

-XX:PretenureSizeThreshold

注意: 这个参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不认识这个参数,Parallel Scavenge 收集器一般并不需要设置,如果遇到必须使用这个参数的场合,可以考虑ParNew加CMS的收集器的组合。

长期存活的对象将进入老年代
虚拟机采用分代的收集思想来管理内存,每次对象回收时就必须能识别哪些对象应放在新生代,哪些对象应该放在老年代。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1 。对象在Survivor区中每经过一次MinorGC,年龄就加1岁,当年龄达到15岁(默认值),就会被晋升到老年代中。
对象晋升老年代的年龄阈值,可以通过参数设置。

-XX:MaxTenuringThreshold

在这里插入图片描述
动态年龄判断
为了能更好的适应不同程序的内存状况,虚拟机并不是永远地要求兑现过的年龄必须达到了MaxTenuringThreshold才能晋升老年代。

除了对象的年龄达到了MaxTenuringThreshold(默认15)能晋升老年代。还有一个条件:

如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

举个小栗子,如对象年龄5的占34%,年龄6的占36%,年龄7的占30%,按照对象年龄进行区别,对象是不能进入老年代的,但Survivor都已经100%了啊?

大家可以关注这个参数TargetSurvivorRatio,目标存活率,默认为50%。大致意思就是说年龄从小到大累加,如加入某个年龄段(如栗子中的年龄6)后,总占用超过Survivor空间TargetSurvivorRatio的时候,从该年龄段开始及大于的年龄对象就要进入老年代(即栗子中的年龄6,7对象)。动态对象年龄判断,主要是被TargetSurvivorRatio这个参数来控制。而且算的是年龄从小到大的累加和,而不是某个年龄段对象的大小。
在这里插入图片描述
老年代分配担保
新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。

  • 空间分配担保
    在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间
    如果大于,则此次Minor GC是安全的
    如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
    如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
    上面提到了Minor GC依然会有风险,是因为新生代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。
    取平均值仍然是一种概率性的事件,如果某次Minor GC后存活对象陡增,远高于平均值的话,必然导致担保失败,如果出现了分配担保失败,就只能在失败后重新发起一次Full GC。虽然存在发生这种情况的概率,但大部分时候都是能够成功分配担保的,这样就避免了过于频繁执行Full GC。

JVM的分代年龄为什么是15?
在 32 位的 HotSpot 虚拟机中,如果对象处于未被锁定的状态下,那么 Mark Word 的 32bit 空间中的 25bit 用于存储对象哈希码,4bit 用于存储对象分代年龄,2bit 用于存储锁标志位,1bit 固定为 0
在这里插入图片描述

本地方法栈
  • 本地方法栈(Native Method Stacks)与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行
    Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native
    方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
  • Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给
    Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会创建一个栈帧并压入
    Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java
    虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的 native 方法。
  • 本地方法栈是一个后入先出(Last In First Out)栈。
  • 由于是线程私有的,生命周期随着线程,线程启动而产生,线程结束而消亡。
  • 本地方法栈会抛出 StackOverflowError 和 OutOfMemoryError 异常。

this指针

在这里插入图片描述

    new #2 <com/zy/four/jvm/Test>
  • 在堆区生成了一个对象(不完全对象,未执行构造方法)InstanceOopDesc
  • 将不完全对象的指针压入栈
    dup
  • 复制栈顶元素
  • 压入栈
    invokespecial
  • 执行invokespecial字节码指令,完成运行方法的环境构建,弹出栈顶元素,this指针赋值(index为0数据)
  • 执行构造方法(此时方法为完全体)

堆、栈、方法去的交互关系

在这里插入图片描述

虚拟机栈与方法区关系

动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接

虚拟机栈与堆区关系

局部变量

方法区与堆区关系

方法区->堆区:静态引用类型的属性
堆区->方法区:klass pointer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值