Java GC

Java技术体系

从广义上讲,Clojure、JRuby、Groovy等运行于Java虚拟机上的语言及其相关的程序都属于Java技术体系中的一员。如果仅从传统意义上来看,Sun官方所定义的Java技术体系包括以下几个组成部分:

  • Java程序设计语言
  • 各种硬件平台上的Java虚拟机
  • Class文件格式
  • Java API类库
  • 来自商业机构和开源社区的第三方Java类库

我们可以把Java程序设计语言、Java虚拟机、Java API类库这三部分统称为JDK(Java Development Kit),JDK是用于支持Java程序开发的最小环境。

另外,可以把Java API类库中的Java SE API子集[1]和Java虚拟机这两部分统称为JRE(Java Runtime Environment),JRE是支持Java程序运行的标准环境。

以上是根据各个组成部分的功能来进行划分的,如果按照技术所服务的领域来划分,或者说按照Java技术关注的重点业务领域来划分,Java技术体系可以分为4个平台,分别为:

  • Java Card:支持一些Java小程序(Applets)运行在小内存设备(如智能卡)上的平台。
  • Java ME(Micro Edition):支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,并加入了针对移动终端的支持,这个版本以前称为J2ME。
  • Java SE(Standard Edition):支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核心API,这个版本以前称为J2SE。
  • Java EE(Enterprise Edition):支持使用多层架构的企业应用(如ERP、CRM应用)的Java平台,除了提供Java SE API外,还对其做了大量的扩充[3]并提供了相关的部署支持,这个版本以前称为J2EE。

JVM

Java Virtual Machine,Java虚拟机。是java编译后的.class文件(字节码文件)与硬件系统之间的接口,也就是说用来运行.class文件。JVM实现了Java最重要的特性:平台无关性

编译后的 Java 程序指令并不直接在硬件系统的 CPU 上执行,而是由 JVM 执行,JVM屏蔽了与具体平台相关的信息。JVM对字节码文件进行解释执行,把字节码翻译成相关平台上的机器指令。

javac 是收录于 JDK 中的 Java 语言编译器。我们使用javac命令编译Java源文件,得到.class文件。然后使用java命令执行.class文件(也就是使用JVM运行.class文件)。使用jar命令可对字节码文件以及配置文件进行打包(可对一个由多个字节码文件和配置文件等资源文件构成的项目进行打包)。

所以不熟悉java的同学要注意了,JVM并不是用来编译和执行java的,JVM只负责执行字节码文件,编译java文件由 javac来完成

java有一套公用的规范:Java Language and Virtual Machine Specifications

目前有三大Java虚拟机:HotSpot,oracle JRockit,IBM J9。

JRockit是oracle发明的,用于其WebLogic服务器,IBM JVM是IBM发明的用于其Websphere服务器,不同的JDK可能存在兼容性问题。

JRockit和J9不存在永久代这种说法。这里只讨论HotSpot虚拟机,这也是目前使用的最多的JVM。Sun JDK7 HotSpot虚拟机的内存模型如下图所示:

JVM的内存可分为:线程栈、堆、静态方法区,native方法使用的是直接内存,不包含在JVM中。Java NDK可以调用C/C++。

方法区

方法区是可供各线程共享的运行时内存区域

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

在不同的JDK版本中,方法区中存储的数据是不一样的。在JDK1.6及之前,运行时常量池是方法区的一个部分,同时方法区里面存储了类的元数据信息、静态变量、即时编译器编译后的代码(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)等。在JDK1.7及以后,JVM已经将运行时常量池从方法区中移了出来,在JVM堆开辟了一块区域存放常量池。

方法区和永久代的关系

方法区(method area)只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。

永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间(Metaspace),‑XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。

永久代(PermGen)包含了JVM需要的应用元数据,这些元数据描述了在应用里使用的类和方法。注意,永久代不是Java堆内存的一部分。永久代存放JVM运行时使用的类。永久代同样包含了Java SE库的类和方法。永久代的对象在full GC时进行垃圾收集。

对于习惯在HotSpot虚拟机上开发、部署程序的开发者来说,很多人都更愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。对于其他虚拟机(如BEA JRockit、IBM J9等)来说是不存在永久代的概念的。原则上,如何实现方法区属于虚拟机实现细节,不受虚拟机规范约束,不同的JVM厂商,针对自己的JVM可能有不同的方法区实现方式。但使用永久代来实现方法区,现在看来并不是一个好主意,因为这样更容易遇到内存溢出问题(永久代有-XX:MaxPermSize的上限,J9和JRockit只要没有触碰到进程可用内存的上限,例如32位系统中的4GB,就不会出现问题),而且有极少数方法(例如String.intern())会因这个原因导致不同虚拟机下有不同的表现。因此,对于HotSpot虚拟机,根据官方发布的路线图信息,现在也有放弃永久代并逐步改为采用Native Memory来实现方法区的规划了,在目前已经发布的JDK 1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。

也就是说永久代是JVM规范中方法区的一种实现方式

Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是必要的。在Sun公司的BUG列表中,曾出现过的若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。

根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

  1. 堆大小=新生代+老年代。默认情况下,新生代(Young generation)占$\frac{1}{3}$的堆空间大小,老年代(Old generation)占$\frac{2}{3}$的堆空间大小。
  2. 新生代被细分为一个Eden(伊甸园)和两个Survivor区域,这两个Survivor区域分别被命名为fromto以示区分。默认情况下,它们的空间大小关系是Eden:from:to=8:1:1
  3. JVM每次只会使用Eden和其中一块Survivor区域来为对象服务,所以无论什么时候,总有一块Survivor区域空闲着。因此,新生代实际上可以用的内存空间为90%的新生代空间。

下面要讲的GC(garbage collection)就是针对进行内存回收。

GC的策略

  1. 分区的目的是为了方便对不同的区采取不同的回收策略。GC分为两种:新生代中的Minor GC,老生代中的Major GC;两个加在一起叫做Full GC,由于Major GC的前提是Minor GC,所以发生Major GC就一定代表着Full GC,。新生代是GC收集垃圾的频繁区域。
  2. 数据会首先分配到Eden区中(当然也有特殊情况,如果是大对象那么会直接放到老年代(大对象是指需要大量连续内存的java对象)),当Eden没有足够的空间的时候就会触发JVM发起一次Minor GC。如果对象经历一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中,并将其年龄设为1,对象在Survivor中每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。新生代是GC收集垃圾的频繁区域。
  3. 新生代主要存放的是那些很快就会被GC回收掉的或者不是特别大的对象(要看你是否设置了-XX:PretenureSizeThreshold参数了)。-XX:PretenureSizeThreshold 的默认值和作用
  4. -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -Xss1M Xms是JVM初始堆的大小,Xmx是JVM最大堆大小,Xmn是新生代大小,PermSize是永久代的初始大小,MaxPermSize是永久代的最大大小,Xss是每个线程栈的大小。JVM系列三:JVM参数设置、分析

    • Xms64m or -Xms64M
    • Xmx1g or -Xmx1G
    • Can also use 2048MB to specify 2GB
    • Also, make sure you just use whole numbers when specifying your arguments. Using -Xmx512m is a valid option, but -Xmx0.5g will cause an error.
  5. 虚拟机初始化时已经设定了各部分的内存大小,分为三部分:

    • 新生代:新创建的对象
    • 老年代:经过多次垃圾回收没有被回收的对象或者太大的对象
    • 永久代:JVM自身使用的内存,包含类信息等

新生代策略

新生代采用复制算法。将新生代分为3个区:较大的Eden和两个较小的Survivor。发生在新生代的GC为Minor GC,在Minor GC时会将新生代中还存活着的对象复制进一个Survivor中,然后对Eden和另一个Survivor进行清理。所以平常可用的新生代大小为Eden大小+一个Survivor大小。

你可能感兴趣的:(垃圾回收,gc,java)