JVM学习笔记之 内存模型

  • 2019-07-31
  • 63
  • 0

在java中,所使用的内存都是由java虚拟机托管的,对象的创建和销毁都不需要我们手动去分配内存和释放内存。相比c c++不太容易出现内存泄漏内存溢出之类的问题。那么,本章节主要讲讲java虚拟机如何实现对内存管理。

观察下上图,简单划分了java程序中内存的各个功能区域。

运行时数据区域

java程序运行时,jvm会向系统申请一定的内存用于运行程序,虚拟机把这些内存划分成了诺干个不同的数据区域。这些区域有不同的功能和生命周期。

程序计数器
它是一块很小的内存空间,可看成当前线程所执行的字节码行号的指示器,表示当前执行到第几行代码了。每个线程都有单独的指示器。

虚拟机栈
线程私有,生命周期与线程相同。虚拟机栈描述了java执行方法的内存模型,每个方法被调用时,会生成一个对应的栈帧,栈帧中存入了方法中定义布局变量,操作数栈,动态链接,方法出口等信息。。一个方法从调用到执行完成,对应了一个栈帧从入栈到出栈的过程。我们常说的堆内存和栈内存中的栈内存指的就是虚拟机栈中的局部变量表。
单个线程内能创建栈帧得数量是有限的, 如果出现了死循环调用方法,则会抛出StackOverflowError异常

private static void add(int i) {
        System.out.println(i);
        add(++i);
    }

//控制台输出
。。。。
11406
11407
11408
Exception in thread "main" java.lang.StackOverflowError
    at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
    at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)

本地方法栈
与虚拟机栈类似,虚拟机栈是为执行的java方法服务的,本地方法栈是为native方法服务的,native方法指的是非java语言编写的程序或模块。
栈帧中的局部表量表存储了方法的入参和方法内定义的布局变量,非静态方法的的局部表单表第一个位置存放了this对象的引用地址
动态链接 简单的讲就是方法内调用的其他方法的地址,比如方法内创建了一个接口实例,每次调用这个方法可能都是不同的实现,那么这个接口方法的地址也不是固定的
方法返回地址 一个方法执行的时候有2种推出方法方式,一个是return 一个是出现了未处理的异常 无论哪种方式退出,都必须返回方法被调用的位置,一般情况下,调用者的pc计数器的值可以作为返回地址,那么那么栈帧中就可能保存了这个计数器值。方法的推出->当前栈帧出栈,恢复2上层方法的局部表量表和操作数栈,把返回值压入上层方法的操作数栈,pc计数器的值指向方法调用指令的后一条指令、
方法调用也属于操作数。
java堆
一般而言,java堆占用了大部分的内存空间,线程共享,用于存放对象实例和数组,成员变量,静态成员变量(java1.8),interned Strings

Metaspace

运行时常量池

存在于java堆,存放编译时产生的各种字面量和符号引用

方法区
线程共享,存放了被虚拟机加载的类信息 常量 静态变量 即使编译器编辑后的代码等数据,用于和java堆区分。

虚拟机栈会存放基本类型的变量数据和对象的引用
全局变量(全局静态变量)是放在方法区中。成员变量如果没有实例化那么变量是放在栈中;实例化了对象放在堆中,栈中放的是指向堆中对象的引用地址。局部变量放在栈中,new的对象放在堆中,


对象的内存数据结构

如图,一个对象实例数据分成了三部分,对象头存储了实例本身的一些属性信息,比如哈希值,GC分代年龄,锁状态标志,线程持有的锁,类型指针等信息,类型指针指向了类元数据的指针,通过它来确定这个对象是哪个类的实例。如果对象是一个数组,则还需要一块区域记录数组的长度。实例数据存放着类实例以及它的父类的数据,第三部分对其填充并不是一定存在,本身也没什么特殊的作用,只不过hotspot虚拟机要求对象的起始地址必须是8字节的整倍数。如果对象的数据凑巧不是8的整倍数则需要对齐填充。

如何访问一个对象
如下的一行代码

Student stu = new Stuent();

stu这个局部变量会存在于栈帧的局部表链表里,student的实例存放于java堆,stu它的值是reference类型,指向了java堆中实例的内存地址。JVM规范中只规定了reference类型的数据是指向了对象的引用,但没规定用何种方式去定位和访问对象。所以对象的访问方式取决于具体虚拟机内的实现。目前主流的实现方式有句柄和直接指针两种方式。
如果是句柄,则java堆中会维护一个句柄池,reference类型的数据 访问的就是句柄地址,句柄中包含了对象实例和类型数据各自的具体地址信息。
如果是直接指针,则存放的是对象实例的内存地址。对象实例的数据结构则需要考虑如何存放访问类型数据的相关信息。




class Ca {
public static String a ="a";
public final String b ="b";
public String c ="c";
 public void d(){
   String e = "";
   String a1 ="a1";
   String a2 = "a1";
   String a3 = "a"+"1";
   String tmp ="a";
   String a4 = tmp+"1";
 }
}

Ca ca = new Ca();
ca.d();

如上代码 请问ca a b c e变量实例分别保存到内存模型的哪个区域
方法d执行的时候 a1 a2 a3 a4变量 使用双等于判断返回true还是false
怎么操作才能使a1==a4
字符串的不可变性的深层次原理
java堆溢出

E:\sources>java   -Xms20m -Xmx200m  -Xss128k  application
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
        at application.outOfHeap(outOfMemoryError.java:17)
        at application.print(outOfMemoryError.java:10)
        at application.main(outOfMemoryError.java:6)
感谢打赏!
微信
支付宝

评论

还没有任何评论,你来说两句吧