最近在和同事朋友聊天的时候,发现一个很让人思考的问题,很多人总觉得JVM将java和操作系统隔离开来,导致很多人不用熟悉操作系统,甚至不用了解JVM本身即可完全掌握Java这一门技术,其实个人的观点是,Java由于有了JVM才使这门语言简单上手,同时也正是因为Java有了JVM才使的Java这门技术很难深入了解。

在C/C++中我们可以很方便的new内存,delete内存,在内存的使用中我们拥有至高的权利,而Java则不行,JVM这一扇大门死死的堵住了内存的操作细节,你无法直接操作内存,所以你能做的就是百分之百的信任JVM给你带来的各种便利都是非常科学和合理的,但是有时候事实并非如此,JVM也不能百分之百的根据你的程序去猜想你所需要的内存资源更谈不上分布情况了,那么JVM能做的就是以他自认为比较合理的方式去为申请,划分内存。

举个最简单的例子,你了解OutOfMemoryError么?笼统来说它就是内存不足引起的,可是他到底是那一块内存溢出所导致的呢?我们只有掌握和了解了JVM的内存划分,才能真的掌握关于内存出现问题的诊断,甚至可以很方便的调优,起到事半功倍的效果,当然也就会让你不再抱怨JVM。

在本文中,我们将重点介绍如下内容:

  • Java的内存划分
  • 各个内存的详解
  • 创建一个对象后的内存分布情况。

第一、Java内存划分

JVM在运行java程序的时候会把内存划分为如下的几部分,如下图所示:

1.1 程序计数器:

首先来说说程序计数器,程序计数器是一个比较小的内存空间,他的作用是什么呢?回想一下CPU的总线结构吧,CPU有三个总线,数据总线,控制总线,地址总线,RAM和CPU交互的时候其实就是逐条的通过一些命令字透过控制总线发送命令,并且将数据通过数据总线进行来回交互,Java在运行时期,其实也是内存在和cpu来回往返的发送各种命令字,并且交换数据,我们的java代码会通过java编译器最终转换成一些底层的命令字(class文件->本地方法将class文件解析转换成标准的命令字)既然是一堆命令字相关的东西,也就存在先运行什么?调用哪个方法,获取那个数据,进行如何的操作等等,在程序计数器中存放的就是这些东西。

我们知道cpu执行的执行时间和分配是由cpu随机或者根据某种cpu的算法规则轮流切换执行某个命令字的,在某一个时刻,他始终只能执行一条命令字,在执行完毕某个命令字之后也需要能够确保回到下一个执行命令字位置的正确性,因此java将这一块内存设计成了私有的,也就是说一个执行的线程都会有一个自己私有的/独享的程序计数器内存空间,该内存空间非常小,另外如果调用的是一个native的方法,则内存计数器不会分配内存空间,并且此内存空间不会出现OutofMemoryError的情况,也是唯一一个。

1.2 虚拟机栈:

虚拟机栈也是线程私有的,每一个方法被执行的时候都会创建一个栈帧,存放在虚拟机栈中,虚拟机栈的结构大致如下所示

每一个方法被调用直到完成的过程,就对应着一个栈帧在虚拟机栈中从如栈到出栈的过程,其中局部变量表就是很多人所说的栈(堆栈地址),他所存放的是基本类型数据和对象的引用类型(reference),其局部变量表中的数据在编译时期就基本上已经确认了,操作栈主要就是压栈或者弹栈,其中动态链接这一部分我个人的理解是动态寻找获取下一个方法的入口地址信息等(个人理解的,有可能不准确)
大多数JVM的虚拟机栈都可以动态扩展的,当无法申请到足够的内存时候会抛出OutOfMemoryError。

1.3 本地方法栈

本地方法栈和虚拟机栈基本上类似,只不过区别是这样的,虚拟机栈是虚拟机本身为java程序开辟的一段内存单元,而本地方法栈是虚拟机调用本地方法时所需要的内存空间。本地方法栈也存在着内存溢出的风险,在SUN提供的JDK中本地方法栈和虚拟机栈合二为一!

1.4 堆

堆在java内存单元中占据着比较大的比重,也是最大的一部分内存单元,在虚拟机启动的时候,该部分的内存就会被创建,所有的对象创建,以及数组内存的申请分配都是在该内存单元上发生的。

由于堆内存所占的比重比较大,因此他也就是java垃圾回收器最关注的一块内存,因此该内存单元也被称为GC堆。如果以后您了解了GC机制,您会知道,Java允许内存单元不连续,只要逻辑上是连续的即可,这部分的内存也是可以进行扩展的,在启动虚拟机时我们可以通过-Xmx,-Xms进行控制,当堆中的内存再也申请不到的时候就会抛出内存溢出的异常,另外该内存空间是线程共享的,我们经常使用到的锁其实就是在这部分内存中活动。

1.5 方法区

方法区也是各个内存的共享区域,用于存放虚拟机的类加载信息,常量,变量,静态变量等数据,每一个方法的执行其实就是在这部分内存中运行,要操作的数据是在虚拟机栈中获取,也可以理解为方法的活动区域。

1.6 运行时常量池

java在编译的时候会将我们定义的final类型做自动的优化存放在常量池中,这样可以提高访问和寻址的速度,因为常量不会再运行期间变化,也就是说他的数据单元地址不会发生改变,一次寻址即可,他其实是方法区的一部分,在android中执行完编译之后除了有class文件还会有一个idx文件,该文件其中的一些数据就是将java文件中常量字面量,这也是为什么一些有经验的人在编写代码的时候非常喜欢用final进行类型的修饰,在方法的参数中,方法体中,类变量中,只要是认为不可变的都进行final声明,试图告诉java虚拟机,这样的变量存放在常量池中,提高寻址速度。

1.7 直接内存

还记得《java NIO》一书中,作者在描述NIO为什么比传统的IO快得原因么?传统的IO进行数据读写操作,首先是Java程序操作java堆中的内存,java堆又拷贝本地方法内存中的数据,java本地方法通过操作系统进行文件的操作,而直接内存就可以不通过java堆和本地方法堆的来回拷贝,而直接操作堆内存以外的内存,这样会节省很多来回数据复制的时间。

2、对象访问

其实上文中的很多内容,也是通过参阅jvm规范以及别人写的jvm得来的,因为它并不像我们进行一个加法运算或者方法调用那样理所当然的去分析,如果碰到很底层的问题或者JVM内部的问题,尽可能的了解到,并且记住他的大概原理,如果要我自己去论证JVM的内存分布是否真的是如此,确实是一个很艰巨的任务,当然借助一些工具也可以看到他们的大概分布情况,在以后的文章中介绍JVM相关工具的时候我们在一起研究探索吧。
了解了上面的知识,为了将概念性的东西,转换为比较直观的东西,我们来看看对象访问的一个过程,并且用图解的方式来说明。
 
一个很简单的Object obj = new Object()其实涉及的内存单元有虚拟机栈内存,堆内存,方法区,程序计数器等。当执行了new方法之后,jvm会在堆内存中开辟一块内存单元存放obj信息,与此同时,obj的类型信息,父类,接口,方法等信息会被存放在方法区中,堆内存为了能够访问到这些信息,除了存放obj的信息之外,还会存放访问这些信息的地址信息。与此同时产生的引用,基本数据类型也会存放到栈内存之中,编译时期生成的命令字也理所当然的存放到了程序计数器之中,如下图所示。

上图所描述的是通过句柄的方式访问方法区,并且给栈内存提供访问方式,下图中将是通过指针的方式访问方法区,提供栈内存访问基本上没有太多的变化,如下图所示。
可以看到在对象实例中就包含了方法区的地址指针,而不用再存在一个句柄空间专门存放,这样当栈访问堆中的引用时,堆就可以直接获取方法区的数据。

好了,本文就大概描述到这里,JVM是一个非常神秘的东西,很多东西我们只有记住的份,因为内存你说了不算,只有他才是操作内存的入口。但是了解他的内存结构,我们就能有效的合理的分配堆栈内存,提高程序运行效率,在下一篇文章中,我们一起设计一些能直接影响到java不同内存的程序,一起分析他们的原因和如何规避,如何调试等。本文中肯定存在很多偏颇之处,希望各位能够直言不讳,写作博客的目的就是为了进步,如果写出来仅此而已,那么意义不大,另为,如果本文中有些欠妥的东西,千万不要以讹传讹。

JVM之--Java内存结构(第一篇)的更多相关文章

  1. 【Java 之 JVM】Java内存结构概述

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWl3dXpoaWxpbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...

  2. jvm(1)---java内存结构

    jvm主要由三个子系统构成:类加载子系统,运行时数据区,执行引擎 运行时数据区主要包括: 1.本地方法栈:登记native方法,执行时加载本地方法库 2.程序计数器:就是一个指针,用来存储指向下一条执 ...

  3. Java内存结构详解

    Java内存结构详解 Java把内存分成:栈内存,堆内存,方法区,本地方法区和寄存器等. 下面分别介绍栈内存,堆内存,方法区各自一些特性: 1.栈内存 (1)一些基本类型的变量和对象的引用变量都是在函 ...

  4. Java 内存结构备忘录

    本文详细描述了 Java 堆内存模型,垃圾回收算法以及处理内存泄露的最佳方案,并辅之以图表,希望能对理解 Java 内存结构有所帮助.原文作者 Sumith Puri,本文系 OneAPM 工程师编译 ...

  5. Java内存结构、类的初始化、及对象构造过程

    概述 网上关于该题目的文章已经很多,我觉得把它们几个关联起来讲可能更好理解一下.与其它语言一样,它在执行我们写的程序前要先分配内存空间,以便于存放代码.数据:程序的执行过程其实依然是代码的执行及数据的 ...

  6. java内存结构学习的一种打开方式

    用Java开发已经四年,中途学了python,Scala,接触这些开发语言后,总感觉Java就像老奶奶裹脚——又臭又长.然,Java虐我千百遍,我待Java如初恋.聊起Java,不得不谈Java的内存 ...

  7. JVM之Java内存区域

    JVM之Java内存区域 世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本来就是一个不断追求完美的过程. 一.JAVA内存区域 谈及JAVA虚拟机运行时数据区域就不得不祭出这张经典的图了: ...

  8. JVM之---Java内存分配参数(第四篇)

    1.内存分配参数---大纲 Ø如何设置堆内存 Ø如何设置栈内存 Ø如何设置方法区 Ø如何设置对的分配比率 Ø设置参数打印堆栈: ØJava程序的两种模式:Server&Client 2.设置堆 ...

  9. java内存结构JVM——java内存模型JMM——java对象模型JOM

    JVM内存结构 Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途.其中有些区域随着虚拟机进程的启动而存在,而有些区 ...

随机推荐

  1. EXCEL表格常用函数使用的难点

    1.  INDIRECT(ref_text,逻辑值) 返回由文本字符串指定的引用.此函数立即对引用进行计算,并显示其内容.ref_text,文本引用说明, (1) A1-样式的引用(逻辑值,T,缺省) ...

  2. Jquery小东西收集

    1. $(document).ready(),$(function(){}),$(window).load(),window.onload的关系与区别 $(document).ready(functi ...

  3. MemCache缓存和C#自带的Cache缓存

    1.MemCache: //初始化 static SockIOPool _pool; // 创建Memcached private static MemcachedClient Create(stri ...

  4. 利用Range改变光标位置

    先上代码,代码取自网上某插件中 function caret(begin, end) { if (this.length == 0) return; if (typeof begin == 'numb ...

  5. 用Hopper搞定Mac迅雷的会员以及离线下载功能

    转自 用Hopper搞定Mac迅雷的会员以及离线下载功能 先定位Mac迅雷的可执行文件 snakeninnys-iMac:~ snakeninny$ ls /Applications/Thunder. ...

  6. Hive学习之五 《Hive进阶—UDF操作案例》 详解

    hive—UDF操作 udf的操作过程: 在HIVE会话中add 自定义函数的jar文件,然后创建function,继而使用函数. 下面就以下面课题为例: 课题:统计每个活动的PV和UV 一.Java ...

  7. 【USACO 2.4.5】分数化小数

    [描述] 写一个程序,输入一个形如N/D的分数(N是分子,D是分母),输出它的小数形式. 如果小数有循环节的话,把循环节放在一对圆括号中. 例如, 1/3 =0.33333333 写成0.(3), 4 ...

  8. 各种开源协议介绍 BSD、Apache Licence、GPL V2 、GPL V3 、LGPL、MIT

    现今存在的开源协议很多,而经过Open Source Initiative组织通过批准的开源协议目前有58种(http://www.opensource.org/licenses /alphabeti ...

  9. jQuery键盘控制方法,以及键值(keycode)对照表

    键盘控制应用范围非常广泛,比如快捷键控制页面的滚动:在填写表单时候,限制输入内容:或者是屏蔽复制.粘贴.退后等功能.这里说说用jQuery怎么来实现.个人觉得jQuery比原生态的JS好用,代码简单清 ...

  10. 编写可维护的javascript代码--- 2015.11.21(基本格式化)

    1.1 每行的编码需要控制在80字符. 1.2 改用:的地方必须用上. 1.3 缩进用2个制表符,不过4个也可以. 1.4 当代码一行显示不全需要折行显示,这里我暂且假定缩进为4个字符. 1.5 如果 ...