java内存管理

1、什么是虚拟机,什么是java虚拟机

1.1虚拟机

定义: 模拟某种计算机体系结构,执行特定指令集的软件
分类 系统虚拟机(VMware,Virtual Box等), 进程虚拟机java

1)进程虚拟机

特色:并不会完整的模拟一个操做系统的运行环境,仅仅提供了特定指令集的运行环境
实例: JVM, Adobe Flash, FC模拟器算法

2)高级语言虚拟机

特色:把特定指令集范围进一步限定为高级语言。
属于进程虚拟机的一种,若有 JVM, .NET CLR, P-Code缓存

3)java语言虚拟机

可执行java语言的高级语言虚拟机。java语言虚拟机并不必定能够称为JVM, 例如:Apache Harmony数据结构

4)java虚拟机

  • 必须经过java TCK 的兼容性测试的java语言虚拟机才能称为java虚拟机
  • java虚拟机并不是必定要执行java程序,java虚拟机是和java编译后的class文件相关。
  • 业界三大商用JVM : Orcale HotSpot , Oracle JRockit VM , IBM J9 VM

1.2概念模型和具体实现

共有设计,私有实现
java虚拟机规范声明了概念模型,这些概念模型不约束虚拟机的具体实现,只要求虚拟机实现的效果在外部看起来和规范描述同样便可。
例如规范中规定了java堆中的对象所在的内存,须要虚拟机自动完成垃圾回收,这个实现过程能够不一样,可是效果须要相同。app

1.3java虚拟机运行时数据区

  • 在java虚拟机规范中定义了若干中程序运行期间会使用到的存储不一样类型的区域。
  • 有一些区域是全局共享的,随着虚拟机的启动而建立,随着虚拟机退出而销毁。有些区域是线程私有的,随着线程开始和结束而建立和销毁。
  • 是全部java虚拟机共同的内存区域概念模型

运行时数据区域的划分:

  • 程序计数器
  • java堆
  • java虚拟机栈
  • 本地方法栈
  • 方法区


其中 :
方法区和堆是全部线程共享数据区。
虚拟机栈、本地方法栈、程序计数器 是线程私有的。jvm

2、程序计数器区(pc)

  • 是全部java运行时内存区域最小的一块,
  • 做用: 能够看作是当前线程所执行的字节码的行号指示器。(能够看作是指针,指向当前运行的那行字节码代码)
  • 注意:若是正在执行一个java方法,则pc记录的是正在执行的虚拟机字节码指令的地址;若是正在执行 Native方法,则pc为空。
  • 此内存区域是惟一一个在java虚拟机规范中没有规定任何OutOfMemoryError状况的区域。

3、java虚拟机栈和本地方法栈

3.1 java虚拟机栈的概念和特征

  • 线程私有,生命周期和线程相同
    java虚拟机栈描述的是java方法执行时候的内存概念模型,每一个方法在执行时都会建立一个栈帧(用来建立这个方法的操做数栈,局部变量表,方法出口,动态链接等信息)。每个方法在调用和结束的过程,就对应了一个栈帧在虚拟机的入栈和出栈的过程。
  • 后进先出(LIFO)栈
    靠后执行的方法会先优先完成。
  • 存储栈帧,支撑java方法的调用、执行和退出
  • 可能会出现OutOfMemoryError异常和StackOverflowError异常
    线程请求的栈深度大于java虚拟机所容许的最大深度将会抛出 StackOverflowError异常。
    若java虚拟机被设计为可动态扩展,而动态扩展时又不能够申请到足够的内存时就会抛出OutOfMemoryError异常。

3.2 本地方法栈的概念和特征

  • 线程私有
  • 后进先出(LIFO)栈
  • 做用是支撑Native方法的调用、执行和退出
  • 可能会出现OutOfMemoryError异常和StackOverflowError异常
  • 一些虚拟机(如HotSpot)将java虚拟机栈和本地方法栈合并实现了

3.3 栈帧的概念和特征

  • java虚拟机栈中存储的内容,它被用于存储数据和部分过程结果的数据结构,同时也被用来处理动态连接、方法和异常分派
  • 一个完整的栈帧包含: 局部变量表、操做数栈、动态连接信息、方法正常完成和异常完成信息

在编译程序代码时,栈帧须要的局部变量表的大小和操做数栈的深度,在编译期间就已经彻底肯定了,java虚拟机会把这些数据彻底写在class文件的code表中。所以一个栈帧须要分配的内存大小不会受程序运行期间变量数据的影响,而仅仅取决于具体的java虚拟机。
在一个线程里面方法的调用链可能很长,不少方法可能都同时处于执行的状态,对于执行引擎而言,在活动线程之中,只有位于java虚拟机栈顶的栈帧才是有效的,这个栈帧被称为当前栈帧,与此栈帧相关联的方法被称为当前方法。虚拟机中全部执行的字节码指令都针对当前方法和当前栈帧操做。ide

局部变量表概念和特征

定义: 是一组变量值的存储空间,用于存储方法,参数和方法内部定义的局部变量等等。
在java编译器编译class文件时,就已经肯定了该方法须要的局部变量表的最大容量,局部变量表是以槽(Slot)为最小单位的。java虚拟机规范中没有明确一个slot的具体占用的内存大小,只是描述了任何slot能够储存的类型。性能

  • 由若干Slot组成,长度由编译期决定
  • 单个Slot能够存储一个类型为 boolean, byte, char, short, float, reference 和 returnAddress的数据,两个Slot能够存储一个类型为long或者double的数据。
    其中, reference类型表示一个对象实例的引用,经过reference能够间接或者直接查找到变量的实例数据,还能够经过reference直接或者间接的查找到这个对象的类型数据,而returnAddress 已经弃用。
  • 局部变量表用于方法间参数传递,以及方法执行过程当中存储基础数据类型的值和对象的引用。
    在方法执行中,若是正在调用的方法是一个实例方法,那这个方法的局部变量的第0号Slot将默认做为存储这个方法的实例引用,在方法之中能够经过关键字diss俩访问到这个隐含的参数。剩下的方法参数将会按照参数表的顺序排列,占用从1开始的局部变量表空间。在参数表分配完毕以后,在根据方法表中的局部变量定义顺序和他们的做用域来分配剩下的局部变量Slot,为了节省栈帧的空间,局部变量表中Slot是能够被重用的。

操做数栈的概念和特征

  • 单个操做数栈的元素,被称为Entry
  • 后进先出栈,由若干个Entry组成,操做数栈最大栈深度在编译期间决定
  • 单个Entry便可以存储一个java虚拟机中定义的任意数据类型的值,包括long和double类型,可是存储long和double类型的Entry深度为2,其余类型的深度为1。
  • 在方法执行过程当中,操做数栈用处存储计算参数和计算结果;在方法调用时,操做数栈也用来准备调用方法的参数以及退出时的返回结果。
    当一个方法刚刚开始执行的时候,这个方法的操做数栈是空的,在方法执行的过程当中会遇到各类字节码指令往操做栈中写入和提取内容就对应了操做数栈的出栈和入栈操做。

3.4 本地变量表和操做数栈实例

在cmd中输入测试

copy con Test.java

而后输入程序:ui

public class Test{
        public int calc(){
                int a =100;
                int b =200;
                int c =300;
                return (a+b)*c;
        }
}

而后编译

javac Test.java

再查看字节码:

javap -verbose Test.class

获得:

0: bipush        100               //把100入栈到操做数栈的栈顶
    2: istore_1                           //把操做数栈顶的元素出栈并把此元素存储在局部变量表中的1号Slot
    3: sipush        200
    6: istore_2
    7: sipush        300
    10: istore_3
    11: iload_1                           //将局变量表中的为1号的操做数入栈到操做数栈栈顶
    12: iload_2                        //将局变量表中的为2号的操做数入栈到操做数栈栈顶
    13: iadd                        //将操做数栈栈顶的两个元素出栈,而后相加入栈
    14: iload_3
    15: imul
    16: ireturn

3.5 内存异常实例

注意: 在 idea中能够经过设置选项中的run --> Edit configuration --> configuration --> VM options 设置java虚拟机栈的大小 添加: -Xss128k 便可。
StackOverflowError异常

public class JavaVMStackSOF {
    private int stackLength = 1;

    public void stackLeak(){
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try{
            oom.stackLeak();
        }catch (Throwable  e){
            System.out.println("length" + oom.stackLength);
            throw e;
        }
    }
}

OutOfMemoryError异常

public class JavaVMStackOOM {
    private void dontStop(){
        while(true){
        }
    }

    public void stackLeakThread(){
        while(true){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakThread();
    }
}

4、java 堆

4.1 java堆的概念

特征:

  • 全局共享
  • 一般是java虚拟机中最大的一块内存区域
  • 做用是做为java对象的主要存储区
  • JVMS明确要求该区域须要实现自动内存管理,即常说的GC,单并不限制采用哪一种算法和技术去实现
  • 可能出现OutOfMemoryError
    java堆的实现能够是固定大小的,也能够是动态扩展的,如今的主流虚拟机都是按照可扩展方式的来实现java堆的,java堆没有内存可用时就会抛出此 OOM 异常。

划分方式:

  1. TLAB: java堆中的对象时线程共享的,因此就会产生数据竞争,为了不这种竞争,java虚拟机极可能会将堆又根据各个线程来划分出若干个线程私有的内存缓冲区,这一类缓冲区被称为TLAB,即线程本地分配缓存。这时各个线程会在各自的TLAB中分配对象,仅在TLAB用完时才会加锁,并向java堆分配新的TLAB内存。 -Xmx512m(设置最大值) -Xms16m(设置最小值)
  2. 新生代,老年代,永久代,这是基于一种GC回收内存的算法来划分的。

4.2 栈和堆

从栈到堆的关联过程:

第二种实现方式:

对比:
第二种方式中,reference中存储的是稳定的句柄的地址,在对象被移动时(对象常常被移动,垃圾回收时会发生移动),只会改变句柄池中的到对象类型数据的指针,而reference不会改变。
第一种方式,速度更快,对比使用句柄的方式,节省了指针开销,reference直接指向了对象的实例数据。

4.3 java堆内存异常实战

可能发生的异常:

  • 若是实际所需的堆超过了自动内存管理系统能提供的最大容量,那java虚拟机将会抛出一个OOM异常。java堆是出现内存异常几率最大的区域。

出现OOM实例以下:
不断的建立对象,而且保证这些内存不被回收便可作到。

public class javaHeapOOM {
    static class OOMObject{

    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while(true){
            list.add(new OOMObject());
        }
    }
}

5、方法区和运行时常量池

5.1 方法区的概念

  • 全局共享
  • 做用是存储java类的结构信息
  • JVMS不要求该区域实现自动内存管理,可是基本都能实现自动管理该区域的内存
  • 可能出现OutOfMemoryError异常

注意:
类实例数据 和 类类型信息
实例数据是指在类中定义的各类实例对象以及它们的值,而类型信息是指定义在类代码中的常量,静态变量,类中声明的各类方法,字段等,还会包括即时编译器编译产生的数据。

5.2 运行时常量池的概念

  • 全局共享
  • 是方法区的一部分
  • 做用是存储java类文件常量池中的符号信息

5.3 HotSpot 方法区实现的变迁

永久代与方法区

  • 在JDK1.2~JDK1.6, HotSpot使用永久代实现方法区
  • 在JDK7开始,HotSpot开始了移除永久代的计划
    • 符号表被移到Native Heap中
    • 字符串常量和类的静态引用被移到Java Heap中
  • 在JDK8开始,永久代已被元空间(MetaSapce)所代替

5.4 方法区内存异常实战

永久代变迁过程致使的异常差别:
实例1:

public class RuntimeConstantPoolChange {
    public static void main(String[] args) {
        String str1 = new StringBuilder("hptj").append("zzj").toString();
        System.out.println(str1.intern() == str1);

        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }
}

intern方法:按期的维护了一个字符串池,若是字符串中有这个字符串,则会返回这个池中常量的地址,若是这个字符串中没有这个字符串所需的常量,则jvm会先把这个字符串加到字符串常量池中,而后再返回这个字符串的地址。

可知:java1.7开始后字符串常量池被移到了Heap中,
当采用jdk1.6运行时,则出现 false false ,由于 str1.intern返回的是常量池中的地址,而str1本生的地址是在heap中的是不可能相同的。
当采用jdk1.7运行时,则出现 true false , 由于 str1.intern和heap都是堆中的地址,则这个hptjzzj字符串在heap中没有出现过,则会加入到heap中的常量池中,并返回此地址和str1在堆中的地址一致,而java字符串在堆常量池中已经存在,当new以后会新建一个对象,因此是不相等的。

6、直接内存

6.1 直接内存的概念和特征

  • 并不是JVMS定义的标准java运行时内存区域
  • 随JDK1.4中加入的NIO被引入,目的是避免在java堆和Native堆中来回复制数据带来的性能损耗
  • 全局共享
  • 能被自动管理,可是检测手段上可能会有一些简陋
  • 可能出现OutOfMempryError异常
相关文章
相关标签/搜索