• 《深入理解Java虚拟机》读书笔记一


    第二章 Java内存区域与内存溢出异常

    1、运行时数据区域

    程序计数器:

    • 当前线程所执行的字节码的行号指示器,用于存放下一条需要运行的指令。
    • 运行速度最快位于处理器内部。
    • 线程私有。

    虚拟机栈:

    • 描述的是Java方法执行的内存模型,用于存放对象的引用和基本数据类型。
    • 每个方法执行的时候都会创建一个栈帧(stack frame)用于存放 局部变量表、操作栈、动态链接、方法出口。
    • 线程私有,生命周期与线程相同。

    方法栈:

    • 和虚拟机栈功能类似,管理本地的方法调用。
    • 虚拟机栈为虚拟机执行的Java方法的方法服务,方法栈则为虚拟机使用的本地方法的服务。

    堆:

    • 虚拟机最大的一块区域,虚拟机启动的时候创建。
    • 用于存放对象的实例,所有对象实例和数据的在堆上分配内存。
    • 线程共享的区域。

    方法区:

    • 用于存放一些类信息,常量,静态变量和即时编译后的代码等数据。
    • 线程共享的区域。

    运行时常量池:

    • 方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

    直接内存:

    • 不属于运行时数据区,堆外内存。

    2、HotSpot虚拟机对象探秘

    对象的创建:

    • 接受new关键字指令后,检查指令参数是否能在常量池中定位到一个类的符号引用
    • 然后检查符号引用对应的类是否已被加载、解析和初始化。如果没有就执行。
    • 类加载通过后,虚拟机为新生的对象分配内存。
    • 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。
    • 虚拟机设置对象头信息。

    内存分配的方式:

    • 指正碰撞,内存是规整的,所有用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器。
    • 空闲列表,内存不规整,虚拟机维护一个列表用来记录那些内存是可用的,分配时从中找到足够的内存划分给对象,并更新表记录。

    保证线程安全的方式:

    • CAS,对分配的内存空间进行同步处理,采用CAS配上失败重试的方式保证操作的原子性。
    • 线程分配缓冲区,把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配好内存。

    对象的内存布局:

    • 对象在内存中存储的布局可以分为三块区域对象头、实例数据和对齐填充。
    • 对象头,又可以分为两部分,第一部分存储自身运行时数据,第二部分是类型指针
    • 自身运行数据主要包括哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳。
    • 类型指针即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
    • 实例数据,对象真正存储的有用的有效信息,继承父类的字段也会包含。
    • 对齐填充,对象起始地址必须是8的整数倍,对齐填充起到了占位符的作用。

    对象的访问定位:

    • 使用句柄的访问方式,堆中将划分出来一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址。reference中存储的是稳定的句柄地址,如果对象被移动,reference的地址无需改变。
    • 使用直接指针访问,reference中存储的直接就是对象的地址。直接指针访问可以减少指针定位的时间开销。

    3、OOM异常

    • Java堆溢出,Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Root到对象之间有可达路径来避免垃圾回收,那么对象数量到达最大堆的容量限制后就会产生内存溢出异常。
      package com.ecut.exception;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * -Xms20m -Xmx20m  -XX:+HeapDumpOnOutOfMemoryError
       */
      public class HeapOOM {
      
          static class OOMObject{
      
          }
          public static void main(String[] args) {
              List<OOMObject> list = new ArrayList<>();
              while (true){
                  list.add(new OOMObject());
              }
          }
      }

      运行结果如下:

      java.lang.OutOfMemoryError: Java heap space
      Dumping heap to java_pid6776.hprof ...
      Heap dump file created [28247587 bytes in 0.149 secs]
      Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
          at java.util.Arrays.copyOf(Arrays.java:3210)
          at java.util.Arrays.copyOf(Arrays.java:3181)
          at java.util.ArrayList.grow(ArrayList.java:261)
          at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
          at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
          at java.util.ArrayList.add(ArrayList.java:458)
          at com.ecut.exception.HeapOOM.main(HeapOOM.java:17)
    • 虚拟机栈和本地方法栈溢出,如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,如果虚拟机在拓展栈时无法申请到足够的内存空间则抛出OutOfMemoryError异常。
      package com.ecut.exception;
      
      /**
       * -Xss128k
       */
      public class JavaVMStackSOF {
      
          private int stackLength = 1;
      
          public void stackLeak(){
              stackLength++;
              stackLeak();
          }
      
          public static void main(String[] args) {
              JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
              try {
                  javaVMStackSOF.stackLeak();
              }catch (Exception e){
                  System.out.println("stack length :" + javaVMStackSOF.stackLength);
                  throw e;
      
              }
          }
      }

      运行结果如下:

      Exception in thread "main" java.lang.StackOverflowError
          at com.ecut.exception.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)

      创建线程导致内存溢出异常:

      package com.ecut.exception;
      
      /**
       * -Xss2M
       */
      public class JavaVMStackOOM {
      
          private  void dontStop(){
              while(true){
      
              }
          }
      
          public void stackLeakByThread(){
              while (true){
                  Thread thread = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          dontStop();
                      }
                  });
                  thread.start();
              }
          }
      
          public static void main(String[] args) {
              JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
              javaVMStackOOM.stackLeakByThread();
          }
      }
    • 方法区和运行时常量池溢出
      package com.ecut.exception;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * -XX:PermSize=10M -XX:MaxPermSize=10M  JDK 1.6 会抛出OOM异常,JDK1.7开始了去永久代
       */
      public class RuntimeConstantPoolOOM {
          public static void main(String[] args) {
              //使用list保持着产量池的引用,避免fullGC回收常量池的行为
              List<String> list  = new ArrayList<>();
              int i = 0 ;
              while(true){
                  list.add(String.valueOf(i++));
              }
          }
      }

      String.intern()方法如果字符串常量池中已经包含了一个等于String对象的字符串,则返回代表池中这个字符串的String对象。否则将此对象包含的字符串添加到常量池中。

      package com.ecut.exception;
      
      public class RuntimeConstantPool {
          public static void main(String[] args) {
              /*jdk1.6 intern方法会把首次遇到的字符串实例复制到永久代中,返回的也是这个永久代中的这个字符串实例的引用。
              StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用
              jdk1.7中intern实现只是在常量池中记录首次出现的实例引用,因此intern返回的引用和StringBuilder创建的那个字符
              串实例时同一个*/
              String s1 = new StringBuilder("计算机").append("软件").toString();
              System.out.println(s1.intern() == s1);
          }
      }

      运行结果:

      true
    • 本机直接内存溢出
      package com.ecut.exception;
      
      import sun.misc.Unsafe;
      
      import java.lang.reflect.Field;
      
      /**
       *  -Xmx20M -XX:MaxDirectMemorySize = 10M
       */
      public class DirectMemoryOOM {
          private  static final int _1MB = 1024*1024;
      
          public static void main(String[] args) throws IllegalAccessException {
              Field unsafeField = Unsafe.class.getDeclaredFields()[0];
              unsafeField.setAccessible(true);
              Unsafe unsafe = (Unsafe) unsafeField.get(null);
              while (true){
                  unsafe.allocateMemory(_1MB);
              }
          }
      }

      运行结果如下:

      Exception in thread "main" java.lang.OutOfMemoryError
          at sun.misc.Unsafe.allocateMemory(Native Method)
          at com.ecut.exception.DirectMemoryOOM.main(DirectMemoryOOM.java:18)

    源码地址:

    https://github.com/SaberZheng/jvm-test

    转载请于明显处标明出处:

    https://www.cnblogs.com/AmyZheng/p/10504443.html

  • 相关阅读:
    五个知识体系之-Linux常用命令学习
    测试职业生涯中,五个知识体系
    英语:真正有效的英语学习心得,把英语当母语学习!(转载)
    侧滑面板(对viewGroup的自定义)
    安卓程序员要拿到5000和1w的薪资,分别需要掌握哪些技术?
    轻巧级记事本的开发
    web.xml 中的listener、 filter、servlet 加载顺序及其详解
    如何向android studio中导入第三方类库
    【NPR】卡通渲染
    线程池原理及其实现
  • 原文地址:https://www.cnblogs.com/AmyZheng/p/10504443.html
Copyright © 2020-2023  润新知