JVM各个区域对应到类中解释。以及jdk8和jdk8之前 成员变量、静态变量、局部变量变化。final修饰变量的细节

JVM各个区域对应到类中解释。以及jdk8和jdk8之前 成员变量、静态变量、局部变量变化。final修饰变量的细节_第1张图片

jvm栈、本地方法栈、程序计数器为线程隔离的数据区,方法区、堆为线程共有

 

 

java虚拟机栈

  • 对于虚拟机栈是线程私有的,,它主要由 局部变量表 、操作数栈、动态连接、方法出口等信息。每个方法对应着一个栈帧在虚拟机中从入栈到出栈的过程。
  • 局部变量表存放了编译器可知的各种java虚拟机基本数据类型(boolean ,byte char,short int float long double)、对象引用(reference),对于对象引用,(比如新建一个对象,将其赋值给局部变量,局部变量存于栈帧的局部变量表 中,而对象存于堆中,则从这个变量指向这个对象的指针存于局部变量表中,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和 指向字节码指令的地址。对于这些类型在局部变量表中存储空间一变量槽表示,局部表在编译期间可以确认大小。
  • 何时抛stackOverflowError?答:当线程请求站深度大于虚拟机允许的深度抛出(hotspot的jvm是不动态扩展栈容量)

本地方法栈

  • 简言就是为本地方法服务的栈hotspot将其与虚拟机栈合二为一。

java堆

  • 线程共享的,几乎存储的是对象的是对象的实例和数组。也是GC垃圾回收的重点区域
  • 因为主流垃圾回收期基于分代收集理论所以将堆分为

             新生代、老年代、永久区、eden空间、From Servivor和to Servivor空间。它并不是java虚拟机规范细分的,而是由于垃圾收集器设计风格共同特写的叫法。

  • 如果栈没有内存完成实例分配并且无法扩展就会抛出 outOfmemoryError(OOM)
  • 通过 -Xmx最大堆大小 -Xms 初始堆大小 -Xmn年轻代大小
  • jdk8以后常量池以及静态成员变量以及全局变量(成员变量)放入到堆中
  • jdk8前实例变量随着new创建对象存放在堆中,静态变量又叫类变量,只会随着类的加载被存放到方法区中的静态区中!

方法区

  • 方法区是JVM的规范,永久代(元空间)是方法区的具体实现
  • 是线程共享,主要存储被虚拟机加载类型信息、常量、静态变量、即时编译器编译的缓存等。
  • jdk7将字符串常量池移到堆中,运行时常量池还在永久代中
  • jdk8后使用元空间完全替换掉了永久代,元空间存储类信息,使用物理内存而永久代使用虚拟机内存,这样做的目的是防止永久代内存溢出。另一部分被划到了堆中,将原来用永久代存储的成员变量(静态和非静态)、常量、常量池放入堆中。
  • 方法区又被称为静态区,是线程共享的区域。用于存储虚拟机加载的类信息、常量、静态变量。运行时常量池是方法区的一部分。
  • 对于HotSpot来说,Class对象比较特殊,它虽然是对象,但是存储在方法区内。

常量池

https://cloud.tencent.com/developer/article/1450501

  • 是方法区的一部分,Class文件中除了有类版本字段方法、接口描述信息外还有常量池。
  • 运行时常量池存放在方法区中(不是运行时产生的新的变量,例如 String.intern()才会放入运行常量)
  • JVM常量池主要分为Class文件常量池、运行时常量池,全局字符串常量池,以及基本类型包装类对象常量池。
  • 用于存放编译器生成的各种字面量(private String a="www",www则为字面量)与符号引用,还保存符号引用翻译来的直接引用也保存。

   引用

  •   符号引用:在二进制数据中,包含了堆其他类/接口/类方法的符号引用,他由方法的全名和相关描述符组成。
  •   直接引用:将符号引用在解析阶段替换为指针,这个指针指向另外类/方法的内存位置。这个指针就是直接引用

 

直接内存

在jdk4后引入Nio基于通道和缓冲区的I/O方式,直接虚拟机与物理内存直接交易,减少java堆和native堆之间来回赋值。提高性能。

  • 一个线程中方法的调用链可能会很长,很多方法都同时处于执行状态。对于JVM执行引擎来说,在在活动线程中,只有位于JVM虚拟机栈栈顶的元素才是有效的,即称为当前栈帧,与这个栈帧相关连的方法称为当前方法,定义这个方法的类叫做当前类。
  • 执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。
  • 调用新的方法时,新的栈帧也会随之创建。并且随着程序控制权转移到新方法,新的栈帧成为了当前栈帧。方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧(返回给方法调用者),随后虚拟机将会丢弃此栈帧。

对象和引用

    对象

在HotSpot虚拟机中,对象在内存中的布局分为3部分:对象头、实例数据、对齐填充。

1. 对象头:分为两部分。①第一部分存储对象的运行时数据,如哈希码、GC分代年龄、状态标志。②类型指针,即对象指向他自己的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。

2.实例数据:存储对象的真正的有效信息,也就是程序代码中所定义的字段内容。

3.对齐填充:HotSpot虚拟机中规定,对象头的起始地址必须是8的倍数,当实例数据长度不齐的时候需要对齐填充来补全。

   引用

创建引用的时候可以指定关联的引用队列,当GC释放对象内存的时候,会将引用加入到引用队列中。引用队列中的对象在内存被回收之前会采取一些机制,类似于与后置通知。

  • 强引用。new出来的,只要引用在,其对象就不会被回收。
  • 软引用。内存不够时才进行回收。比如浏览器中的后退功能,打开新页面时,把旧页面的引用置为软引用。
  • 弱引用。每次GC时都会被回收。
  • 虚引用。get方法总是返回null。GC时不会被立马清除,会被放入引用队列,做一些后置工作。且必须和引用队列一起使用。

访问对象的方式

通过句柄访问对象

 

JVM各个区域对应到类中解释。以及jdk8和jdk8之前 成员变量、静态变量、局部变量变化。final修饰变量的细节_第2张图片

优点:reference存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要改变。

缺点:增加了一次指针定位的时间开销。

通过直接指针访问对象(HotSpot使用的方式)

JVM各个区域对应到类中解释。以及jdk8和jdk8之前 成员变量、静态变量、局部变量变化。final修饰变量的细节_第3张图片

 

优点:节省了一次指针定位的开销。

缺点:在对象被移动时reference本身需要被修改。

final 修饰的成员变量

赋值问题

  • 对于方法内的局部变量,final修饰的只能赋值一次
  • 实例变量可以在构造方法中赋值,并且只能赋值一次
  • 对于静态常量,可以直接赋值,或者静态代码块赋值
public class FinalDemo {
    void doSomething() {
        // 没有在声明的同时赋值
        final int e;
        // 只能赋值一次
        e = 100;
        System.out.print(e);
        // 声明的同时赋值
        final int f = 200;
    }
    // 实例常量
    final int a = 5; // 直接赋值
    final int b; // 空白final变量
    // 静态常量
    final static int c = 12;// 直接赋值
    final static int d; // 空白final变量
    // 静态代码块
    static {
        // 初始化静态变量
        d = 32;
    }
    // 构造方法
    FinalDemo() {
        // 初始化实例变量
        b = 3;
        // 第二次赋值,会发生编译错误
        // b = 4;
    }
}

对于线程共享问题

  • java基本类型的变量使用final修饰,会直接在复制一份到调用类的栈中,保证线程安全并提升速度。引用类型则需要使用GETSTATIC获取

 

JVM各个区域对应到类中解释。以及jdk8和jdk8之前 成员变量、静态变量、局部变量变化。final修饰变量的细节_第4张图片

 

 

你可能感兴趣的:(jvm,java)