文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么理解并掌握Java虚拟机内存区

2023-06-16 14:44

关注

本篇内容主要讲解“怎么理解并掌握Java虚拟机内存区”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解并掌握Java虚拟机内存区”吧!

一、方法区(Method Area)

方法区的概念

方法区又叫静态区,存放的是已加载的类的基本信息、常量、静态变量等。它是各个线程共享区域。

比方说我们在写 Java  代码时,每个线程度可以访问同一个类的静态变量对象。由于使用反射机制的原因,虚拟机很难推测哪那个类信息不再使用,因此这块区域的回收很难。

静态块和非静态块有什么区别?

方法区的特点

方法区的异常

对这块区域主要是针对常量池回收,值得注意的是 JDK1.7 已经把常量池转移到堆里面了。同样,当方法区无法满足内存分配需求时,会抛出  OutOfMemoryError。制造方法区内存溢出,注意,必须在 JDK1.6及之前版本才会导致方法区溢出,原因后面解释,执行之前,可以把虚拟机的参数  -XXpermSize 和 -XX:MaxPermSize 限制方法区大小。

代码清单如下:

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

输出异常结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space  at java.util.Arrays.copyOf(Arrays.java:2245)  at java.util.Arrays.copyOf(Arrays.java:2219)  at java.util.ArrayList.grow(ArrayList.java:242)  at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)  at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)  at java.util.ArrayList.add(ArrayList.java:440)  at com.vprisk.knowledgeshare.MethodAreExample.main(MethodAreExample.java:15)  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)  at java.lang.reflect.Method.invoke(Method.java:606)  at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

关于 String 的 intern() 函数intern() 的作用:

如果当前的字符串在常量池中不存在,则放入到常量池中。

上面的代码不断将字符串添加到常量池,最终肯定会导致内存不足,抛出方法区的 OOM。解释一下,为什么必须将上面的代码在 JDK1.6 之前运行。我们前面提到  JDK1.7 后,把常量池放入到堆空间中,这导致 intern() 函数的功能不同,代码清单如下:

public static void testInternMethod(){  String str1 =new StringBuilder("hua").append("chao").toString();  System.out.println(str1.intern()==str1);  String str2=new StringBuilder("ja").append("va").toString();  System.out.println(str2.intern()==str2); }

在场景 jdk6,输出结果:

false , false

在场景 jdk7,输出结果:

true , false

为什么了?

原因是在 JDK 1.6 中,intern() 方法会把首次遇到的字符串实例复制到常量池中,返回的也是常量池中的字符串的引用,而  StringBuilder 创建的字符串实例是在堆上面,所以必然不是同一个引用,返回false。在 JDK 1.7 中,intern  方法不再复制实例,常量池中只保存首次出现的实例的引用,因此intern() 返回的引用和由 StringBuilder 创建的字符串实例是同一个。为什么对  str2 比较返回的是 false呢?这是因为,JVM 中内部在加载类的时候,就已经有”java”这个字符串,不符合“首次出现”的原则,因此返回  false。

方法区的作用

方法区存放的是类信息、常量、静态变量等,是各个线程共享区域。

方法区的运用

通过过设置虚拟机的参数-XXpermSize 以及 -XX:MaxPermSize 限制方法区大小。

二、虚拟机栈(VM Stack)

虚拟机栈的概念

Java 方法执行的内存模型:

每个方法被执行的时候都会同时创建一个栈帧  (StackFrame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

局部变量表

局部变量表存放了编译器克制的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(Object  reference)和字节码指令地址(returnAddress 类型)。

操作栈

操作数栈也常被称为操作栈,它是一个后入先出(Last In First Out, LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到  Code 属性的max_stacks 数据项之中。操作数栈的每一个元素可以是任意的 Java 数据类型,包括 long 和 double。32  位数据类型所占的栈容量为 1,64 位数据类型所占的栈容量为 2。在方法执行的任何时候,操作数栈的深度都不会超过在 max_stacks  数据项中设定的最大值。

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。

举个例子,整数加法的字节码指令 iadd  在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值和并相加,然后将相加的结果入栈。

操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。再以上面的  iadd 指令为例,这个指令用于整型数加法,它在执行时,最接近栈顶的两个元素的数据类型必须为int 型,不能出现一个 long 和一个float 使用 iadd  命令相加的情况。

动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。我们知道 Class  文件的常量池有存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。

虚拟机栈的特点

虚拟机栈的异常

一种是 StackOverflowError

当前线程如果请求的栈深度大于虚拟机所允许的深度时,则会抛出该异常。例如,将一个函数反复递归自己,最终会出现栈溢出错误(StackOverflowError)。

代码清单如下:

public class StackOverflowErrorDemo {  public static void main(String []args){  printStackOverflowError();  }  public static void printStackOverflowError(){  printStackOverflowError();  } }

输出异常结果:

Exception in thread "main" java.lang.StackOverflowError stack length:9482  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)  at com.itech.jvm.demo.StackOverflowErrorDemo.printStackOverflowError(StackOverflowErrorDemo.java:22)

需要说明的是,在单个线程环境下,无论是栈帧太大,还是虚拟机栈容量太小,当内存无法分配时,虚拟机都会抛出 StackOverflowError  异常。

一种是 OOM 异常

当虚拟机栈支持动态扩展时,如果无法申请到足够多的内存时就会抛出 OOM 异常。

代码清单如下:

public class VMOOMDemo {  public static void main(String[] args) throws Throwable {  VMOOMDemo demo = new VMOOMDemo();  demo.printVMOOM();  }  public void printVMOOM() {  while (true) {  new Thread() {  public void run() {  while (true) {  }  }  }.start();  }  } }

这个例子慎用...

本例通过不断地建立线程的方式产生内存溢出异常。但是,这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系,或者准确地说,在这种情况下,给每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。其原因是操作系统分配给每个进程的内存是有限制的,如  32 位的Windows 限制为 2 GB。。

虚拟机栈的作用

用于存储局部变量、操作栈、动态链接、方法出口

虚拟机栈的运用

对于 32 位的 jvm,默认大小为 256 kb, 而 64 位的 jvm, 默认大小为 512 kb, 可以通过 -Xss  设置虚拟机栈的最大值。不过如果设置过大,会影响到可创建的线程数量。

三、本地方法栈(Native Method Stack)

本地方法栈的概念

本地方法栈与虚拟机栈所发挥的作用很相似,他们的区别在于虚拟机栈为执行Java代码方法服务,而本地方法栈是为 Native  方法服务。与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

本地方法栈的特点

本地方法栈的异常

与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

本地方法栈的作用

有时 java 应用需要与 java 外面的环境交互。这是本地方法存在的主要原因,你可以想想 java  需要与一些底层系统如操作系统或某些硬件交换信息时的情况。

本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解 java 应用之外的繁琐的细节。

JVM 支持着 java 语言本身和运行时库,它是 java  程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一些底层(underneath  在下面的)系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法,我们得以用 java 实现了 jre 的与底层系统的交互,甚至 JVM  的一些部分就是用 C 写的,还有,如果我们要使用一些 java 语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。

Sun's Java Sun 的解释器是用 C 实现的,这使得它能像一些普通的 C 一样与外部交互。jre 大部分是用java  实现的,它也通过一些本地方法与外界交互。例如:类 java.lang.Thread

的 setPriority() 方法是用 java 实现的,但是它实现调用的是该类里的本地方法setPriority0()。这个本地方法是用 C  实现的,并被植入 JVM 内部,在 Windows 95 的平台上,这个本地方法最终将调用 Win32 SetPriority()  API。这是一个本地方法的具体实现由JVM直接提供,更多的情况是本地方法由外部的动态链接库(external dynamic link  library)提供,然后被 JVM 调用。

四、Java 堆(Heap)

Java 堆的概念

Java  堆可以说是虚拟机中最大一块内存了。它是所有线程所共享的内存区域,几乎所有的实例对象都是在这块区域中存放。当然,随着JIT编译器的发展,所有对象在堆上分配渐渐变得不那么“绝对”了。

Java 堆是垃圾收集器管理的主要区域。由于现在的收集器基本上采用的都是分代收集算法,所有 Java  堆可以细分为:新生代和老年代。在细致分就是把新生代分为:

根据 Java 虚拟机规范的规定:

Java  堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。

Java 堆的特点

Java 堆的异常

当堆无法再扩展时,会抛出 OutOfMemoryError 异常。

Java 堆的作用

唯一目的就是存放对象实例,几乎所有的对象实例都在 java 堆中分配内存

Java 堆的运用

通过 -Xmx 和 -Xms 控制

五、程序计算器(Program Counter Register)

程序计算器的概念

类似于 PC 寄存器,程序计数器是线程私有的区域,每个线程都有自己的程序计算器。可以把它看成是当前线程所执行的字节码的行号指示器。

程序计算器的特点

程序计算器的异常

此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OOM(OutOfMemoryError)情况的区域

程序计算器的作用

程序计算器的运用

通过 -Xmx 和 -Xms 控制

六、直接内存(Direct Memory)

什么是直接内存与非直接内存?

根据官方文档的描述:

A byte buffer is either direct or non-direct. Given a direct byte buffer, the  Java virtual machine will make a best effort to perform native I/O operations  directly upon it. That is, it will attempt to avoid copying the buffer's content  to (or from) an intermediate buffer before (or after) each invocation of one of  the underlying operating system's native I/O operations.

byte byffer 可以是两种类型,一种是基于直接内存(也就是非堆内存);另一种是非直接内存(也就是堆内存)。

直接内存(Direct Memory)既不属于虚拟机运行时数据区的一部分,也不属于  Java虚拟机规范中定义的内存区域,但是这部分内存却被频繁地使用,而且还可能导致OutOfMemoryError 异常出现。

对于直接内存来说,JVM 将会在 IO 操作上具有更高的性能,因为它直接作用于本地系统的 IO 操作。而堆内存如果要作 IO  操作,会先复制到直接内存,再利用本地 IO 处理。

从数据流的角度,非直接内存的作用链:

本地IO-->直接内存-->非直接内存-->直接内存-->本地IO

而直接内存的作用链:

本地IO-->直接内存-->本地IO

很明显,在做 IO 处理时,比如网络发送大量数据时,直接内存会具有更高的效率。

A direct byte buffer may be created by invoking the allocateDirect factory  method of this class. The buffers returned by this method typically have  somewhat higher allocation and deallocation costs than non-direct buffers. The  contents of direct buffers may reside outside of the normal garbage-collected  heap, and so their impact upon the memory footprint(内存占用) of an application  might not be obvious. It is therefore recommended that direct buffers be  allocated primarily for large, long-lived buffers that are subject to the  underlying system's native I/O operations. In general it is best to allocate  direct buffers only when they yield a measureable gain in program  performance.

但是由于直接内存使用allocateDirect  创建,它比申请普通的堆内存需要耗费更高的性能。不过它不会占用应用的堆内存。所以,当你有大量数据要缓存时,并且它的生命周期又比较长,那么使用直接内存是个不错的选择。但如果该选择不能带来显著的性能提升,推荐使用堆内存。

在 JDK1.4 的 NIO 中,ByteBuffer 有个方法是:

public static ByteBuffer allocateDirect(int capacity) {  return new DirectByteBuffer(capacity); } DirectByteBuffer(int cap) {  ......  protected static final Unsafe unsafe = Bits.unsafe();  unsafe.allocateMemory(size);  ...... } public final class Unsafe {  ......  public native long allocateMemory(long var1);  ...... }

另外直接内受限于本机总内存(包括 RAM 及 SWAP 区或者分页文件)的大小及处理器寻址空间的限制。

服务器管理员配置虚拟机参数时,一般会根据实际内存设置 -Xmx  等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现  OutOfMemoryError 异常。

直接内存的特点

直接内存的异常

动态扩展时出现 OutOfMemoryError 异常

直接内存的作用

基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java  堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和Native  堆中来回复制数据。

直接内存的运用

XX:MaxDirectMemorySize=10M

直接内存的使用场景

例如在 IO 处理时,比如网络发送大量数据时,直接内存会具有更高的效率。

到此,相信大家对“怎么理解并掌握Java虚拟机内存区”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯