JVM运行时内存分配机制

版权声明:欢迎转载评论~哈哈哈哈请标明出处呀 https://blog.csdn.net/legendaryhaha/article/details/88597350


前言

Java在内存管理方面不象C/C++那样,可以直接管理内存,而是将内存分配和内存管理的工作交托给JVM(Java虚拟机)。虽然这减轻了程序员的工作,但也带来了不好的一方面,开发人员若毫不关心内存的情况,可能会造成程序运行的低效且耗资源。

另外,内存管理在不同的jdk版本中,管理方式可能会有些差别,特别是JDK1.6,1.7,1.8,它们就像分水岭那样,在它们之前和之后的变化给我的感觉最为明显。

还有,我们口中一直说的Java虚拟机,在不同操作系统中也是不同的,它更像一个规范,然后在这个规范下,有着很多不同版本的实现。举个例子吧,用的较为广泛的Hotspot虚拟机,就是Java虚拟机的其中一个版本。

而在Hotspot虚拟机这个版本中,永久代(后面会讲)是其特有的概念,别的JVM都没有这个东西。在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间。

说这么多,其实还是想表达:由于学艺还不够精湛,开发经验还有所欠缺加上JVM的知识过于繁杂,种类甚多,所以某些方面的知识可能理解的不够透彻。


内存模型

该图参考深入理解JVM一书和多位博主所写的文章后,自己用Visio画的一个图。
在这里插入图片描述
该图中,左边蓝色背景部分(包括绿色)是线程共享的,黄色部分是线程私有的。


模型解析

Java heap

最左边部分的上部分是Java heap,即堆。它是垃圾回收器主要管理的区域,它可以分为如下几块区域:

1. 新生代
2. 老年代

其中,新生代大致分为如下两块区域:

  1. Eden区:即Java新对象诞生的地方,另外,如果新创建的对象占用内存很大,则直接分配到老年代。当Eden区内存不够的时候就会触发MinorGC(垃圾回收器),对新生代区进行一次垃圾回收。
  2. Servivor区:当新建对象是,Eden区不够分时,就会将新建的对象放置在Servivor区。该区又分为to Servivor和Servivor from,有些书上的说法是FromSpace、To Space。这块区域,平常只会用到一块,即Servivor from,当垃圾回收期进行内存清理时,才会用到另一块。

老年代则主要存放应用程序中生命周期长的内存对象。其中,老年代的对象比较稳定,来及回收器不会频繁执行。新生代如果有对象经过几次GC都没被清除,说明这个对象经常需要用,此时会晋升为老年代。当堆无法完成实例的分配,即内存空间不够时会抛出OutOfMemoryError异常


直接内存

首先,我们要知道直接内存并不属于JVM运行时数据内存分配的范畴。但在JDK1.4中,Java引入了NIO方式,即new inPut/ outPut类。该IO方式是基于通道(channel)和缓冲区(buffer),通过存储在堆中的对象作为该区域的引用,从而实现对该区的操作。

注意事项:(1)我们知道,程序运行时,可以通过-Xmx和 -Xms 参数设置堆内存实际为程序分配的内存,但很明显,该区域不属于堆内存,即不受其限制,但会受计算机本身配置的限制。所以,我们在实际使用中,避免设置的内存区域总额大于物理内存。(2)该区域的空间不足时,也是会抛出OutOfMemoryError异常的。


方法区

方法区,Method Area 又叫 Non-Heap,在HostPost虚拟机中又叫永久代,也是线程共享内存区域,它是jdk版本变换中,JVM变化的较为明显的一个区域。它一般负责存储程序中的:

  1. 类信息
  2. 常量
  3. 静态变量
  4. 字符串常量池

其中,在JDK1.7及之后,字符串常量池移动到堆中了,在JDK1.8中,HostPost虚拟机更是使用了元空间(MetaSpace)来代替永久代存储这些信息,从此,永久代的大小不需要再制定,只要不超出物理内存的限制就不会产生 OOM (OutOfMemory Error)异常。

而在方法区中,它还包含着一个运行时常量池,它存储的信息有:

  1. 类的版本、字段、方法、接口等描述信息
  2. 常量池表

而常量池表又有如下这些信息:

  1. 存储各种字面量和符号引用

我这里根据英文翻译成常量池表,而有些地方直接就叫常量池,我觉得这反而不易人们区分各种形形色色的常量池。譬如类常量池:类常量池其实就是一个数组,存储着类的各种信息,当他被虚拟机加载入内存时,会根据其中的信息分配在JVM中的相应数据区域,具体的表现就是运行时常量池。类常量池和运行时常量池的区别就是,前者具备动态性,根据程序运行的需要,动态的加入常量。

这里再说一下,字面量和常量、变量、符号引用的含义及区别:(1)字面量指的是右值,即等号右边的值,如:String a=“123”,这里的a为左值,123为右值。(2)而常量和变量都属于变量,只不过常量是赋过值后不能再改变的变量,在Java中的体现就是用final修饰的变量(这里的不能再改变,对于基本类型变量来说,是内容不可变,对于引用变量来说,是地址不可变)而普通的变量可以再进行赋值操作。(3)符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用和虚拟机的布局无关。


程序计数器

这个东西主要存放程序执行顺序的指令,如while,for,return等等,该东西和JVM的其他区域相比,除了存储信息不同,还有一个区别就是,它不会抛出OOM异常。


JVM Stack

Java 虚拟机栈,当类中的方法,不管是静态方法,还是非静态方法被调用时,此时会JVM Stack将创建栈帧,存储其中的局部变量、方法出口信息等。该区域经常会出现StackOverflowException,当栈可以动态扩展时,但申请的内存得不到满足时,则出现OOM异常。

本地方法栈

我们知道Java中有一个关键字native,它是用来表示调用非Java代码书写的方法的申明,譬如在Java程序中,申明一个调用C++程序的方法。此时,本地方法栈就起作用了,它会为JVM提供寻址,局部变量信息保存的服务。具体用法可以百度一下,似乎挺多资源的。

猜你喜欢

转载自blog.csdn.net/legendaryhaha/article/details/88597350