Java内存模型

1. 内存简介

        

         在程序执行过程中,需要不断的将逻辑地址和物理地址进行映射。作为操作系统架构,Java面临着于其他程序完全相同的内存限制,受限于操作系统架构提供的可寻址地址空间。操作系统架构提供的可寻址地址空间由处理器的位数决定。

       32位处理器:2^32的可寻址范围、4GB

       64位处理器:2^64的可寻址范围。

 

   地址空间的划分:

      1.内核空间:连接计算机硬件,调度程序,提供联网、虚拟内存等

      2 用户空间:Java程序实际运行时使用的内存空间。

2 JVM内存模型--JDK8

    程序计数器(Program Counter Register):是一块较小的内存空间

              1. 当前线程所执行的字节码行号指示器,是一个逻辑计算器,

             2.通过改变计数器的值来选取下一条需要执行的字节码指令。(分支、循环、跳转、异常处理、线程恢复等)       

            3.  和线程是一对一的关系,即“线程私有“,因为在确定时刻,一个处理器只会执行一个线程。

            4.对Java方法计数(计算器记录的时正在执行的JVM字节码指令的地址),如果Native方法则计数器值为Undefined.

           5 不会发生内存泄露

   Java虚拟机栈(Stack)

          1. java 方法执行的内存模型、

         2. 每个Java方法被执行时,都会创建一个栈帧,即方法运行期间的基础数据结构。因此java虚拟机栈包含许多栈帧,栈帧用于存储局部变量表、操作数栈、动态链接、返回地址等,Java方法调用结束后,栈帧才会被销毁。

                                                  

          局部变量表和操作数栈

                  局部变量表:包含方法执行过程中的所有的变量。

                 操作数栈:入栈、出栈、复制、交换、产生消费变量(在执行字节码指令过程中被用到)

              例:c为 局部变量

         

          通过Javac指令进行编译,得到,class 文件

       

      通过javap 指令对 ,class 文件进行反编译,得到jvm 机器码指令

    

     执行add(1,2)如下图:

       

     如图:  局部变量的大小(深度)为3,操作数栈的深度为2,一个长方形代表一个栈帧,总7个栈帧。JVM栈会按照程序计数器从大到小依次把栈帧压入,执行时,根据栈的性质,就会从小到大依次执行。执行时先执行iconst_0,就是将int 0压入操作数栈中,同时入参是1和2,因此这里的局部变量表就是1和2;下一步就执行iconst_2执行,就是讲操作数栈的0 pop出,存入到局部变量表的第二个变量中;下一步,iload_0, 就是将第0个局部变量的数1  ,压入栈中。iload_1, 就是将第1个局部变量的数2  ,压入栈中。iadd 就是将栈中的元素pop出,执行运算相加,在将得到的结果压入栈。istore_2 将栈顶的元素3 弹出,存入到局部变量中,替换掉0。iload2再次把第二个元素3 压入操作数栈中,最后调用ireture,将栈顶元素返回。所有的栈帧被销毁。

   问题:递归为什么会引发Java.lang.StackOverflowErrow异常。

      当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压入虚拟机栈中,当方法执行完毕时,便会将栈帧出栈,因此可知线程当前执行的方法所对应的栈帧必定位于Java栈的顶部,而我们的递归函数不断的调用自身,每一次方法调用会涉及一下三点:第一,每次调用一个方法就会生成一个栈帧。第二,保存当前方法的栈帧状态,将它放在虚拟机栈中。第三,栈帧上下文切换时,会切换到最新的方法栈帧中。 但是我们每个线程对的虚拟机栈的深度是固定的,递归实现将导致栈深度的增加,每次递归都会往栈里压一个栈帧,如果超过了线程的虚拟机栈的最大深度,就会引发Java.lang.StackOverflowErrow异常。

      总结: 递归过深,栈帧数超过虚拟栈深度。

  虚拟机栈过多会引发Java.lang.OutOfMenoryError异常(当虚拟机栈可以动态扩展时,如果无法申请足够多的内存,就会异常)

 

本地方法栈  

     与虚拟机栈相似,主要作用于标注了native的方法。

 

 元空间(MetaSpace)与永久代(PermGen)的区别:

   在JDK8之后,开始把类的源数据放在本地堆内存中的一块区域,这块区域就叫做MetaSpace,该区域在JDK7之前属于永久代,元空间和永久代都是用来存贮class的相关信息,包括class对象的方法和属性。元空间和永久代都是方法区的实现,在Java7之后,原来位于方法区中的字符串常量池,已经被移动到Java堆中,并且在JDK8以后,使用了元空间替代了永久代。)

    1. 元空间使用本地内存,永久代使用的是JVM的内存。使用本地内存最直接的好处就是、

             java,lang,OutOfMemoryError:PermGen  Space 异常将不复存在。因为默认类的源数据的分配只受本地内存的限制。

 

MetaSpace相比PermGen的优势:

       1 字符串常量池存在永久代中,容易出现性能问题的内存溢出。

       2  类和方法的信息大小难以确定,给永久代的大小指定带来了困难。

       3 永久代会为GC带来不必要的复杂性。 

       4 方便HotSpot与其他JVM 如Jrockit的集成。

 

Java 堆(Heap):被所有线程共享的内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存

       1. 对象实例的分配区域

        (图  32位处理器的java进程的内存布局)

           

    2  垃圾回收(GC)管理的主要区域

       问题: JVM三大性能调优参数 -Xms   -Xmx  -Xss的含义?

             1 . -Xss: 规定了每个线程虚拟机栈(堆栈)的大小,一般256K,此配置会影响此进程中并发线程数的大小。

            2. -Xms : 堆的初始值,表示初始化的java堆的大小,即该进程刚创建出来时专属java堆的大小。一旦对象容量超过java堆的初始容量,java堆就会自动扩容,扩容至Xmx

           3. -Xmx:堆能达到的最大值。

      问题:java内存中堆和栈的区别--内存分配策略:

        java程序运行时,有三种内存分配策略:静态的、栈式的、堆式的

        静态存储:编译时确定每个数据目标在运行时的存储空间需求。这种分配策略要求程序代码中不允许有可变数据结构的存在,也不允许有嵌套、递归的出现。

       栈式存储:数据区需求在编译时未知,运行时模块入口前确定。该分配可成为动态存储分配,是由一个类似有堆栈的运行栈来实现的,和静态存储的分配方式相反。

      堆式存储:专门针对在编译时或者运行时模块入口都无法确定存储空间需求的,动态分配。例如:可变长度串,对象实例。堆有大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。

 

问题: java内存中堆和栈的区别与联系?

     1. 联系引用对象、数组时,栈里定义变量保存堆中目标的首地址。(创建好的数组、对象实例都会保存在堆中,想要引用这些数组和对象实例,可以在栈中定义一个特殊的变量,让这个变量的取值等于数据、对象实例在堆内存中的首地址,栈中的变量就是引用变量。引用变量是普通的变量,定义时在栈中分配,在程序运行到其作用域之外就会被释放掉。而数组和对象本身在堆中分配,即使程序运行在其作用域之外,数组和对象本身占据的内存不会被释放,他们在没有引用变量指向的时候,才会变成垃圾。)

   

    2  区别:

        1. 管理方式:栈自动释放,堆需要GC   (JVM本身可以对内存栈管理操作,该内存空间的释放,编译器就可以操作。而堆空间,JVM执行引擎不会对其操作,会让垃圾回收机制,对其进行管理)

        2.空间大小:栈比堆小

       3 碎片相关:栈产生的碎片远小于堆。

       4 分配方式:栈支持静态和动态分配,堆仅支持动态分配。

       5 效率:栈的效率比堆高。(因为内存块本身的排列就是一个堆栈结构,并且计算机底层内存空间本身就是使用的是最基础的堆栈结构,因此栈的效率会高很多)

  

   问题:元空间、堆、线程独占部分空间的联系--内存角度

      元空间: Class :HelloWorld --method   :sayhello\setName\main  --Field:name

                      Class: System

                   (class对象的信息,还有System类对象,和System类中的成员变量和方法)

     java堆:  Object:String(“test”) (String实例test)

                     Object:helloWorld  (对象实例)

    线程独享:  Parameter   reference:"test" to String Object   (Sring类型的引用参数)

                        Variable   reference:"hw" to Hello World  object     (地址引用Hw,保存是堆中helloworld的地址值)

                        Loacl Variables: a with 1, lineNo   (局部变量a  值为1,系统自带的行号 ,用来记录代码的执行)

                 程序执行时,main线程会分配对应的虚拟机栈、本地栈、程序计数器

 

   问题: 不同JDK版本之间intern()方法的区别-----JDK6  与JDK6+

          

     JDK6:当调用intern 方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中该字符串的引用,否则,将此字符串对象添加到字符串常量池中,并且返回该字符串对象的引用。 

   JDK6+:当调用intern方法时,如果字符串常量池先前已经创建出该字符串对象,则返回池中该字符串的引用。否则,如果该字符串对象已经存在于java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。

           

    

   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值