十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
本篇内容介绍了“Java内存区域与内存溢出异常知识讲解”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
创新互联专业为企业提供龙里网站建设、龙里做网站、龙里网站设计、龙里网站制作等企业网站建设、网页设计与制作、龙里企业网站模板建站服务,10多年龙里做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
在开始讲解之前, 需要先明确关于 JVM 的一些基本概念
我们都知道, Java 是一个跨平台的语言, Java 跨平台的基本支撑其实就是 JVM 对操作系统底层细节的屏蔽, 相当于加了一个中间层(计算机中的任何问题都可以加一个中间层解决~), Java 不再像 C/C++ 等语言一样直接翻译为针对特殊平台的机器码, 而是翻译为字节码, 也即是我们的 class 文件, 下图大概可以比较简明的概括了~; 字节码就相当于 Java 世界中的汇编, 而 JVM 则不是跨平台的, 只是不同平台的 JVM 都能识别和运行标准格式的字节码文件而已

关于 JVM 运行 class 文件, 我觉得下图已经可以比较准确的表达了

我们下面要讲的就是 Runtime Data Area 部分
JVM 会在执行 Java 程序的时候把它所管理的内存划分为若干个不同的数据区, 如下:

线程私有
指向下一条需要执行的字节码指令; 如果线程正在执行一个 Java 方法, 该计数器记录的是正在执行的虚拟机字节码指令的地址; 如果正在执行 Native 方法, 该计数器值则为空( Undefined )
该区域是是唯一一个在 Java 虚拟机中没有规定任何 OutOfMemoryError 情况的区域
线程私有
描述 Java 方法执行的内存模型, 每个方法调用就对应着一个栈帧的入栈和出栈; 一个栈帧里面存储了局部变量表, 操作数栈, 动态链接, 方法出口等信息
局部变量表存储了编译器可知的各种基本数据类型, 对象引用, returnAddress ; 局部变量表的大小在编译期间即可确定, 运行期间大小不变
StackOverflowError : 线程请求栈深度大于虚拟机允许深度
异常示例代码:
public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak() {
stackLength++;
stackLeak();
} public static void main(String[] args) {
JavaVMStackSOF sof = new JavaVMStackSOF(); try {
sof.stackLeak();
} catch (Throwable e) {
System.out.println("Stack Length: " + sof.stackLength); throw e;
}
}
}OutOfMemoryError : 虚拟机栈动态扩展时无法申请到足够内存
异常示例代码:
public class JavaVMStackOOM { private void dontStop() { while (true) {
}
} public void stackLeakByThread() { while (true) { new Thread(new Runnable() { @Override
public void run() {
dontStop();
}
}).start();
}
} public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}注: 由于操作系统分配给每个进程的内存空间是有限制的, 所以如果是由于建立过多的线程导致内存溢出, 在不能减少线程数或者更换 64 位虚拟机的情况下, 可以选择通过减少最大堆和减少栈容量来换取更多的线程
线程私有
和虚拟机栈类似, 只是本地方法栈提供的是 Native 方法服务
StackOverflowError 和 OutOfMemoryError
线程共享
垃圾收集管理的主要区域
几乎所有的对象实例都在这里分配
OutOfMemoryError
异常示例:
public class JavaVMHeapOOM { static class HeapOOM {
} public static void main(String[] args) { List list = new ArrayList(); while (true) { list.add(new HeapOOM());
}
}
} 线程共享
该区域的垃圾回收目标主要是针对常量池的回收和对类型的卸载
存储已被虚拟机加载的类信息, 常量, 静态变量, 即使编译器编译后的代码等数据
运行时常量池是方法区的一部分, 但是 JDK6 之后, 常量池被放入了堆中;
Class 文件中也有常量池部分, 即编译期生成的各种字面量和符号引用, 这部分将在类加载后进入方法区的运行时常量池中, 此外还会把翻译出来的直接引用也存储在运行时常量池中
运行时常量池相对于 Class 文件常量池的另外一个最重要的特征是具备动态性, 即运行期间也可以将新的常量放入池中, 比如 String 的 intern() 方法
String.intern() 作用是: 如果字符串常量池中已经包含一个等于此 String 对象的字符串, 则返回代表池中这个字符串的 String 对象; 否则, 将此 String 对象包含的字符串添加到常量池中, 并且返回此 String 对象的引用
同样, 收方法区的限制, 当常量池无法再申请到内存时会抛出 OutOfMemoryError
OutOfMemoryError : 方法区无法满足内存分配需求
异常示例:
public class RuntimeConstantPoolOOM {
public static void main(String[] args) { List list = new ArrayList<>(); int i = 0; while (true) {
list.add(String.valueOf(i++).intern());
}
}
} 直接内存不是虚拟机运行时数据区的一部分, 但是也被频繁使用, 如: 在 JDK1.4 中新加入了 NIO 类, 引入了一种基于通道( Chanel )和缓冲区( Buffer )的 I/O 方式, 它可以使用 Native函数库直接分配堆外内存, 然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作, 避免了在 Java 堆和 Native 堆中来回复制数据, 提高性能
同样会产生 OutOfMemoryError
“Java内存区域与内存溢出异常知识讲解”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!