架构社区我爱编程

深入理解java虚拟机(一):Java 内存区域与内存溢出异常

2018-04-04  本文已影响178人  susu2016

参考博客
http://blog.csdn.net/dongyuxu342719/article/details/78809049

一、运行时数据区域

1、线程隔离的数据区

Notice:

  1. HotPot 的实现合并了本地方法栈和虚拟机栈

2、由线程共享的数据区

Notice:

  1. 为什么GC的主要区域是Heap:动态创建的对象都在Heap上为其实例分配内存。Stack在方法和局部代码调用完成后释放帧栈空间,但Heap生命周期与虚拟机相同,内存不能自动释放。C/C++中程序员往往在代码中显示调用 free/delete 释放堆中对象的空间,操作繁琐,且容易发生内存泄漏。java 虚拟机的自动垃圾收集机制则能自动释放不需要用到的对象实例,实现堆内存的自动管理。
  1. 已加载类的基本信息和方法储存在方法区
  2. 常量(final)和静态变量(static)储存在方法区。问题:局部变量声明final,储存在哪个区域(堆、方法区、虚拟机栈)。
  3. Object 和 Array 的实例在堆中分配内存,并在相应的位置(如栈)创建引用。没有有效引用将被GC。
  1. HotPot 的实现把方法区合并到了堆的永久代区(Permanent Generation)。
  2. 如何实现方法区是虚拟机的技术实现细节,但是使用永久代实现方法区现在看来并不是一个好主意,因为这样更容易遇到内存泄漏问题。JDK 1.7 的 HotPot 中,已经把原本放在永久代的字符串常量池移出。

二、HotPot 虚拟机对象探秘

1、对象的内存布局

  1. 对象头(Header)
    1. (Mark Word)对象自身的运行时数据:如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
    2. 类型指针:对象指向元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。数组还会记录数组的长度。
  2. 实例数据(Instance Data)
    1.各种成员变量,包括父类继承的和子类定义的。
  3. 对齐填充(Padding)
    1. HotPot VM 要求对象起始地址必须是8字节的整数倍
    2. 对象实例数据没有对齐时,用对齐填充补全。

2、对象的访问定位

通过句柄访问对象 通过直接指针访问对象

三、堆栈溢出异常

1、对应参数:

2、Java 堆溢出

3、虚拟机栈和本地方法栈溢出

4、方法区和运行时常量池溢出

5、本机直接内存溢出

四、Intern() 方法

不必太纠结于 intern() 方法,这个例子只是告诉我们,虚拟机底层的不同实现,会影响某些代码的结果。高版本的虚拟机对低版本做了一些优化,效率更高。

public class RunTimeConstantPool {
    public static void main(String [] args){
        String str1=new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern()==str1);
        String str2=new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern()==str2);
    }
}

这段代码在JDK1.6中运行,会得到两个false,而在JDK1.7及以后的版本运行,会得到一个true和一个false。 产生差异的原因是:在JDK1.6中intern()会把首次出现的字符串复制到永久代中,返回的也是永久代中这个字符串实例的引用,而由StringBuilder创建的字符串实例在堆中,所以必然不是同一个引用,将返回false。而在JDK1.7中的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和StringBuilder创建的那个字符串是同一个。对str2比较返回false是因为”java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中指向的是第一次出现的引用,所以和str2不相同。

JDK 1.6:intern()

image

JDK 1.7:intern()

image
上一篇下一篇

猜你喜欢

热点阅读