参考: http://www.iteye.com/topic/821872
stack是jvm的内存指令区。stack管理很简单,每次操作的数据或者是指令长度都是已知的。java指令代码(java方法),以及常量都保存在stack中。
heap是jvm的内存数据区。heap管理很复杂,每次分配不定长度的内存空间,专门用来保存对象的实例。在heap中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性类型和对象本身的类型标记符号。
对象在heap分配好内存后,需要在stack中保存一个4字节的heap的内存地址,用来定位对象实例在heap中的位置,便于找到该对象。
stack的内存管理是顺序分配的,而且不存在内存回收问题。
1 JVM
简介
JVM
是我们Javaer
的最基本功底了,刚开始学Java
的时候,一般都是从“Hello World
”开始的,然后会写个复杂点class
,然后再找一些开源框架,比如Spring
,Hibernate
等等,再然后就开发企业级的应用,比如网站、企业内部应用、实时交易系统等等,直到某一天突然发现做的系统咋就这么慢呢,而且时不时还来个内存溢出什么的,今天是交易系统报了StackOverflowError
,明天是网站系统报了个OutOfMemoryError
,这种错误又很难重现,只有分析Javacore
和dump
文件,运气好点还能分析出个结果,运行遭的点,就直接去庙里烧香吧!每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了。我想Java
做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢?—— JVM
。
JVM
全称是Java Virtual Machine
,Java
虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare
不一样,那个虚拟的东西你是可以看到的,这个JVM
你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM
也是有这成套的元素,运算器是当然是交给硬件CPU
还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM
自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU
,比如8086
系列的汇编也是可以用在8088
上的,但是就不能跑在8051
上,而JVM
的命令集则是可以到处运行的,因为JVM
做了翻译,根据不同的CPU
,翻译成不同的机器语言。
JVM
中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO
,NO
, JVM
是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。
2 JVM
的组成部分
我们先把JVM
这个虚拟机画出来,如下图所示:
从这个图中可以看到,JVM
是运行在操作系统之上的,它与硬件没有直接的交互。我们再来看下JVM
有哪些组成部分,如下图所示:
该图参考了网上广为流传的JVM
构成图,大家看这个图,整个JVM
分为四部分:
q
Class Loader
类加载器
类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java
程序,然后通过javac
编译成class
文件,那怎么才能加载到内存中被执行呢?Class Loader
承担的就是这个责任,那不可能随便建立一个.class
文件就能被加载的,Class Loader
加载的class
文件是有格式要求,在《JVM Specification
》中式这样定义Class
文件的结构:
ClassFile {
u4
magic;
u2
minor_version;
u2 major_version;
u2
constant_pool_count;
cp_info
constant_pool[constant_pool_count-1];
u2
access_flags;
u2
this_class;
u2
super_class;
u2
interfaces_count;
u2
interfaces[interfaces_count];
u2
fields_count;
field_info
fields[fields_count];
u2
methods_count;
method_info
methods[methods_count];
u2
attributes_count;
attribute_info
attributes[attributes_count];
}
需要详细了解的话,可以仔细阅读《JVM Specification
》的第四章“The class File Format
”,这里不再详细说明。
友情提示:Class
Loader
只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution
Engine
负责的。
q
Execution Engine
执行引擎
执行引擎也叫做解释器(Interpreter)
,负责解释命令,提交操作系统执行。
q
Native Interface
本地接口
本地接口的作用是融合不同的编程语言为Java
所用,它的初衷是融合C/C++
程序,Java
诞生的时候是C/C++
横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++
程序,于是就在内存中专门开辟了一块区域处理标记为native
的代码,它的具体做法是Native Method Stack
中登记native
方法,在Execution Engine
执行时加载native libraies
。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java
程序驱动打印机,或者Java
系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket
通信,也可以使用Web Service
等等,不多做介绍。
q
Runtime data area
运行数据区
运行数据区是整个JVM
的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java
生态系统如此的繁荣,得益于该区域的优良自治,下一章节详细介绍之。
整个JVM
框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!
2 JVM
的内存管理
所有的数据和程序都是在运行数据区存放,它包括以下几部分:
q
Stack
栈
栈也叫栈内存,是Java
程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over
。问题出来了:栈中存的是那些数据呢?又什么是格式呢?
栈中的数据都是以栈帧(
Stack Frame
)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法
(Method)
和运行期数据的数据集,当一个方法
A
被调用时就产生了一个栈帧
F1
,并被压入到栈中,
A
方法又调用了
B
方法,于是产生栈帧
F2
也被压入栈,执行完毕后,先弹出
F2
栈帧,再弹出
F1
栈帧,遵循“先进后出”原则。
那栈帧中到底存在着什么数据呢?栈帧中主要保存3
类数据:本地变量(Local Variables
),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack
),记录出栈、入栈的操作;栈帧数据(Frame Data
),包括类文件、方法等等。光说比较枯燥,我们画个图来理解一下Java
栈,如下图所示:
图示在一个栈中有两个栈帧,栈帧2
是最先被调用的方法,先入栈,然后方法2
又调用了方法1
,栈帧1
处于栈顶的位置,栈帧2
处于栈底,执行完毕后,依次弹出栈帧1
和栈帧2
,线程结束,栈释放。
q
Heap
堆内存
一个JVM
实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:
Permanent Space
永久存储区
永久存储区是一个常驻内存区域,用于存放JDK
自身所携带的Class,Interface
的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM
才会释放此区域所占用的内存。
Young Generation Space
新生区
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:
伊甸区(Eden space
)和幸存者区(Survivor pace
),所有的类都是在伊甸区被new
出来的。幸存区有两个: 0
区(Survivor
0 space
)和1
区(Survivor 1 space
)。当伊甸园的空间用完时,程序又需要创建对象,JVM
的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0
区。若幸存0
区也满了,再对该区进行垃圾回收,然后移动到1
区。那如果1
区也满了呢?再移动到养老区。
Tenure generation space
养老区
养老区用于保存从新生区筛选出来的JAVA
对象,一般池对象都在这个区域活跃。
三个区的示意图如下:
q
Method Area
方法区
方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。
q
PC Register
程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。
q
Native Method Stack
本地方法栈
3 JVM
相关问题
问:堆和栈有什么区别
答:堆是存放对象的,但是对象内的临时变量是存在栈内存中,如例子中的
methodVar
是在运行期存放到栈中的。
栈是跟随线程的,有线程就有栈,堆是跟随
JVM
的,有
JVM
就有堆内存。
问:堆内存中到底存在着什么东西?
答:对象,包括对象变量以及对象方法。
问:类变量和实例变量有什么区别?
答:静态变量是类变量,非静态变量是实例变量,直白的说,有
static
修饰的变量是静态变量,没有
static
修饰的变量是实例变量。静态变量存在方法区中,实例变量存在堆内存中。
问:我听说类变量是在
JVM
启动时就初始化好的,和你这说的不同呀!
答:那你是道听途说,信我的,没错。
问:
Java
的方法(函数)到底是传值还是传址?
答:都不是,是以传值的方式传递地址,具体的说原生数据类型传递的值,引用类型传递的地址。对于原始数据类型,
JVM
的处理方法是从
Method Area
或
Heap
中拷贝到
Stack
,然后运行
frame
中的方法,运行完毕后再把变量指拷贝回去。
问:为什么会产生
OutOfMemory
产生?
答:一句话:
Heap
内存中没有足够的可用内存了。这句话要好好理解,不是说
Heap
没有内存了,是说新申请内存的对象大于
Heap
空闲内存,比如现在
Heap
还空闲
1M
,但是新申请的内存需要
1.1M
,于是就会报
OutOfMemory
了,可能以后的对象申请的内存都只要
0.9M
,于是就只出现一次
OutOfMemory
,
GC
也正常了,看起来像偶发事件,就是这么回事。
但如果此时
GC
没有回收就会产生挂起情况,系统不响应了。
问:我产生的对象不多呀,为什么还会产生
OutOfMemory
?
答:你继承层次忒多了,
Heap
中
产生的对象是先产生
父类,然后才产生子类,明白不?
问:
OutOfMemory
错误分几种?
答:分两种,分别是“
OutOfMemoryError:java heap size
”
和”
OutOfMemoryError: PermGen space
”
,两种都是内存溢出,
heap size
是说申请不到新的内存了,这个很常见,检查应用或调整堆内存大小。
“
PermGen space
”
是因为永久存储区满了,这个也很常见,一般在热发布的环境中出现,是因为每次发布应用系统都不重启,久而久之永久存储区中的死对象太多导致新对象无法申请内存,一般重新启动一下即可。
问:为什么会产生
StackOverflowError
?
答:因为一个线程把
Stack
内存全部耗尽了,一般是递归函数造成的。
问:一个机器上可以看多个
JVM
吗?
JVM
之间可以互访吗?
答:可以多个
JVM
,只要机器承受得了。
JVM
之间是不可以互访,你不能在
A-JVM
中访问
B-JVM
的
Heap
内存,这是不可能的。在以前老版本的
JVM
中,会出现
A-JVM Crack
后影响到
B-JVM
,现在版本非常少见。
问:为什么
Java
要采用垃圾回收机制,而不采用
C/C++
的显式内存管理?
答:为了简单,内存管理不是每个程序员都能折腾好的。
问:为什么你没有详细介绍垃圾回收机制?
答:垃圾回收机制每个
JVM
都不同,
JVM Specification
只是定义了要自动释放内存,也就是说它只定义了垃圾回收的抽象方法,具体怎么实现各个厂商都不同,算法各异,这东西实在没必要深入。
问:
JVM
中到底哪些区域是共享的?哪些是私有的?
答:
Heap
和
Method Area
是共享的,其他都是私有的,
问:什么是
JIT
,你怎么没说?
答:
JIT
是指
Just In Time
,有的文档把
JIT
作为
JVM
的一个部件来介绍,有的是作为执行引擎的一部分来介绍,这都能理解。
Java
刚诞生的时候是一个解释性语言,别嘘,即使编译成了字节码(
byte code
)也是针对
JVM
的,它需要再次翻译成原生代码
(native code)
才能被机器执行,于是效率的担忧就提出来了。
Sun
为了解决该问题提出了一套新的机制,好,你想编译成原生代码,没问题,我在
JVM
上提供一个工具,把字节码编译成原生码,下次你来访问的时候直接访问原生码就成了,于是
JIT
就诞生了,就这么回事。
问:
JVM
还有哪些部分是你没有提到的?
答:
JVM
是一个异常复杂的东西,写一本砖头书都不为过,还有几个要说明的:
常量池(
constant pool
):按照顺序存放程序中的常量,并且进行索引编号的区域。比如
int i
=100
,这个
100
就放在常量池中。
安全管理器(
Security Manager
):提供
Java
运行期的安全控制,防止恶意攻击,比如指定读取文件,写入文件权限,网络访问,创建进程等等,
Class Loader
在
Security Manager
认证通过后才能加载
class
文件的。
方法索引表(
Methods
table
),记录的是每个
method
的地址信息,
Stack
和
Heap
中的地址指针其实是指向
Methods table
地址。
问:为什么不建议在程序中显式的生命
System.gc()
?
答:因为显式声明是做堆内存全扫描,也就是
Full GC
,是需要停止所有的活动的(
Stop
The World Collection
),你的应用能承受这个吗?
问:
JVM
有哪些调整参数?
答:非常多,自己去找,堆内存、栈内存的大小都可以定义,甚至是堆内存的三个部分、新生代的各个比例都能调整。
分享到:
相关推荐
**JVM那些事** 在Java开发中,JVM(Java Virtual Machine)扮演着至关重要的角色。它是Java程序运行的基础,负责解析和执行字节码,实现了跨平台的特性。了解JVM的工作原理对于优化程序性能、排查问题以及深入理解...
"深入Java虚拟机:JVM中的Stack和Heap" Java虚拟机(JVM)是一种运行Java字节码的虚拟机环境,它具有自己的内存管理机制。JVM的内存分为两个部分:Stack(栈)和Heap(堆)。 Stack(栈)是JVM的内存指令区,管理...
### JVM工具、参数调优与调试技巧 #### 一、JVM工具 ##### 1. jps:虚拟机进程状况工具 - **简介**:`jps` 是一个用于显示当前运行的所有Java虚拟机进程(JVMs)的简单工具。它会列出每个JVM的PID(进程ID)以及...
1. **堆(Heap)**:这是Java对象的主要存储区域,分为年轻代(Young Generation)和老年代(Tenured Generation)。年轻代又细分为Eden区、Survivor区(S0和S1),新生的对象主要在Eden区分配,经过几次垃圾收集后...
4. **jhat(Java Heap Analysis Tool)**:当JVM生成堆转储文件后,jhat可以用来分析内存使用情况,查找内存泄漏和对象引用问题。 5. **jmap(Memory Map for Java)**:它可以导出堆内存快照,供其他工具如jhat或...
- **JVM栈(JVM Stack)**:线程私有,每个线程创建时都会创建对应的JVM栈。栈中存储局部变量(包括基本类型和对象引用)、方法参数、Stack Frame。当变量超出作用域,JVM会自动释放栈内存。栈内存分配快速,但大小...
### JVM 详细介绍:掌握 JVM 的各个组成部分与功能 #### 一、Java 源文件编译及执行 Java 应用程序的核心在于源文件的编译与执行。不同于 C/C++ 这类需要针对不同平台进行编译的语言,Java 采用了一种更为灵活的...
JVM的内存管理主要分为堆(Heap)、栈(Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)四个部分。堆用于存放对象实例,是垃圾回收的主要区域。栈负责存储局部变量和方法调用的状态,每个线程...
Java程序运行在JVM上,JVM将内存划分为几个区域,包括堆内存(Heap)、栈内存(Stack)、方法区(Method Area)、程序计数器(PC Register)和本地方法栈(Native Method Stack)。其中,堆内存是所有线程共享的一块...
在Java编程语言中,了解和控制JVM(Java虚拟机)的内存管理是至关重要的,尤其是在性能调优、资源管理和避免内存泄漏等方面。本文将深入探讨如何在Java中获取JVM内存大小,包括堆内存的总量、最大值以及剩余空间,并...
主要包括方法区(Method Area)、堆(Heap)、Java栈(Java Stack)、本地方法栈(Native Method Stack)、程序计数器(Program Counter)五个部分。 4. 垃圾回收机制:JVM中的垃圾回收主要是针对堆区进行的。垃圾...
2. **内存模型**:JVM的内存分为堆内存(Heap)、栈内存(Stack)、方法区(Method Area)、程序计数器(PC Register)和本地方法栈(Native Method Stack)。书中会详细解析各区域的功能、分配规则以及它们之间的...
Stack Frame,非基本类型的对象在 JVM 栈上仅存放一个指向堆上的地址,因此 Java 中基本类型的变量是值传递,而非基本类型的变量是引用传递,Sun JDK 的实现中 JVM 栈的空间是在物理内存上分配的,而不是从堆上分配...
2. **内存模型**:详细讲解JVM内存结构,如堆内存(Heap)、栈内存(Stack)、方法区(Method Area)、本地方法栈(Native Method Stack)和程序计数器(PC Register)等,以及它们各自的作用和相互关系。...
在一些平台上,在有些情况下,javacore也被称为javadump,它包含jvm和应用程序相关的在特定时刻的一些诊断信息,如操作系统,应用程序环境,线程,native stack本地堆,锁,和内存的信息。在生成heapdump文件的时候...
1. **堆内存(Heap)**:这是JVM管理的最大块内存区域,用于存储所有对象实例以及数组。堆内存又分为年轻代(Young Generation)和老年代(Old Generation),年轻代进一步细分为Eden区和两个Survivor区(S0、S1)。 2. **...
Java内存主要分为五个区域:堆(Heap)、栈(Stack)、方法区(Method Area)、程序计数器(PC Register)和本地方法栈(Native Method Stack)。 1. 堆(Heap):这是Java对象的主要存储区域,所有通过new创建的...
1. 堆(Heap):所有对象都在堆中分配内存,是线程共享的区域,分为新生代和老年代。 2. 新生代(Young Generation):新生代进一步划分为Eden区和两个Survivor区(From空间和To空间)。大部分对象在Eden区创建,...
在运行过程中,JVM会管理内存,包括堆(Heap)、栈(Stack)以及方法区(Method Area),这些区域各有其特定的功能和限制。 堆栈溢出(Stack Overflow)是程序运行时常见的错误,通常发生在当一个函数或方法的递归...