Java 虚拟机的内存管理分为以下几个运行时数据区:
其中,方法区和堆是所有线程共享的数据区,而其他的是线程隔离的数据区。
Java
堆,又称 GC 堆,是 GC 的管理的主要区域。在虚拟机启动时创建。主要作用是存放对象实例,几乎所有的对象实例都会存放在 Java
堆中。Java 堆可以处于物理不连续的内存空间中,只要逻辑连续即可。通常 Java 堆是可扩展的。当 Java
堆无法申请到所需的内存空间来存放实例,也无法扩展时,会抛出,OutOfMemoryError 异常。
public classHeapOOM{
static classOOMObject{
}
publicstaticvoidmain(String[]args){
List<OOMObject> list=new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject);
}
}
}
复制 如上面 demo 代码所展示的。由于 OOMObject 的实例是存放在堆上。当使用死循环进行创建时,便会逐渐占满堆的空间,最后产生 OutOfMemoryError。
Java 虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈是 Java 方法执行的内存模型。每个方法在执行的同时会创建一个栈帧。用于存放局部变量表、操作数栈、动态链接和方法出口等信息。
局部变量表存放了编译期可知的各种基本数据类型 (boolean,byte,char,short,int,float,long,double)、对象引用 (reference)、returnAddress 类型 (指向一条字节码指令的地址)。
局部变量表所需的内存空间在编译期内完成分配。当进入一个方法时,这个方法需要帧中分配多大的局部内存是完全确定的,期间不会改变大小。
在这一区域中,虚拟机有两种异常。
线程请求的栈深度大于虚拟机所允许的深度
public classJavaVMStackSOF{
private int stackLength=1;
public void stackLeak {
stackLength++;
stackLeak();
}
publicstaticvoidmain(String[] args) throws Throwable{
JavaVMStackSOF oom = new JavaVMStackSOF();
try{
oom.stackLeak();
}catchThrowable(e){
System.out.println ("stack length "+oom.stackLength);
throw e ;
}
}
}
复制 虚拟机栈无法申请足够的动态内存
public classJavaVMStackOOM{
private void dontStop {
while (true) {
}
}
public void stackLeakByThread {
while (true) {
Thread thread=new Thread (new Runnable() {
@Override
public void run {
dontStop();
}
});
thread.start();
}
}
publicstaticvoidmain(String[] args) throws Throwable{
JavaVMStackOOM oom=new JavaVMStackOOM();
oom.stackLeakByThread();
}
复制 我们通过两种形式的迭代,展现了虚拟机栈两种 OOM 的产生原因。
与 Java 堆一样是各个线程共享的内存区域。它用于存放虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码。
/**
*VM Args -XX PermSize=10M-XX MaxPermSize=10M *@author zzm
*/
public classRuntimeConstantPoolOOM{
publicstaticvoidmain(String[] args){
List<String> list = new ArrayList<String>();
int i=0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
复制 intern
public String intern()
返回字符串对象的规范化表示形式。
一个初始时为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
所有字面值字符串和字符串赋值常量表达式都是内部的。
返回:
一个字符串,内容与此字符串相同,但它保证来自字符串池中。
复制 说明中的字符串池,也就是我们上文说的方法区。
本地方法栈与虚拟机栈的机理类似,不再赘述。
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现,所以我们放到这里一起讲解。
在
JDK 1.4 中新加入了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的
I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer
对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native
堆中来回复制数据。显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括 RAM 及
SWAP 区或者分页文件)的大小及处理器寻址空间的限制。
服务器管理员配置虚拟机参数时,一般会根据实际内存设置 - Xmx 等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError 异常。
如有问题,欢迎指正。
免责申明:
本文系转载,版权归原作者所有,如若侵权请联系我们进行删除!
《数据治理行业实践白皮书》下载地址:https://fs80.cn/4w2atu
《数栈V6.0产品白皮书》下载地址:https://fs80.cn/cw0iw1
想了解或咨询更多有关袋鼠云大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:https://www.dtstack.com/?src=bbs
同时,欢迎对大数据开源项目有兴趣的同学加入「袋鼠云开源框架钉钉技术群」,交流最新开源技术信息,群号码:30537511,项目地址:https://github.com/DTStack