博客 Java 学习笔记 —— 内存管理 Java 内存管理

Java 学习笔记 —— 内存管理 Java 内存管理

   数栈君   发表于 2023-11-23 14:28  408  0

简介


Java 虚拟机的内存管理分为以下几个运行时数据区:


  • 方法区
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

其中,方法区是所有线程共享的数据区,而其他的是线程隔离的数据区。




Java
堆,又称 GC 堆,是 GC 的管理的主要区域。在虚拟机启动时创建。主要作用是存放对象实例,几乎所有的对象实例都会存放在 Java
堆中。Java 堆可以处于物理不连续的内存空间中,只要逻辑连续即可。通常 Java 堆是可扩展的。当 Java
堆无法申请到所需的内存空间来存放实例,也无法扩展时,会抛出,OutOfMemoryError 异常。



OOM 实践



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 类型 (指向一条字节码指令的地址)。


局部变量表所需的内存空间在编译期内完成分配。当进入一个方法时,这个方法需要帧中分配多大的局部内存是完全确定的,期间不会改变大小。


在这一区域中,虚拟机有两种异常。


  1. 线程请求的栈深度大于虚拟机所允许的深度。
  2. 虚拟机栈无法申请足够的动态内存。

OOM 实践


线程请求的栈深度大于虚拟机所允许的深度



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 堆一样是各个线程共享的内存区域。它用于存放虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码。



OOM 实践



/**
*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

所有字面值字符串和字符串赋值常量表达式都是内部的。

返回:

一个字符串,内容与此字符串相同,但它保证来自字符串池中。
复制

说明中的字符串池,也就是我们上文说的方法区。




本地方法栈


本地方法栈与虚拟机栈的机理类似,不再赘述。




程序计数器


  1. 简述 程序计数器(program counter register)只占用了一块比较小的内存空间,有时可以忽略不计的。
  2. 作用 可以看作是当前线程所执行的字节码文件(class)的行号指示器。在虚拟机的世界中,字节码解释器就是通过改变计数器的值来选取下一条执行的字节码指令,分支、循环、跳转、异常处理、线程恢复都需要它完成/
  3. 特性
    1. 因为处理器在一个确定是时刻只会执行一个线程中的指令,线程切换后,是通过计数器来记录执行痕迹的,因而可以看出,程序计数器是每个线程私有的。
    2. 如果执行的是 java 方法,那么记录的是正在执行的虚拟机字节码指令的地址的地址,如果是 native 方法,计数器的值为空(undefined)。
    3. 这个内存区域是唯一一个在 java 虚拟界规范中没有规定任何 OutOfMemoryError 的情况的区域。

直接内存


直接内存(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

0条评论
社区公告
  • 大数据领域最专业的产品&技术交流社区,专注于探讨与分享大数据领域有趣又火热的信息,专业又专注的数据人园地

最新活动更多
微信扫码获取数字化转型资料
钉钉扫码加入技术交流群