/ java  

JVM初探-内存区域划分

最近写的爬虫抓取某写网站数据时,发现程序有出现OutOfMemory异常..记得以前面试的时候面试官问有没有遇到过OOM,我都不懂对方问的是啥..好了,扯点废话,接下来开始学习一下JVM相关的知识,以便于今后遇到OOM问题知道如何解决.

内存区域划分

程序计数器

可以看做是一小块内存区域用于存放当前线程所执行的字节码到什么位置了,我们都知道不同线程执行有个CPU轮询分配时间片的过程,假设是单核CPU,线程A执行到三分之一的位置,CPU由于执行线程B,线程A需要暂停执行.这时就把线程执行到的位置记录在程序计数器中,等线程B执行后再回来执行线程A就可以读取一下位置,继续执行下去.

这块区域是线程私有的内存,每个线程都会分配,实际上这块内存大小可以不做考虑,因为比较小

java虚拟机栈

表示代码中方法的内存模型,主要用于存放局部变量表,操作数栈,动态链接,方法等信息.

//小例子
public int sum() {
    int sum = getNum() + getNum();
    return sum;
}


public int getNum1() {
    return 1;
}

public int getNum2() {
   return 2;
}

如果我们在调用sum()方法,sum()方法以及getNum1(),getNum2()方法首先会创建3个栈帧(理解成java虚拟机栈的组成部分),按照调用顺序,sum()方法对应的栈帧先入栈,其次是getNum1(),再是getNum2(),每个栈帧里面还需要内存存放方法对于的信息.例如,3个方法的返回值是int,访问控制级别都是public等,其中sum()方法还有类型为int的局部变量sum.
虚拟机栈按照先进后出的方法,依次调用getNum2(),getNum1(),sum().所以如果方法写的过程,会导致分配的内存迟迟得不到释放,例如在sum()方法里面分配了一个局部变量集合对象占用了大量的内存,然后又调用了其它N个方法,这时要想等到sum()方法对于的栈帧出栈就得等很长时间了.

线程私有的区域,通过-Xss参数来分配每个方法对于的内存大小

本地方法栈

实际上java有些类的实现是要基于C语言代码的,例如IO流相关,如果我们看源码就会发现,有些方法都点不进去了,就只有一个native声明,这就表示该方法是会调用底层的C语言程序.
和虚拟机栈类似

java堆

个人觉得这是最容易出现OOM的地方了.堆内存主要用于存放实例化的对象,举个例子List<String> list = new ArrayList<String>(); 后面new ArrayList<String>()就是用的堆内存.如果不断地实例化对象,对象由于引用还存在无法被GC回收,持续下去就会出现OOM异常.

各个线程共用的区域,可以通过-Xms(初始化堆内存大小)和-Xmx(最大堆内存大小)来设置对应的大小.

当初我报这个异常是因为我的程序含有很多定时任务去爬取数据,通过spring的quartz执行这些定时任务的间隔时间太短,多个任务几乎同时执行.
方法带Http请求和循环字符串处理,持续时间比较长,大量的对象实例的回收比较慢,以至于最后内存不足以分配给新的对象.

方法区

存放已被虚拟机加载的类信息,常量,静态变量,即使编译器编译后的代码数据(JIT编译后的机器代码可以直接执行,不需要JVM解释执行).
常量主要指那些字符串常量,String s = "a";这里的a就会占用方法区内存.
静态变量指static修饰开头的,有些数据(例如请求的URL),就用static修饰,避免循环分配回收这些操作重复执行.

各个线程共用的区域,通过-XX:MaxPermSize来设置

直接内存

实际上属于堆外内存,如果程序想要更好地使用堆外内存而且不想收到GC的控制,就是被GC回收,可以考虑用BigMemory.