zoukankan      html  css  js  c++  java
  • JVM内存结构

    JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。
    JVM运行时数据区:
    1、方法区:类信息(类名,访问修饰符、字段描述、方法 描述等)、常量、静态变量、即时编译后的class文件等。在GC时用永久代来实现方法区
          
    2、运行时常量池:是方法区的一部分,存放编译期生成的各种字面量和符号引用(字面量就是实际的值,如1,"abc",符号引用是不知道实际引用对象的实际地址而抽象出的一种引用)。
      字面量如:文本字符串,声明为final的常量值;
      符号引用包括了三种常量,分别是:类和接口的全限定名,字段的名称和描述符,方法的名称和修饰符
    3、堆:存放对象的实例,数组内存在此分配(所有的对象实例和数组都在堆上分配),可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)
    4、栈:局部变量表(局部基本变量的值和引用类型在堆中的地址值,方法参数),方法的返回值等信息
     
    问题:字符串在JVM中如何存放?
      1、使用字符串初始化的字符串对象,它的值存放在字符串常量池中
      2、使用字符串构造方法创建的字符串对象,它的值存放在堆内存中
    java.lang.String.intern(),这个API可以手动将一个字符串对象的值转移到字符串常量池中
    在1.7及之前,字符串常量池是在永久代(方法区),大小是固定的,也不能被垃圾回收器回收,如果有太多了字符换调用了intern方法的话,就有可能造成OOM。
    1.8里,字符串常量池移到了堆内存中,可以被垃圾回收器回收降低了字符串常量池OOM的风险。
    String str1="hello";
    String str2="he"+new String("llo");
    System.out.println(str1==str2);//false

    1、两个或者以上的字符串常量相加,【String str="s1"+"s2"】,在预编译的时候“+”会被优化,相当于把两个或者两个以上字符串常量自动合成一个字符串常量

    2、字符串的+操作本质上是new了StringBuilder对象进行append操作,拼接后调用toString()返回String对象(可通过javap -c xxx.class查看字节码指令)

    复制代码
     Code:
           0: ldc           #10                 // String hello
           2: astore_1
           3: new           #11                 // class java/lang/StringBuilder
           6: dup
           7: invokespecial #12                 // Method java/lang/StringBuilder."<init>":()V
          10: ldc           #13                 // String he
          12: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          15: new           #15                 // class java/lang/String
          18: dup
          19: ldc           #16                 // String llo
          21: invokespecial #17                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
          24: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          27: invokevirtual #18                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    复制代码
     
     
    直接内存:Direct Memory
    Direct Memory容量可通过-XX:MaxDirectMemorySize指定,如果不指定则默认和Java堆的最大值-Xmx一样。
    不受到Java堆内存大小的限制,只会受到计算机总内存(包括RAM以及SWAP或者分页文件)大小以及处理器寻址空间的限制,若直接内存和JVM各个区域占用总内存大小超过物理内存限制则会出现OutOfMemoryError。
    NIO:JDK1.4引入的一种基于通道channel和缓冲区Buffer的IO方式;它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,
    避免了在Java堆和Native堆中来回复制数据,因此能在一些场景中显著提高性能。
     
     
    Java堆中对象分配,布局和访问过程
    1、对象创建(普通Java对象,不包括数组和Class对象)
      虚拟机遇到一条new指令时,首先去常量池中检查这个指令的参数能否定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析和初始化过,如果没有先执行类的初始化过程。
       在类加载检查通过后,对象所需的内存的大小在类加载完成后便可完全确定,接下来虚拟机将为新生对象分配内存,即把一块确定大小的内存从堆中划分出来。
      (若堆中内存是规整的,即用过的和空闲的各放在一边,指针指向分界点,内存分配即指针向空闲移动对象所需的内存大小。这时内存分配采用指针碰撞(bump the pointer)的方式
      若堆中的内存是不规整的,虚拟机需维护一张列表记录哪些内存块是可用的,在分配的时候找到一块足够大的内存块划分给对象,并更新列表,这种方式称为空闲列表(free list))
      因此使用哪种方式取决于堆内存是否规整,是否规整又取决于采用的垃圾收集器是否带有压缩整理功能决定。因此采用Serial、ParNew采用的是指针碰撞,采用CMS收集器使用的是空闲列表。
     
      给对象分配内存需要考虑线程安全的问题,需要同步进行,避免两个对象分配一块内存;解决这个问题有两种方案:
      1、对内存分配空间的动作进行同步处理-实际上虚拟机采用CAS配上失败重试的方式保证更新的原子性
      2、把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),线程先在TLAB上分配,
      只有TLAB用完分配新的TLAB时才需要同步锁定。
      内存分配完成后JVM将分配到的内存空间都初始化为0值,然后进行必要的设置,类的元数据信息,对象的哈希码,对象的GC分代年龄都存在对象头中,最后进行init初始化。
     
     2、对象的内存布局
      在HotSpot 虚拟机中,对象在内存中的存储被分成了3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
      对象头分成两部分:
        1、用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
        2、类型指针,即对象指向它的类元数据的指针。JVM通过这个指针来确定这个对象是哪个类的实例。如果是数组对象,对象头中还需记录数组的长度
      实例数据:对象真正存储的有效信息,即是代码中定义的字段内容,包括从父类继承下来的和自身定义的
      对齐填充:并不是必然存在的,仅仅起着占位符的作用。JVM要求对象的大小必须是8字节的整数倍。
     
    3、对象的访问定位
      建立对象是为了使用对象,Java程序通过栈上的reference数据来操作堆上的具体对象。reference只是指向对象的一个引用,如何访问定位对象在堆上的具体位置呢?
      1、句柄:Java堆中划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据和类型数据各自的具体地址信息。
        好处是稳定:如垃圾收集时若对象被移动,只需修改句柄中的实例数据指针,reference无需修改。
      2、直接指针:reference中存放的直接就是对象地址。好处是速度快,节省了一次指针定位的开销,HotSpot就是通过这种方式访问的。
     
    4、Java对象模型
     
     
      线程共享:堆,方法区(常量池)
      线程私有:栈,程序计数器,本地方法栈
  • 相关阅读:
    博客开启
    .NET 异常
    .NET 深入研究
    算法研究
    数据库相关
    非比较排序算法———桶排序(箱子排序)
    非比较排序算法———计数排序
    NHibernate深入学习
    数据结构与算法
    结对编程1 四则运算生成器的改进(201421123060 61 40)
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/12515524.html
Copyright © 2011-2022 走看看