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

package JVM;

import java.util.ArrayList;
import java.util.List;

public class test1 {
    static class OOMObject{}
    
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        
        while(true) {
            list.add(new OOMObject());
        }
    }
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid17800.hprof ...
Heap dump file created [13098631 bytes in 0.028 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Unknown Source)
	at java.util.Arrays.copyOf(Unknown Source)
	at java.util.ArrayList.grow(Unknown Source)
	at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
	at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
	at java.util.ArrayList.add(Unknown Source)
	at JVM.test1.main(test1.java:13)

  

package JVM;

import java.util.ArrayList;
import java.util.List;
/*
 * VM Args: -Xss128k
 */

public class test1 {
    private int stackLength = 1;
    
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    
    
    public static void main(String[] args) {
        test1 oom = new test1();
        try {
            oom.stackLeak();
        }catch(Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}
stack length:990
Exception in thread "main" java.lang.StackOverflowError
	at JVM.test1.stackLeak(test1.java:13)
	at JVM.test1.stackLeak(test1.java:14)
	at JVM.test1.stackLeak(test1.java:14)
	at JVM.test1.stackLeak(test1.java:14)
	at JVM.test1.stackLeak(test1.java:14)
	at JVM.test1.stackLeak(test1.java:14)
        ...

 

1、Java为什么可以跨平台?

Java源代码(.java)经过javac编译之后变成一种中间码(Java字节码(.class))。中间码不能被计算机系统直接运行,它不依赖于特定的计算机硬件架构。在不同的硬件系统安装有不同的Java虚拟机(JVM),JVM可以把字节码“翻译”成对应硬件平台能够执行的代码。所以对于Java程序员来说,不需要考虑硬件平台具体是什么。因为有了Java虚拟机,Java代码可以跨平台运行。

注意:java代码不是直接运行在CPU上的,而是运行在java虚拟机(简称JVM)上的。这里简单解释一下虚拟机的概念: 通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。实际上就是通过软件模拟完整的硬件环境。

 

2、Java虚拟机内存的各个区域

 

一、程序计数器

1.1 作用:用来指示执行哪条指令,可以看成是当前线程所执行的字节码的行号指示器字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

1.2 特点:

  • 线程私有:由于JVM多线程是通过线程轮流切换并分配处理器执行时间的方法来实现,因此在任何一个确定的时刻,一个处理器(多核处理器的一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,每条线程之间的计数器互不影响,独立存储。
  • 无内存溢出:如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器值为Undefined。程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM(Out Of Memory Error)情况的区域。
  • 这里补充一下Java方法和本地方法,后面讲到栈的时候还要用:
  1. Java方法是用JAVA编写的,编译成字节码,存储在class文件中。Java方法是与平台无关的。
  2. 本地方法是由其它语言编写的,编译成与处理器相关的机器代码。本地方法保存在动态链接库中,如果是windows系统,则为.dll文件。

 

二、Java虚拟机栈

2.1 概述:Java虚拟机栈描述的是Java方法执行的内存模型每个Java方法从调用至执行完成的过程中,都对应一个栈帧在虚拟机栈中入栈和出栈的过程。其中,栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。我们之前一直将的栈区域实际上就是Java虚拟机栈(或者虚拟机栈中的局部变量表)。

2.2 局部变量表

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。其中基本数据类型的变量,局部变量表直接存储它的值;而对于引用类型的变量,则存的是指向它对象的引用。这一局部变量表的大小在编译时就确定好了,在执行期间不会改变局部变量表的大小。

 

2.3 异常

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverFlowError异常。例如:递归无出口。
  • 如果虚拟机在动态扩展时无法申请到足够的内存,则会抛出OOM(OutOfMemoryError)异常。

 

2.4 特点

  • 线程私有
  • 与线程的生命周期相同

 

三、本地方法栈

3.1 本地方法栈与Java虚拟机栈的作用完全一样,它俩的区别就是本地方法栈为虚拟机使用的Native方法服务,Java虚拟机栈为JVM执行的Java方法服务。
3.2 作用与虚拟机栈完全一样,异常也与本地虚拟机栈一样,抛出StackOverFlowErrorOutOfMemoryError异常。
3.3 在虚拟机规范中,对本地方法栈中使用的语言,使用方法的数据结构没有强制规定,因此虚拟机可以自由的实现它,甚至在HotSpot虚拟机中,本地方法栈与虚拟机栈是同一块内存区域。

 

四、Java堆

4.1概念:Java堆(Java Heap)是JVM所管理的最大内存区域。 Java堆唯一目的就是存放对象实例,所有对象实例以及数组都要在堆上分配。Java堆是垃圾回收管理的主要区域,因此很多时候称之为“GC堆”(Garbage Collected Heap)。根据JVM规范规定的内容,Java堆处于物理上不连续的内存空间中,只要逻辑上时连续的就可以。

4.2 作用:存放对象实例

4.3 特点:

  1. 所有线程共享
  2. 在虚拟机启动时创建

4.4 异常 如果Java堆中没有足够的内存完成实例分配并且堆也无法扩展时,将会抛出OOM异常。

 

五:方法区

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

永久代并不意味着数据进入方法区就永久存在,此区域的内存回收主要是针对常量池的回收以及对类型的卸载。

特点:各个线程共享

异常:当方法区无法满足内存分配需求时,将抛出OOM异常。

 

五、运行时常量池

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

Java 语言并不要求常量池一定只有编译期才能产生,也就是并非预置入 Class 文件中常量池的内容后才能进入方法区的运行时常量池,运行期间也可以将新的常量放入池中,这种特性用的比较广泛的便是 String 类的 intern() 方法。

字面量: 字符串(JDK1.7后移动到堆中)、final常量、基本数据类型的值。

符号引用: 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。

 

问题:对象的创建

 

posted @ 2020-11-22 11:01  Peterxiazhen  阅读(143)  评论(0编辑  收藏  举报