您的位置:首页 > 编程语言 > Java开发

【java基础 7】java内存区域分析及常见异常

2016-12-04 17:51 543 查看
本篇博客,主要是读书笔记总结,还有就是结合培训分享的总结,没有太多的技术含量!

java 的自动内存管理机制,使得程序员不用为每一个new惭怍的对象写配对的delete/ free代码(回想起C++的编写析构函数,还是挺亲切的)因为内存由虚拟机管理,所以,一旦出现了内存泄漏和溢出等问题,刚好又不了解虚拟机是怎样使用内存的,那排错工作将会变得比较艰难!

一、内存分配



Program counter register:一块比较小的内存空间,作用可以看做是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个java方法,计数器记录的是正在执行的虚拟机的字节码指令的地址,如果正在执行的是native方法,计数器值设置为空(Undefined) 备注:这是唯一一个在java虚拟机规范中没有规定任何outofmemoryerror的区域。
——详细了解计数器,可以看看CPU阿甘系列文章。

栈:

1,java virtual machine stacks:描述java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧的在虚拟机中的入栈和出栈过程。这个区域是线程私有的,它的生命周期和线程相同。

线程私有:java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时候,一个处理器(对于多核即指一个内核)只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“”线程私有“”的内存。 ——PS:看吧,别以为多线程就一定能提高效率,说的好像切换不需要时间,程序计数器不耗性能似的。(额,看情况哈)

2,native method stacks:与java虚拟机栈相似,区别是:虚拟机栈为执行java方法(字节码)服务,而本地栈则是为虚拟机使用到的native方法服务。

注意:栈区抛出的异常一般有:stackoverflowerror和outofmemoryerror

堆:所有的对象实例以及数组都要在对上分配,原文:the heap is the runtima data area from which memory for all class instances and arrays is allocated.

由于收集器基本采用分代收集算法,所以java heap可分为新生代和老年代,再细致的分。。。。。。。。

一般来说,如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出outofmemoryerror异常

方法区:

1,method area:是各个线程共享的内存区域(与堆一样)它用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。在这个区域里面,不需要连续的内存、可以选择固定大小、可扩展,可选择不实现垃圾收集。当方法区无法满足内存分配需求时,将抛出outofmemoryerror异常。

2,runtime constant pool,它属于方法区的一部分,class文件除了有类的版本、字段、方法、接口等信息,还有一项是常量池:用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 PS:区别class文件中的常量池和运行时常量池。 当常量池无法再向方法区内存申请到内存,抛出outofmemoryerror异常。

二、一般问题解决思路

总体说来,JVM的异常,大体分为两个部分:outofmemoryerror(内存溢出) 和 stackoverflowerror(堆栈溢出)从上述文章来看,异常可以极端简化为两种情况:1,操作系统没有给JVM(各个区)足够的内存——调整JVM的内存大小可解决,通过设置相应参数;2,JVM没有给程序足够的内存——一般而言JVM不会拒绝分配内存,一旦没有足够的内存,那么就是内存用尽,即有程序劫持了空间不释放,改代码。

要想解决问题,先写出以下几段代码先:

2.1,写出一段会造成堆区溢出的代码

分析:堆区存储的是对象实例,所以,要造成堆区溢出,那么就无限制的new出对象实例吧,然后为了防止GC工作,再将这些对象实例,添加到list数组中,这样就。。。。

public class heapOOM{

static class OOMObject{

}

public static void main(String[] args){
List<OOMObject> list=new ArrayList<OOMObject>();

while(true){
list.add(new OOMObject);
}
}
}


2.2,写出一段会造成栈区溢出的代码

分析:

java虚拟机栈描述java方法执行时的内存模型,如果一个方法不停的调用自己(递归)当深度达到JVM所允许的最大深度时,出错!

public class javaVMStackSof{

private int stackLength=1;

public void stackLeak(){
stackLength++;
stackLeak();
}

public static void main(String[] args){
javaVMStackSof oom=new javaVMStackSof();

oom.stackLeak();

}
}


本地方法栈为native方法服务,所以要溢出的话,也是通过递归使用native方法!

2.3,写出一段方法区溢出的代码

方法区是被各个线程共享的内存区域,用于存储被虚拟机加载的类信息等,所以,只要在运行时不停地加载类文件等,就会导致方法区溢出——增强class

public class javamethodareaoom{

public static void main(String[] args){
while(true){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor(){
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy)throws Throwable{
return proxy.invokeSuper(obj,args);
}
});
enhancer.create();
}
}
static class OOMObject{

}
}


2.4,写出一段常量池溢出的代码

只要新的常量请求不到方法区的内存,就会出错,那么最简单直接的方式,就是在运行时不停的请求内存咯!最常用的就是string.intern()方法

public class juntimeconstantpooloom{

public static void main(String[] args){
List<String> list=new ArrayList<String>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
}

}
}


三、总结

这是基本的内存分布分析,知道了怎么写代码会造成内存溢出或者内存消耗,那么当想要去优化的时候,就会轻松很多!在写这个博客的时候,突然想起了我有一次去面试的面试题,唉,现在了解了内存分配之后,才发现我当时做的关于堆栈创建对象的题,好像都错了!

不过,知识本身就是在不断的更新、更正的,现在知道错了,也不算晚,多积累,多学习吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: