首页 > 深入理解Java虚拟机 > 正文

[总结]-第二章 Java内存区域与内存溢出异常

佛若2018-09-101人围观

[总结]-第二章 Java内存区域与内存溢出异常

一、知识点

1、虚拟机运行时数据区

  • 方法区:运行时常量池(JDK1.7被移出)
  • 堆:存放对象实例或数组新生代和老年代逃逸分析
  • 虚拟机栈:线程私有
  • 本地方法栈:线程私有Native
  • 程序计数器:线程私有行号指示器无OOM

字节码解释器工作时就是通过改变程序计数器的值来选取下一条需要执行的字节码指令。

2、Java堆的内存分配方式

  • 指针碰撞(内存规整)
  • 空闲列表(内存不规整)

选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

3、对象的内存布局

  • 对象头
    • 对象自身运行时数据(Mark Word) - 如果32bit
      • 哈希码(25bit)[未锁定时]
      • GC分代年龄(4bit)[未锁定时]
      • 锁状态标志位(2bit)
        • 01 未锁定 [未锁定时]
        • 01 偏向锁 [锁定时]
        • 00 轻量级锁 [锁定时]
        • 10 重量级锁 [锁定时]
        • 11 GC标记
      • 固定为0(1bit)
    • 类型指针:对象指向它的类元数据的指针,虚拟机通过这个指针判断此对象是哪个类的实例。
  • 实例数据:程序代码中定义的各种类型的字段内容。
  • 对齐填充:仅仅是占位符。

4、对象的访问定位

  • 使用句柄 -> 句柄池

    • reference中存储的就是对象的句柄地址
    • 好处:reference中存储的是稳定的句柄地址,在对象被移动(GC时移动)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
  • 直接指针

    • reference中存储的直接就是对象地址
    • 好处:速度更快,节省一次指针定位的时间开销。

二、名词解释

  1. JIT编译器:Just in time compiler 即时编译器;
  2. 字面量:文本字符串,声明为final的常量池;
  3. 符号引用:类和接口的全限定名(包名+类名)、字段的名称和描述符、方法的名称和描述符;
  4. 偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价;
  5. CAS:Compare And Swap 比较交换;
  6. 逃逸分析:开启逃逸分析,直接栈上分配;

三、常见问题:

1、程序计数器为什么不会内存溢出?

因为程序计数器中存储的数据所占空间的大小不会随着程序的执行而发生改变。

2、intern()的使用?

JDK1.6在常量池创建与此String内容相同的字符串;常量池 创建字符串->返回引用
JDK1.7在常量池中记录Heap中首次出现的引用,并返回该引用。常量池 记录引用->返回引用

  1. String str1 = new StringBuilder("计算机").append("软件").toString();
  2. System.out.println((str1.intern() == str1));
  3. //JDK1.6:false
  4. //JDK1.7:true

3、String s = new String(“你好”);创建了几个对象?

  • 直接使用双引号声明出来的String对象会直接存储在常量池
  • 使用new,JVM会在中创建一个内容相同的String对象,然后返回堆中String对象的引用。

4、内存分配怎么解决线程安全问题?

  • 对分配内存空间的动作进行同步处理-采用CAS配上失败重试的方式保证更新操作的原子性;
  • 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)

5、CAS是什么?

CAS(Compare And Swap 比较交换),是无锁执行者,可以用来保证线程执行的安全性。核心思想如下:

执行函数:CAS(V,E,N)

其包含3个参数

  • V 表示要更新的变量
  • E 表示预期值
  • N 表示新值

如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,此时当前线程不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。

CAS是一条CPU的原子指令,完成某个功能的一个过程的原语的执行必须是连续的,不会造成所谓的数据不一致问题。

6、字节与byte与bit的关系

  1. 1 字节 = 1 byte = 8 bit

7、如何避免堆自动扩展?

  1. 将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展。

8、常见OOM有哪些?分别怎么分析定位?

  1. java.lang.OutOfMemoryError: Java heap space

堆内存溢出(最常见的),可以Dump堆转储快照信息查看具体问题。

  1. java.lang.StackOverflowError

虚拟机栈内存溢出 - 超出当前方法栈深度

  1. java.lang.OutOfMemoryError: unable to create new native thread

多线程导致内存溢出,可通过“减少最大堆”和“减少栈容量”来换取更多的线程。

  1. java.lang.OutOfMemoryError: PermGen space

出现这个异常说明运行时常量池属于方法区的一部分,可以手动设置MaxPermSize大小修改。
jdk1.7已经将常量池从方法区移出,jdk1.8完全删除永久代,所有jdk1.8不会出现此OOM。

  1. java.lang.OutOfMemoryError

由DirectMemory(本机直接内存)导致的OOM,没有明显的异常说明,因为这里的内存不足时它本身计算出来,并手动抛出异常。

四、常见命令

  1. -XX:+HeapDumpOnOutOfMemoryError

可以让虚拟机在出现OOM时Dump出当前的内存堆转储快照以便事后进行分析

  1. -Server -XX:+DoEscapeAnalysis

开启逃逸分析,直接栈上分配

  1. -XX:MaxDirectMemorySize

指定本机直接内存容量,默认为java堆最大值-Xmx