`
javandroid
  • 浏览: 25363 次
  • 性别: Icon_minigender_1
文章分类
社区版块
存档分类
最新评论

JVM内存结构浅析

 
阅读更多

内存作为系统中重要的资源,对于系统稳定运行和高效运行起到了关键的作用,Java和C之类的语言不同,不需要开发人员来分配内存和回收内存,而是由JVM来管理对象内存的分配以及对象内存的回收(又称为垃圾回收、GC),这对于开发人员来说确实大大降低了编写程序的难度,但带来的一个副作用就是,当系统运行过程中出现JVM抛出的内存异常(例如OutOfMemoryError)的时候,很难知道原因是什么,另外一方面,要编写高性能的程序,通常需要借助内存来提升性能,因此如何才能合理的使用内存以及让JVM合理的进行内存的回收是必须掌握的,本节将主要分析一下JVM的内存结构。

其实对于我们一般理解的计算机内存,它算是CPU与计算机打交道最频繁的区域,所有数据都是先经过硬盘至内存,然后由CPU再从内存中获取数据进行处理,又将数据保存到内存,通过分页或分片技术将内存中的数据再flush至硬盘。那JVM的内存结构到底是如何呢?JVM做为一个运行在操作系统上,但又独立于os运行的平台,它的内存至少应该包括象寄存器、堆栈等区域。

JVM在运行时将数据划分为了6个区域来存储,而不仅仅是大家熟知的Heap区域,这6个区域图示如下:


JVM内存的分配结构示意图



下面将逐一介绍下各个区域所做的工作及其充当的功能。

1.PC Register(PC寄存器)

PC寄存器是一块很小的内存区域,主要作用是记录当前线程所执行的字节码的行号。字节码解释器工作时就是通过改变当前线程的程序计数器选取下一条字节码指令来工作的。任何分支,循环,方法调用,判断,异常处理,线程等待以及恢复线程,递归等等都是通过这个计数器来完成的。

由于Java多线程是通过交替线程轮流切换并分配处理器时间的方式来实现的,在任何一个确定的时间里,在处理器的一个内核只会执行一条线程中的指令。因此为了线程等待结束需要恢复到正确的位置执行,每条线程都会有一个独立的程序计数器来记录当前指令的行号。计数器之间相互独立互不影响,我们称这块内存为“线程私有”的内存。

如果所调用的方法为native的,则PC寄存器中不存储任何信息。

2.JVM栈

JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址,因此Java中基本类型的变量是值传递,而非基本类型的变量是引用传递,Sun JDK的实现中JVM栈的空间是在物理内存上分配的,而不是从堆上分配。

由于JVM栈是线程私有的,因此其在内存分配上非常高效,并且当线程运行完毕后,这些内存也就被自动回收。

当JVM栈的空间不足时,会抛出StackOverflowError的错误,在Sun JDK中可以通过-Xss来指定栈的大小,例如如下代码:

new Thread(new Runnable(){
           public void run() {
              loop(0);
           }
          
           private void loop (int i){
              if(i!=1000){
                  i++;
loop (i);
              }
              else{
                  return;
              }
           }
          
}).start();

当JVM参数设置为-Xss1K,运行后会报出类似下面的错误:

Exception in thread "Thread-0"java.lang.StackOverflowError


3. 堆(Heap)

Heap是大家最为熟悉的区域,它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收,Heap在32位的操作系统上最大为2G,在64位的操作系统上则没有限制,其大小通过-Xms和-Xmx来控制,-Xms为JVM启动时申请的最小Heap内存,默认为物理内存的1/64但小于1G,-Xmx为JVM可申请的最大Heap内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大Heap的大小到-Xmx指定的大小,可通过-XX:MinHeapFreeRatio=来指定这个比例,当空余堆内存大于70%时,JVM会将Heap的大小往-Xms指定的大小调整,可通过-XX:MaxHeapFreeRatio=来指定这个比例,但对于运行系统而言,为了避免频繁的Heap Size的大小,通常都会将-Xms和-Xmx的值设成一样,因此这两个用于调整比例的参数通常是没用的。其实jvm中对于堆内存的分配、使用、管理、收集等有更为精巧的设计,具体可以在JVM堆内存分析中进行详细介绍。

当堆中需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。


4. 方法区域(MethodArea)

方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,可见方法区域的重要性。同样,方法区域也是全局共享的,它在虚拟机启动时在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代,默认为64M,可通过-XX:PermSize以及-XX:MaxPermSize来指定其大小。


5. 运行时常量池(RuntimeConstant Pool)

类似C中的符号表,存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。类或接口的常量池在该类的class文件被java虚拟机成功装载时分配。


6. 本地方法堆栈(NativeMethod Stacks)

JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。

例如有这么一段代码:

public class A {
                   public static void main(String[]args){
           String a="a";
          String b="b";
           String ab="ab";
           System.out.println((a+b)==ab);       // false
           System.out.println(("a"+"b")==ab);   // true
           final String afinal="a";
           String result=afinal+"b";
           System.out.println(result==ab);      // true
           String plus=a+"b";
           System.out.println(plus==ab);        // false
             System.out.println(plus.intern()==ab);  // true
    }
}

分析下上面代码执行的结果,可通过javap –verbose A来辅助理解分析。

l (a+b)==ab

a+b是两个变量相加,需要到运行时才能确定其值,到运行时后JVM会为两者相加后产生一个新的对象,因此a+b==ab的结果为false。

l (“a”+”b”)==ab

“a”+”b”是常量,在编译时JVM已经将其变为”ab”字符串了,而ab=”ab”也是常量,这两者在常量池即为同一地址,因此(“a”+”b”)==ab为true。

l result==ab

result=afinal+”b”,afinal是个final的变量, result在编译时也已经被转变为了”ab”,和”ab”在常量池中同样为同一地址,因此result==ab为true。

l plus=ab

plus和a+b的情况是相同的,因此plus==ab为false。

l plus.intern()==ab

这里的不同点在于调用了plus.intern()方法,这个方法的作用是获取plus指向的常量池地址,因此plus.intern()==ab为true。

在掌握了JVM对象内存分配的机制后,接下来看看JVM是如何做到自动的对象内存回收的,这里指的的是Heap以及Method Area的回收,其他几个区域的回收都由JVM简单的按生命周期来进行管理。


参考文献:

http://javawebsoa.iteye.com/blog/1558776

http://solidsnake2007.iteye.com/blog/1493671


原文地址:JVM内存结构浅析

分享到:
评论

相关推荐

    浅析JVM内存结构和6大区域

    那JVM的内存结构到底是如何呢?JVM做为一个运行在操作系统上,但又独立于os运行的平台,它的内存至少应该包括象寄存器、堆栈等区域。  JVM在运行时将数据划分为了6个区域来存储,而不仅仅是大家熟知的Heap区域,...

    Java中堆内存与栈内存分配浅析

    ### Java中堆内存与栈内存分配浅析 #### 一、引言 在Java编程语言中,内存管理是一项至关重要的技术。程序运行时所使用的内存主要分为两类:堆内存(Heap Memory)和栈内存(Stack Memory)。理解这两种内存类型的...

    Netty实现原理浅析.pdf

    然而,直接内存的使用需要额外的注意,因为它不会受到JVM的垃圾回收机制的管理。 #### 四、事件处理机制 Netty的事件处理机制是基于`ChannelHandler`和`ChannelPipeline`实现的。每个`Channel`都有一个对应的`...

    浅析JAVA之垃圾回收机制.doc

    在Java中,对象通过`new`关键字动态分配内存,这些内存由JVM管理,当对象不再被引用时,JVM会自动回收。而在C/C++中,程序员需要显式地使用`new`和`delete`来分配和释放内存,如果不小心,可能会导致内存泄漏。 4、...

    浅析Java语言中对象的创建过程.pdf

    首先,Java的内存结构是理解对象创建的基础。JVM(Java虚拟机)为每一个运行中的Java应用程序提供了一个唯一的运行环境。JVM的内存主要被划分为三个区域:堆区、栈区和方法区。堆区(Heap)是存放通过new关键字创建...

    [浅析J2EE应用服务器的JAVA类装载器]python回朔异常的模块.docx

    Java类装载器是Java虚拟机(JVM)的一部分,负责加载类文件到JVM内存中。由于Java程序由多个类组成,这些类不是一次性全部加载,而是按需加载。当JVM需要使用某个类时,它会向相应的类装载器发出请求。类装载器负责...

    Java类加载原理浅析

    Java类加载原理是Java虚拟机(JVM)的重要组成部分,它负责将类的.class文件从磁盘或网络中加载到内存,并转化为运行时的数据结构,以便执行程序代码。本文将深入探讨类加载机制,包括加载、连接、初始化三个主要...

    Notes:This is a learning note | Java基础,JVM,源码,大数据,面经

    jvm垃圾收集机制与内存分配策略 jvm类加载机制 Java的内存模型 锁优化 Think In Java Java容器 Java并发 Java Concurrency in Practice 对象的共享 对象的组合 基础构建模块 JavaGC监控与优化 垃圾回收机制 垃圾回收...

    浅析dalvik虚拟机JIT技术的实现.doc

    Dalvik虚拟机是专为Android设计的虚拟机,不同于传统的Java虚拟机(JVM),它采用寄存器架构而非栈架构,旨在减少内存占用,适应移动设备的硬件限制。在Android 2.2版本中,Dalvik虚拟机引入了JIT技术,这一技术通过...

    浅析计算机软件JAVA编程的特点及应用.pdf

    例如,Java的自动内存管理机制减少了程序员管理内存分配和回收的负担,提高了开发效率,同时降低了程序出错的可能性。 Java语言的扩展性强,是指它在运行时能够动态地加载类,这意味着程序可以在运行时扩展自己的...

    学习笔记

    4. **HashMap存储结构浅析** HashMap是Java中常用的数据结构,用于存储键值对。它基于哈希表实现,提供O(1)的平均查找时间。深入理解HashMap的内部工作,包括哈希函数、链表和红黑树的转换,对于提高代码效率有帮助...

    Java基础知识点 - 内容比较全面

    13. **Java代码优化编程**:优化包括减少冗余代码、使用高效算法、避免全局变量、正确使用数据结构等,以及针对JVM的优化,如方法内联、逃逸分析等。 14. **Java数组浅析**:数组是Java中存储固定数量相同类型元素...

    深入浅析Centos 7系统的Tomcat服务器

    【深入浅析Centos 7系统的Tomcat服务器】 Tomcat,这款由Sun的软件架构师詹姆斯·邓肯·戴维森发起的开源项目,如今已成为Apache软件基金会的一部分,是Java Web应用程序的重要载体。Tomcat服务器以其轻量级、高效...

    浅析final,finally,finalize 的区别

    然而,这种方法并不推荐,因为垃圾收集的具体时机由JVM控制,且调用 `finalize()` 的行为并不可靠。 总结来说,`final` 用于声明不可变的类、变量或方法;`finally` 用于确保异常处理后代码的执行;而 `finalize()`...

    浅析使用JDBC操作MySQL需要添加Class.forName("com.mysql.jdbc.Driver")

    加载过程包括将类的二进制字节流转化为内存中的数据结构,并创建一个`java.lang.Class`对象,作为访问方法区中类数据的入口。类加载通常在以下几种情况下触发: 1. 使用`new`关键字创建对象,或者访问或修改类的...

Global site tag (gtag.js) - Google Analytics