您的位置:首页 > 其它

JVM学习总结--体系结构、内存管理和垃圾回收

2016-05-04 17:26 627 查看
这片其实是一个总结帖,并非完全原创,里面包含很多文章链接,不喜误入!!!
jvm内容较多从虚拟机装载到类加载器、类执行、内存分配、垃圾回收,有很多的知识需要理解。这里主要集中内存管理和垃圾回收。更多虚拟机原理参考下面两篇博客。

jvm原理:/article/7735323.html

jvm原理和优化:http://blog.csdn.net/ning109314/article/details/10411495/

前言

java的jvm不只有sun一家,我们使用的sun的jvm为HotSpot,可以用命令java -version看一下

C:\Users\Administrator>java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)


另外两种为OracleJRockitIBM JVM。如果有更多请在下面回复。订制自己的虚拟机恐怕也只有Oracle和IBM这种大公司有这种实力。以下的jvm都是以HotSpot JVM为例。

三大虚拟机垃圾回收机制的比较http://www.tuicool.com/articles/JVRzAj

一、java虚拟机体系结构

JVM可以由不同的厂商来实现。由于厂商的不同必然导致JVM在实现上的一些不同,然而JVM还是可以实现跨平台的特性,这就要归功于设计JVM时的体系结构了。一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、数据类型和指令这些部分,它们描述了JVM的一个抽象的内部体系结构,其目的不光规定实现JVM时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部行为。每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分。



图-JVM的体系结构
关于体系结构的详述请参考:
1.java虚拟机深入研究http://www.kuqin.com/java/20080525/8907.html
2.百度百科一下jvm

但是当你读完上面的深入研究对jvm体系结构了解多少呢?最核心的运行数据区域即java虚拟机的堆,这里面才是内存管理、垃圾回收需要做的。java虚拟机的内存管理和垃圾回收其是一回事,以下就只用垃圾回收这个词了。

关于java jvm的栈和堆

上图中java中的栈(stack)是由java管理,推(heap)是交给程序(员)的。
1.当前线程的栈存储基本变量和堆里的对象引用(指针),基本类型和引用的对象分别存于不同的内存区域,一个是栈一个是堆。
String str = new String("abc");
String str1 = "abc";

int aa = 1;
解释:aa是基本类型存于栈内;str是对象,他的引用存于栈内,str这个对象存于堆中;str1又是一个基本类型(虽然他是string但他不是new出来的)存于栈内。
补充1:栈内的基本类型和对象的引用如果相同,那么栈的指针都指向同一个栈内的区域(值)。改变变量出现不同,栈的指针发生变化指向其他区域或者分配内存并指向新分配的内存区域。
补充2:vm(virtual machine)不是vs(virtual server),虚拟机像真正的机器一样管理着堆和栈,堆是可以动态分配内存的,而栈的内存大小是固定的。所以jvm中堆和非堆(并不是栈)都可以指派最小和最大内存。

jvm体系结构中的运行时数据区(runtime data area)

1.Java堆(Java Heap)

对于大多数应用来说,Java 堆是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
Java堆中唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
这一点在Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配①,但是随着JIT 编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换②优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称作“GC堆”(Garbage Collected Heap)。
根据Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms 控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常

java虚拟机内存:/article/9472325.html

栈和堆的区别:http://android.blog.51cto.com/268543/50100/

2.方法区(又叫Non-heap)

参考文章:
JVM学习笔记-方法区(Method Area):/content/2615586.html
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
-XX:MaxPermSize为最大非堆内存。
补充:-Xmx的值和-XX:MaxPermSize的总和不可超过JVM内存的最大限制,-Xms和-XX:MaxPermSize一般设置为相同。
-Xms128M

-Xmx512M

-XX:PermSize=64M

-XX:MaxPermSize=128M
更多jvm参数,比如GC策略,不是这篇文章陈述内容。

2.1. 运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后存放到方法区的运行时常量池中。

3.java stacks

每一个JVM线程均有一个私有的栈,与线程是同时创建的。一个JVM的栈中存放了很多frames,JVM的栈和C语言中的栈的概念很类似,它是用来存放本地变量和partial结果的(partial results),并且在方法调用和返回时起到一定作用。由于对栈的操作只有push和pop,所以可以用堆分配。JVM的栈在内存中不要求连续存放。

JVM的规范中允许栈可以是固定尺寸的,也可以根据需要动态扩展或收缩其尺寸。开发或者用户可以设定栈的初始尺寸,对于动态栈空间的情况,也可以设定栈的最大和最小值。

下面是与栈操作的一些相关异常:

1. 如果一个线程所需的栈空间大于允许值,则抛出StackOverFlowError;

2. 如果一个线程的栈可以动态扩展,但当需要扩展栈空间时发现内存空间不足;或者在初始化栈空间时,就发现内存不足了,则抛出OutOfMemoryError;

java虚拟机内存:/article/9472325.html

补充:如前面java的栈和堆提到的,java stacks中存放基本数据类型和引用。

4.PC Register(Program Counter Register)

即程序计数器寄存器

JVM支持同一时间同时运行多个线程,每一个线程都有它们自己的pc register。在同一个时刻,JVM的线程只能运行一个单独方法中的代码,此方法称为该线程的当前方法(Current Method)。 如果这个当前方法不是native的,PC register就指向正在被执行的JVM指令的地址。而如果一个当前方法是native的,则pc register中的值是不确定的。Pc register有足够的空间来存储returnAddress或者native指针。

5.本地方法栈(native method stack)

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun
HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

JVM学习笔记-本地方法栈:/content/2615602.html

补充:本地方法栈相当于java栈的扩展,java可能需要调用其他接口、其他语言接口,这时候就需要本地方法栈。

java jvm GC(垃圾回收)

有了jvm体系结构的认识,那么jvm的GC其实就是管理内存,回收java堆(heap)中的对象。我们都知道java所有的GC都不需要程序员显示的执行,因为在分配内存的时候jvm会根据实际环境(jvm参数)有若干种GC收集器来调用。

java的jvm包括以下四种GC收集器:Serial收集器(client级别默认的GC)、Parallel(并行)收集器(server级别默认采用的GC),CMS收集器、G1收集器

关于收集器请参考:

垃圾收集器Serial 、Parallel、CMS、G1:http://www.zicheng.net/article/55.htm



图-jvm的内存分配

上面的图并不是全部,实际是存在于年轻世代young Generation,年老世代Tenured Generation,永久世代Permanent Generation



1.Young Generation(年轻代)

年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(命名为A和B)。当对象在堆创建时,将进入年轻代的Eden Space。垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制到Old Gen。同时,在扫描Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个持久化对象,则将其移到Old
Gen。扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色(即下次垃圾回收时会扫描Eden Space和BSuvivor Space。这么做主要是为了减少内存碎片的产生。

我们可以看到:Young Gen垃圾回收时,采用将存活对象复制到到空的Suvivor Space的方式来确保尽量不存在内存碎片,采用空间换时间的方式来加速内存中不再被持有的对象尽快能够得到回收。

2.Tenured Generation(年老代)

年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁(譬如可能几个小时一次)。年老代主要采用压缩的方式来避免内存碎片(将存活对象移动到内存片的一边,也就是内存整理)。当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。

3.Permanent Generation(持久代)

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。

总结

通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的MethodArea,不属于Heap。

对于Minor GC 和 Full GC的解释:

新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具

备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常

会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里

就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10

倍以上。
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

至此内存分配、垃圾回收总结完了。
问题,之前只是基础知识,有更多需要了解和动手尝试:

1.什么时候jvm调用GC去执行垃圾回收?
2.哪一种垃圾回收机制更好?
3.jvm的参数需要手动设定吗,如何设定更好?
4.jvm的栈(stacks)动态分配大小吗?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: