java虚拟机随手笔记(1)内存分配

来源:互联网 发布:程序员开发工具网 编辑:程序博客网 时间:2024/06/07 04:00

java内存运行时数据区域及其含义


1.程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。  


2.与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。 虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame[1])用于存储局部变量表、 操作数栈、 动态链接、 方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型booleanbytecharshortintfloatlongdouble)、 对象引用reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。 局部变量表所需要的内存空间在编译期间完成。如果线程请求的栈深度大于虚拟机所允许的最大深度,那么将抛出StackOberFlowError异常,如果虚拟机在拓展栈是无法申请到足够的内存空间,则抛出OutOfMemoryError异常。


3.本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。 

4.堆:在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 同时也是垃圾收集器管理的主要区域,又被称为GC堆(Garbage Collected Heap)

5.方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据。

6.运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

java对象的创建基本过程

遇到new--》检查常量池,判断类是否加载--》已加载,则在堆中分配内存--》初始化分配给对象的堆内存空间('0')--》初始化对象头(Mark  Word,类型指针,数组长度)--》init方法(构造函数)

java对象的内存布局

1.对象内存布局:对象头(Mark Word,类型指针--指向类元数据以确定这个类是哪个类的实例,数组长度),实例数据、对齐填充

对象的访问定位

首先根据栈上的reference数据来访问堆,有两种方式,一种是句柄,一种是指直接指针,如下图。



OutOfMemoryError异常

堆内存溢出:由于不断的创建内存而没有回收,导致堆内存不够使用而溢出,可以用一个while循环不断的往一个list里面添加对象来导致这个异常发生。
虚拟机栈和本地方法栈溢出--StackOverflowError和OutOfMemoryError:
StackOverflowError:可以使用无法结束的递归;
OutOfMemoryError:可以用while循环不断的创建线程来导致异常发生
方法区和运行时常量池溢出:最简单的方法就是使用String字符串的String.intern()方法。这个方法的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。使用while循环不断的网list里面添加不同的String(jdk1.6之前)。 

intern的在jdk1.6和1.7的区别对比

 JDK1.6中的intern:   

调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个String值的引用;如果不存在的话,则会将原先堆中的该字符串拷贝一份到常量池中。  一定注意是拷贝!

JDK1.7中的intern:   

调用intern方法的时候首先会去常量池中查看是否存在与当前String值相同的值,如果存在的话,则直接返回常量池中这个String值的引用;如果不存在的话,则只会将原先堆中该字符串的引用放置在常量池中,并不会将拷贝整个字符串到常量池中。   一定注意是引用!

这也就说明,JDK1.6和JDK1.7对于常量池中不存在此字符串的情况处理不同。

实例:

public static void main(String[] args) {   String str = "str"+new String("01");①   str.intern();②   String str1 = "str01";③   System.out.println(str == str1);       String str2 = new String("str01");④   str2.intern();⑤   String str3 = "str01";⑥   System.out.println(str2 == str3);       String str4 = "str01";⑦   String str5 = new String("str")+new String("01");⑧   str5.intern();⑨   System.out.println(str4 == str5);}


在JDK1.6下输出结果是:

false
false
false

解释:

①执行时会在堆内存创建一个值为"str01"的字符串对象str,同时在常量池创建一个"str"以及"01"常量;

②执行时会首先去常量池中查看是否存在一个值为"str01"的常量,发现不存在,JDK1.6的做法就是将该字符串"str01"在常量池中也生成一份;

③执行时会在常量池中创建一个"str01"对象,发现已经存在,因而不会新建;
第一个输出false的原因是:str指向的是堆内存的"str01",而str1指向的是常量池中的"str01";

④执行时会在堆内存创建一个值为"str01"的字符串对象str2,同时在常量池中创建一个值为"str01"的常量;

⑤执行时会首先去常量池中查看是否存在值为"str01"的常量,发现存在,则直接返回这个常量引用;

⑥执行时会在常量池中创建一个值为"str01"的常量,如果发现已经存在,则不会创建;

第二个输出false的原因是:str2指向的是堆内存的"str01",而str3指向的是常量池中的"str01";

⑦执行时会在常量池创建一个值为"str01"的常量;

⑧执行时会在堆内存创建一个值为"str01"的字符串对象str5,同时在常量池创建一个"str"以及"01"常量;

⑨执行时会去常量池查看是否存在值为"str01"的常量,发现存在则直接返回这个常量引用;
第三个输出false的原因是:str5指向的是堆内存的"str01",而str4指向的是常量池中的"str01";

在JDK1.7下输出结果是:

true
false
false

解释:

发现只有第一个输出结果不一样,所以我们只解释第一个的原因:

①执行时会在堆内存创建一个值为"str01"的字符串对象str,同时在常量池创建一个"str"以及"01"常量;(这点和JDK1.6没什么区别)

②执行时会首先去常量池中查看是否存在一个值为"str01"的常量,发现不存在,JDK1.7的做法就是将堆内存中"str01"的引用复制到了常量池中;

③执行时会在常量池中创建一个"str01"对象,发现已经存在,因而不会新建;
那么此时的str和str1都将指向的是堆内存中的"str01"的值,所以两者相等;



0 0
原创粉丝点击