Java的一些基础小知识之JVM与GC (转)
一、JVM是什么
Java虚拟机(英语:Java Virtual Machine,缩写为JVM),又名爪哇虚拟器,一种能够运行Java bytecode的虚拟机,以堆栈结构机器来进行实做。最早由太阳微系统所研发并实现第一个实现版本,是Java平台的一部份,能够运行以Java语言写作的软件程序。
Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。通过对中央处理器(CPU)所执行的软件实作,实现能执行编译过的Java程序码(Applet与应用程序)。
作为一种编程语言的虚拟机,实际上不只是专用于Java语言,只要生成的编译文件符合JVM对载入编译文件格式要求,任何语言都可以由JVM编译运行。除外,除了甲骨文,也有其他开源或闭源的实现。
——维基百科
|
这个描述还是很简单易懂的,虚拟机的这种机制带给了代码一种全新的生命力,就是一处编绎,到处运行。当然美好的事情总归是有些缺陷的。因为要在一台物理机器上搭建一套虚拟的体系,以此来解决各个硬件与系统间的差异问题,确实是件很美好的事情,但同时损失的自然就是运行时的效率。
二、JVM的体系规格
java的这套体系是种开放的规格,只要能按规格编绎出来的程序都可以跑在JVM上。JVM定义了控制Java代码解释执行和具体实现的五种规格,它们是:
- JVM指令系统
- JVM寄存器
- JVM 栈结构
- JVM 碎片回收堆
- JVM 存储区
三、JVM的工作原理
- 操作系统加载JVM(windows)
1.创建JVM装载环境和配置2.装载JVM.dll3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例4.调用JNIEnv实例装载并处理class类
- JVM加载类
下面看看一个java代码是怎么运行起来的:
四、JVM的内存管理
这里有一篇文章讲的很详细:http://developer.51cto.com/art/201303/387175.htm
JVM的内存结构分为6块:PC Register(PC寄存器)、JVM堆、JVM栈、方法区域、运行时常量、本地方法堆栈。如下图示意:
![](http://images0.cnblogs.com/blog/22162/201502/052023153281594.png)
对于开发来说主要关注的还是堆和栈。
JVM一些参数设置:
-Xss:这个参数就是用来指定栈的大小
-Xms:设置JVM启动时最小的堆内存大小
-Xmx:设置JVM堆的最大内存大小
-XX:PermSize及-XX:MaxPermSize指定方法区域(MethodArea)的初始值与最大值
MethodArea对应的是持久代(PermanetGeneration),所以设置Perm的大小很重要,否则会报java.lang.OutOfMemoryError: PermGen space。
五、垃圾收集器(Garbage Collector,GC)
垃圾收集器这个东西对于程序员来说可谓是一种解脱,可以不用显式释放内存了。这种神奇的疗效还是要看看他的基本原理了解一下情况才行。下面摘了一段话:
有几种垃圾收集的基本策略:引用计数、标记-清除、标记-整理 (mark-compact) 和复制。此外,一些算法可以以 增量 方式完成它们的工作(不需要一次收集整个堆,使得收集暂停时间更短),一些算法可以在用户程序运行时运行( 并发收集)。其他算法则必须在用户程序暂停时一次进行整个收集(即所谓的 stop-the-world收集器)。最后,还有混合型的收集器,如 1.2 和以后版本的 JDK 使用的分代收集器,它对堆的不同区域使用不同的收集算法。
——摘自developerWorks 中国
|
这里就回收的算法来看主要就上面列出来的几种,其中比较重要的是“复制”和“标识-整理”有必要理解一下。
复制收集器就是划分两个对等空间,其中一个放活跃对象,另一个空着,等第一个满了就复制活跃对象到另一个空着的,然后将这两个空间角色换一下。这里有个重点就是只复制活跃对象。活跃对象通常就是可到达的对象也就是不用回收的内存,换言之就是除此之外的就是垃圾,那么这样的好处就是复制一次后,将那些垃圾一次回收就行了。而且复制后内存空间是会经过整理的连续的,不会有碎片问题。
标识-整理收集器算法结合了标记-清除和复制,代价是增加了一些收集复杂性。与标记-清除类似,标记-整理是两阶段过程,在标记阶段访问并标记每个活跃对象。然后,复制标记的对象,使所有活跃对象被整理到堆的底部。如果每一次收集时进行彻底的整理,那么得到的堆就类似于复制收集器的结果 ―― 在堆的活跃部分与自由部分有明确的界线,这样分配成本与复制收集器相当。长寿的对象趋向于沉在堆的底部,这样就不会像在复制收集器中那样反复复制它们。
既然是垃圾回收,那么自然有一个问题什么是垃圾?
由分配器分配的,但是用户程序不可到达的内存块。不可到达是什么意思?可以以两种方式之一访问内存块 ―― 或者用户程序在 根 (root)中有对这一内存块的引用,或者在另一个可到达的块中有对这个块的引用。
——摘自developerworks
|
这里面提到了一个很关键的点,就是根(Root),那什么才是Root?这个得好好了解一下。下面是GC Root的种类:
- Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots,.
- Thread - 活着的线程
- Stack Local - Java方法的local变量或参数
- JNI Local - JNI方法的local变量或参数
- JNI Global - 全局JNI引用
- Monitor Used - 用于同步的监控对象
- Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此就只有留给分析分员去确定哪些是属于"JVM持有"的了。
分代垃圾收集
有人做过分析,应用程序堆中的对象有98%的对象存活的时间比较短,还有一些是会存活很长,甚至会随着应用程序整个生命周期。将这两类对象可以称为年轻代和持久代。
分代收集器(generializational collector)将堆分为多个代。在年轻的代中创建对象,满足某些提升标准的对象,如经历了特定次数垃圾收集的对象,将被提升到下一更老的代。分代收集器对不同的代可以自由使用不同的收集策略,对各代分别进行垃圾收集。
——摘自developerworks
|
可以看出分代垃圾收集主要是将对象以生命周期进行归类,然后针对不同的类别使用不同的回收算法,这样就可以更优的进行跟踪回收。
JDK 1.4.1 默认收集器
在默认情况下,JDK 1.4.1 将堆分为两部分,一个年轻的代和一个老的代(实际上,还有第三部分――永久空间,它用于存储装载的类和方法对象)。借助于复制收集器,年轻的代又分为一个创建空间(通常称为 Eden)和两个生存半空间。 老的代使用标记-整理收集器。对象在经历了几次复制后提升到老的代。小的收集将活的对象从 Eden 和一个生存半空间复制到另一个生存半空间,并可能提升一些对象到老的代。大的收集(major collection)既会收集年轻的代,也会收集老的代。 System.gc() 方法总是触发一个大的收集,这就是应该尽量少用(如果不能完全不用的话) System.gc() 的原因之一,因为大的收集要比小的收集花费长得多的时间。没有办法以编程方式触发小的收集。 ——摘自developerworks |
总的机制就是让寿命长的对象逐步往老的代中放,这样可以优化收集的时机,减少收集暂停给应用程序带来的影响。基本上GC的简单机制就这些内容。
参考文:
http://www.cnblogs.com/5207/p/4275882.html