深入理解JAVA虚拟机《一》

Javan内存区域与内存溢出异常

一、运行时数据区域

1.程序计数器(线程私有)

  • 字节码解释器工作时就是通过改变这个计数器的值来选择下一条需要执行的字节码指令。
  • 线程执行main方法:计数器记录正在执行的虚拟机字节码指令地址。
  • Native方法:计数器值为空。

2.Java虚拟机栈(线程私有)

  • 描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等。

3.本地方法栈(线程私有)

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

4.Java堆(线程共享)

  • 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都要在这分配内存管理。
  • Java堆是垃圾收集器管理的主要区域,也称为GC堆。
  • 从内存回收的角度来看,由于现在收集器基本都采用分代收集算法。
  • Java堆中细分:新生代和老生代,再细致一点:Eden空间,From Survivor空间,To Survivor空间等。

5.方法区(线程共享)

  • 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

6 运行时常量池

  • 运行时常量池是方法区的一部分。
  • 运行时常量池相对Class文件常量池的另外一个重要特征是具备动态性。Java语言并不要求常量一定只有编译器才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得最多得比较多的便是String类的intern()方法。

7.直接内存

  • 直接内存不属于虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是会导致OOM异常出现。
  • 在JDK 1.4中新加入了NIO(New
    Input/Output)类,引入了一种基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。提高了性能,避免在Java堆和Native堆中来回赋值数据。
  • 本机直接内存的分配不受到Java堆大小的限制,但是会受到本机总内存大小以及处理器寻址空间的限制。

Java虚拟机运行时数据区

二、虚拟机对象

  1. 对象的创建

    虚拟机在遇到一个new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

  2. 对象的内存布局

    在虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头、实例数据和对齐填充。

    虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。对象的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

三、OOM异常

  • Java堆溢出

    Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Root到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

  • 虚拟机栈和本地方法栈溢出

    关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了这两种异常:

    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出Stack OverflowError异常。
    • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

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

String.intern()是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

public static void main(String[] args) {
	// TODO Auto-generated method stub

	//	首次出现,记录在常量池中,intern()返回了它的引用和StringBuilder创建的字符串案例是同一个。
	String str1 = new StringBuilder("计算机").append("软件").toString();
	System.out.println(str1.intern() == str1);	//	true
	
	//	字符串常量池中已经有 java 的引用了,不符合首次出现原则。
	String str2 = new StringBuilder("ja").append("va").toString();
	System.out.println(str2.intern() == str2);	//	false
	
}

未完善,待更新…

发布了46 篇原创文章 · 获赞 61 · 访问量 3269

猜你喜欢

转载自blog.csdn.net/hyx1249273846/article/details/103174617