JVM之内存篇

JAVA虚拟机之内存篇

目的:通过对JVM中内存结构的了解,可以处理常见的大部分内存溢出问题,并能大致定位到原因;更高级的希望知道各种框架的不同机制会对JVM内存占用造成哪些影响;

1. Java虚拟机是什么

要理解java虚拟机,你首先必须意识到,当你说“Java虚拟机”时,可能指的是如下三个不同的东西:

1. 抽象规范

2. 一个具体的实现(JVM+基本API)

3. 一个运行中的虚拟机实例

Java虚拟机的生命周期

 

      一个运行时的Java虚拟机实例的天职就是:负责运行一个 Java程序。当启动一个Java程序时,一个虚拟机实例也就诞生了。当该程序关闭退出是,这个虚拟机实例也就随之消亡。如果在同一个计算机上同时运行三 个程序,将得到三个Java虚拟机实例。每个Java程序都运行于它自己的Java虚拟机实例中。

      Java虚拟机实例通过调用某个初始类的main()方法来运行一个Java程序,此方法将作为该程序初始线程的启动,任何其他的线程都是由这个初始线程启动的。

      在Java虚拟机内部有两种线程:守护线程与非守护线程。 守护线程通常是由虚拟机自己使用的,比如执行垃圾收集任务的线程。但是,Java程序也可以把他创建的任何线程标记为守护线程。而Java程序中的初始线程---------就是开始于main()的那个,是非守护进程。只要还有非守护线程在运行,那么这个Java程序也在继续运行(虚拟机仍然存活)。当 该程序中所有的非守护线程都终止时,虚拟机实例将自动退出。假若安全管理器允许,程序本身也能够通过调用Runtime类或者System类的exit()方法来退出。

Java虚拟机体系结构

我们主要关注的是JVM的内存结构,即runtime dataareas部分所示:

Method area(方法区):

主要用来存放类型信息,JVM读入class文件后,会对其进行解析,解析后的类型信息就放在方法区中,包括

【该类型的常量池】:存放该类定义的常量(final变量)

【字段信息】:变量名,类型,修饰符;

【方法信息】:方法名,返回类型,方法参数的数目和类型,修饰符;

【除了常量意外的所有类(静态)变量】:静态变量;

【一个到ClassLoader的引用】:类的装载器信息;

【一个到Class类的引用】:该class文件所对应的CLASS类实例;

【方法表】:存放非抽象类的方法,以方便对象调用;

Heap area(堆区)

用来存放对象数据,在JVM中对象的数据仅包括对象中所包含的非静态变量,其方法信息实际上是存在方法区中的;

Java Stack(JAVA栈)

JVM中的每一个线程都拥有一个JAVA栈

Pc register(PC寄存器)

用来存放每个线程当前指令的地址,故一般来说为32或64位,如果有多个线程,则有多个PC寄存器;很显然,其占用的内存非常之小,以至于在实际JVM实现中根本未将其专门分成一块区域,而且也不可能发生溢出;

Native mehthods stacks(本地方法栈)

JVM的基本API最终调用的是本地方法,而且JAVA也允许在JAVA中调用本地方法,本地方法调用也会产生线程栈;

 

需要特别说明的是:JAVA的垃圾收集算法仅仅针对的是Heap area(堆区),JVM规范中的内存区域指的是JVM必须包含的内存区,而JVM实现也可以包含其它的内存区域;

 

下面具体到一个具体的JAVA虚拟机实现(HOTSPOT JVM(SUN))来看其内存体系结构:

HOTSPOT JVM的内存组成:

1. 整个JAVA程序(即一个JAVA进程)所占用的内存即:1 JVM Process Heap,32位OS来说最多约2G,对于64位OS则更多(相当于无限);

2.  JVM规范中的Heap area(堆区)对应于JAVA ObjectHeap(可使用-Xms –Xmx来手动指定最大内存);

该区域又可分为:Young Generation和Old Genaration,

而Young Generation又可分为Eden Space(default 2M)和Survivor Space(default 64K)

Survivor Space区域分成对等的两块from和to

之后详述垃圾回收算法时可看到,这种分类的方法对应于不同对象的生存期的长短以及垃圾回收算法的不同;

3.  JVM规范中的Method area(方法区)对应于Permanent Space;

4.  JVM规范中的Java Stack(JAVA栈)对应于Threaded JVM Stack+ Native mehthodsstacks(本地方法栈); ,实际上每个JAVA线程都对应着OS中一个真实的线程,故任何一个JAVA线程其实同时对应着两个线程:JAVA线程和操作系统中的实际线程,同理,任何一个JAVA线程其实同时要占两份空间,即JAVA栈和操作系统栈;

5. Code Generation用于转换byte code 为native code.该区域用于JIT技术基本不会导致内存异常。如果该操作没有足够的空间,JVM可能会导致崩溃。

   JIT技术:just-in-time,该技术会将JAVA字节码编译为本地机器代码并缓存,下次执行时直接执行本地机器代码,而不再由JVM执行引擎来翻译再执行,当然,将所有的JAVA字节码都编译成本地机器代码并缓存将占用大量的空间,要据程序局部性原理,实际上JAVA中需要反复执行的代码只是某一小块,因此虚拟机会跟踪代码的执行次数,一旦某一组代码执行次数超过某个阀值,则以后直接执行本地代码,这样就获得了执行时间和所需内存空间的平衡,通过该技术,JAVA可以达到与C等代码相近的执行效率(这个是书上说的);

6. Socket Buffers

包括两部分:1:Receive buffer~37k   2:Send buffer ~25k,需要在JAVA代码中来控制他,所以在外部无法配置。可能导致的异常:IOException: Too many open files (for

example)

16、Direct Memory Space

 

他可以让开发人员映射内存到java Object Heap外。

配置: XX:MaxDirectMemorySize=<value>

 

 

17、JNI Code 、

 

JNI code本身使用的内存非常小。

 

19、JNI allocate memory

JNI 程序本身也需要分配内存。

 

 

18、Garbage Collection

 

其实GC也是需要内存的,gc线程的消耗以及存放GC所缓存的信息。

三、引起的异常

java.lang.OutOfMemoryError: Java heap space

原因:Heap内存溢出,意味着Young和Old generation的内存不够。

解决:调整java启动参数-Xms -Xmx 来增加Heap内存。

java.lang.OutOfMemoryError: unable to create new nativethread

原因:Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了。

解决:由于JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;而系统的用户空间一共是3G,除了Text/Data/BSS /MemoryMapping几个段之外,Heap和Stack空间的总量有限,是此消彼长的。因此遇到这个错误,可以通过两个途径解决:1.通过 -Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);2.通过-Xms -Xmx 两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。

java.lang.OutOfMemoryError: PermGen space

原因:Permanent Generation空间不足,不能加载额外的类。

解决:调整-XX:PermSize= -XX:MaxPermSize= 两个参数来增大PermGen内存。一般情况下,这两个参数不要手动设置,只要设置-Xmx足够大即可,JVM会自行选择合适的PermGen大小。

java.lang.OutOfMemoryError: Requested array size exceeds VMlimit

原因:这个错误比较少见(试着new一个长度1亿的数组看看),同样是由于Heap空间不足。如果需要new一个如此之大的数组,程序逻辑多半是不合理的。

解决:修改程序逻辑吧。或者也可以通过-Xmx来增大堆内存。

在GC花费了大量时间,却仅回收了少量内存时,也会报出OutOfMemoryError,我只遇到过一两次。当使用-XX:+UseParallelGC或-XX:+UseConcMarkSweepGC收集器时,在上述情况下会报错,在HotSpot GC Turning文档 上有说明:

The parallel(concurrent) collector will throw anOutOfMemoryError if too much time is being spent in garbage collection: if morethan 98% of the total time is spent in garbage collection and less than 2% ofthe heap is recovered, an OutOfMemoryError will be thrown.

对这个问题,一是需要进行GC turning,二是需要优化程序逻辑。

java.lang.StackOverflowError

原因:这也内存溢出错误的一种,即线程栈的溢出,要么是方法调用层次过多(比如存在无限递归调用),要么是线程栈太小。

解决:优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小。

 

IOException: Too many open files

原因: 这个是由于TCP connections 的buffer 大小不够用了。

 

java.lang.OutOfMemoryError:Direct buffer memory

 

解决:调整-XX:MaxDirectMemorySize=<value>

 

 

重要调优参数总结:

1.堆内存

-Xms -Xmx 来增加Heap内存:解决java.lang.OutOfMemoryError: Java heap space

事实上还有其它参数:

-XX:NewRatio=4   年轻代(包括Eden和Survivor):年老代Old=1:4

-XX:SurvivorRatio=4Eden:Survivor=1:4

-Xmn 设置年轻代的大小

2.方法区内存

 -XX:PermSize=-XX:MaxPermSize=来增加方法区内存,解决java.lang.OutOfMemoryError:PermGen space

方法区进行垃圾回收的条件:

该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收。
该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

3.栈区内存

 -Xss 来增加栈区内存,解决java.lang.StackOverflowError

但一般来说,发生这种溢出的原因都是因为递归太多或存在死循环不断赋值,故如果出现该异常,通常要做的绝不是调参,而是修改程序;

4.

其它的不常见,故暂时不管;

 

JAVA的引用:

在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。
强引用就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
      软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
弱引用 也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
      虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

 

垃圾回收算法:

垃圾回收算法有:引用记数(有缺陷,无法使用),标记-清扫,标记-整理,停止-复制,火车头算法;

 对于年轻代实行停止-复制(MinorGC)的垃圾回收算法,对于年龄达到n(经过n次的Minor GC)或相同年龄达到Survivor区的一半,移入年老代;软引用在年轻代中可依然存在,直到进行FULL GC时才被清除;

对于年老代,则实行标记-清扫或标记-整理垃圾回收算法,有时也会执行FULLGC(相当于系统中调用的System.gc());

特例:对于大对象(可通过参数确定何为大对象),会直接进行年老代;

执行FULL GC后,年轻代存在的对象会直接进行年老代(适用于SUN虚拟机);

 

调试工具和JAVA自带的函数:

工具个人用过的是JCONSOLE,在装有JDK的环境下,在命令窗口中打该命令即可得到;将其注入某个运行中的JAVA程序中,可观察其内存消耗情况,以及各代内存占用情况;

MemoryMXBean的getHeapMemoryUsage()方法返回记录heap区使用情况的对象

MemoryMXBean的getNonHeapMemoryUsage()方法返回记录方法区使用情况的对象

 

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值