Java内存区域与内存溢出异常
程序员文章站
2022-03-02 13:49:01
...
1、程序计数器:
1)、指令寄存器:寄存器是*处理器内的组成部份。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。指令寄存器(IR )用来保存当前正在执行的一条指令。当执行一条指令时,先把它从内存取到数据寄存器(DR)中,然后再传送至IR。指令划分为操作码和地址码字段,由二进制数字组成。为了执行任何给定的指令,必须对操作码进行测试,以便识别所要求的操作。
2)、程序计数器:程序计数器是用于存放下一条指令所在单元的地址的地方。为了保证程序(在操作系统中理解为进程)能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,所以通常又称为指令计数器。在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。当执行指令时,CPU将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。当遇到转移指令如JMP指令时,后继指令的地址(即PC的内容)必须从指令寄存器中的地址字段取得。在这种情况下,下一条从内存取出的指令将由转移指令来规定,而不像通常一样按顺序来取得。
3)、程序计数器是一块较小的内存空间,它的作用可以看成是当前线程所执行的字节码的行号指示器。字节码解释器工作时是通过改变这个计数器来选取下一条需要执行的字节码指令的。
4)、Java虚拟机的多线程是通过线程轮流切换分配处理器执行时间来实现的,所以任一确定的时候,一个处理器只会执行一条线程中的指令,因此,为了线程切换后能够知道自己执行的确切位置,每条线程都需要一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,这类内存区域为"线程似有"的内存。
5)、如果线程正在执行一个Java方法,这个计数器存储的就是正在执行的字节码指定的地址,如果执行的是Native方法,这个计数器值则为空。
2、Java虚拟机栈:
1)、与程序计数器一样,Java虚拟机栈也是线程似有的,它的生命周期和线程相同。Java虚拟机栈描述的是Java方法在内存中执行的模型。
2)、每个方法执行的时候都会同时创建一个栈帧用来存储局部变量表、操作栈、动态链接、方法出口等信息,每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。
3)、局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(不等同于对象本身,可能只是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄)、returnAddress(指向一条字节码指令的地址)。
4)、局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法需要在栈中分配多大的局部变量空间是完全确定的,在方法运行期间,不会改变局部变量的大小。
5)、栈区域Java虚拟机规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出*Error异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存则会抛出OutOfMemoryError异常。
3、本地方法栈:
1)、本地方法栈与虚拟机栈非常相似,区别在于:虚拟机栈是为虚拟机执行Java方法(字节码)服务,而本地方法栈是为虚拟机使用Native方法服务。
2)、本地方法栈也会抛出*Error和OutOfMemoryError两种异常。
4、Java堆:
1)、在大多数应用中,Java堆是Java虚拟机所管理最大的一块的内存区域。
2)、Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存存储。
3)、Java虚拟机规范中有一句话描述是:所有的对象实例以及数组都在这里分配内存。
4)、Java堆是垃圾收集器管理的主要区域,谁要那些个对象都在这片区域创建呢,因此很多时候也被称为"GC堆",中间会根据不同条件来细分,但是无论如何细分,无论哪个区域,这里存储的仍然是对象实例,进一步划分是为了更好的回收内存,或者更快的分配内存。
5)、如果堆上没有足够的内存完成实例分配,并且堆也无法再扩展,将会抛出OutOfMemoryError异常。
5、方法区:
1)、它和java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟器加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。如果它无法满足内存分配,将抛出OutOfMemoryError异常。
6、运行时常量池:
1)、它是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述性信息外,还有一个是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容会在类加载后存放到方法区的运行时常量池中。当缺少内存时出现OutOfMemoryError
7、例子:
1)、假设有这一段代码:Object obj = new Object();
2)、如果出现在方法体中,则Object obj这部分的语义将会放映到Java栈的本地变量中,作为一个reference类型(对象引用)数据出现,而new Object()这一部分将会放映到Java堆中,存储在内存中的Object类型实例。而这个对象的类型数据(如对象类型、父类、实现的接口、方法等)则存储在方法区中。
8、测试Java堆溢出:
import java.util.ArrayList;
import java.util.List;
/**
* 更改一下Debug/Run页签中Jvm参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
*/
public class GcTest {
static class OoObject{
}
public static void main(String[] args) {
List<OoObject> list = new ArrayList<OoObject>();
while(true){
list.add(new OoObject());
}
}
}
-------------打印信息------------------
[GC [DefNew: 8192K->1024K(9216K), 0.0256963 secs] 8192K->4407K(19456K), 0.0257414 secs]
[GC [DefNew: 6433K->1024K(9216K), 0.0406973 secs] 9817K->9761K(19456K), 0.0407339 secs]
[GC [DefNew: 7581K->7581K(9216K), 0.0000107 secs][Tenured: 8737K->10240K(10240K), 0.1095769 secs] 16318K->11943K(19456K), 0.1096327 secs]
[Full GC [Tenured: 10240K->8016K(10240K), 0.1215974 secs] 19456K->15528K(19456K), [Perm : 1734K->1734K(8192K)], 0.1216411 secs]
[Full GC [Tenured: 8617K->8617K(10240K), 0.1275679 secs] 17833K->17833K(19456K), [Perm : 1734K->1734K(8192K)], 0.1276098 secs]
[Full GC [Tenured: 8617K->8616K(10240K), 0.1453816 secs] 17833K->17832K(19456K), [Perm : 1734K->1732K(8192K)], 0.1454178 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
9、测试虚拟机栈和本地方法栈溢出:
/**
* 更改一下Debug/Run页签中Jvm参数:-Xss128k
*/
public class GcTest {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak(); //不断循环方法
}
public static void main(String[] args) throws Throwable {
GcTest gc = new GcTest();
try{
gc.stackLeak();
}catch (Throwable e) {
System.out.println("stackLength大小为:"+gc.stackLength);
throw e;
}
}
}
--------------打印信息--------------
stackLength大小为:10308
Exception in thread "main" java.lang.*Error
at GcTest.stackLeak(GcTest.java:13)
at GcTest.stackLeak(GcTest.java:13)
at GcTest.stackLeak(GcTest.java:13)
at GcTest.stackLeak(GcTest.java:13)
at GcTest.stackLeak(GcTest.java:13)
at GcTest.stackLeak(GcTest.java:13)
at GcTest.stackLeak(GcTest.java:13)
10、测试运行时常量池溢出:
import java.util.ArrayList;
import java.util.List;
/**
* 更改一下Debug/Run页签中Jvm参数:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class GcTest {
public static void main(String[] args) throws Throwable {
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
//String.intern()方法,如果存在等于此String对象的字符串,则返回代表池中这个字符串的String对象
//否则,将此String对象包含的字符串加入到常量池中
list.add(String.valueOf(i++).intern());
}
}
}
--------------打印信息--------------
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space(permanent区用来存放用得类和类描述、常量)
11、测试方法区溢出:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 更改一下Debug/Run页签中Jvm参数:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class GcTest {
static class OoObject{
}
public static void main(String[] args) throws Throwable {
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OoObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor(){
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invoke(obj, args);
}
});
enhancer.create();
}
}
}
--------------引入cglib--------------
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
--------------打印信息--------------
Caused by: java.lang.OutOfMemoryError: PermGen space(方法区存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等,动态产生类会抛出内存溢出异常)
参考自:《深入理解Java虚拟机》
上一篇: Oracle数据库的SQL性能问题分析
下一篇: Netty的Nio写优化