深入理解 Java 虚拟机之学习笔记(2)

本节介绍

  • Java堆的OutOfMemoryError测试
  • Eclipse Memory Analyzer分析内存溢出
  • 虚拟机栈和本地方法栈StackOverflowError测试
  • 方法区和运行时常量池溢出
  • 本机直接内存溢出

一、Java堆的OutOfMemoryError测试

  (1)首先设置debug configuration。如下图所示:

  (2)接下来进行编码操作,如下面的代码所示,不断添加新的对象到List中。由于Java堆设置的大小为20M并且不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展),通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析。

  Java堆内存溢出测试:如下如所示:

  

  (3)结果分析:Java堆内存的OOM(OutOfMemoryError)异常是实际应用中常见的内存溢出异常情况。当出现Java堆内存溢出时,异常堆栈信息“java.lang.OutOFMemoryError”会跟着进一步提示“Java heap space”

  

二、Eclipse Memory Analyzer分析内存溢出

   (1) Eclipse安装Eclipse Memory Analyzer。

      现在已经出1.2.1了,下载地址http://www.eclipse.org/mat/downloads.php

      也可以通过eclipse install new software ,地址http://download.eclipse.org/mat/1.2/update-site/

   (2)打开进入后,如下所示:

     要解决这个区域的异常,一般的手段是先通过内存映像工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否都是必要的,也就是要先分清楚到底是出现了内存泄露还是内存溢出。

    

    从上图可以看到它的大部分功能。
         1. Histogram可以列出内存中的对象,对象的个数以及大小。
         2. Dominator Tree可以列出那个线程,以及线程下面的那些对象占用的空间。
         3.Top consumers通过图形列出最大的object。
         4.Leak Suspects通过MA自动分析泄漏的原因。

   (3)这次重点是看Leak Suspects,点开后就能看到:

    

       

   (4)结果分析:在这张图上,我们可以清楚的看到,这个对象集合中保存了大量YourBeauty对象的引用,就是它导致的内存泄露。

三、虚拟机栈和本地方法栈StackOverflowError测试

   (1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

  (2)如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

  (3)使用-Xss参数减小栈内存容量。测试结果:抛出StackOverflowError异常。异常出现时输出堆栈深度相应减小。

  

  

  (4) 实验表明:在单线程情况下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。

四、方法区和运行时常量池溢出

  (1)由于运行时常量池是方法区的一部分,因此这两个区域的溢出测试就放在了一起进行。前面提到JDK 1.7开始逐步“去永久代”的事情,在此就以测试代码观察一下这件事对程序的实际影响。

    

   (2)String.intern()是一个Native方法,它的作用是:如果字符串常量池已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。在JDK1.6及之前的版本中,由于常量池分配中永久代内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量。

  

五、本机直接内存溢出

  (1) DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不知道,则默认与Java堆最大值(-Xmx指定)一样,下述代码越过了DirectByteBuffer类,直接通过反射获取Unsafe实例进行内存分配。因为,虽然使用DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是同计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory()

  

  (2)使用unsafe分配本机内存。

  

 

根据GC的工作原理,我们可以通过一些技巧和方式,让GC运行更加有效率,更加符合应用程序的要求。以下就是一些程序设计的几点建议。

1.最基本的建议就是尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null.我们在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组,队列,树,图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC回收它们一般效率较低。如果程序允许,尽早将不用的引用对象赋为null.这样可以加速GC的工作。

2.尽量少用finalize函数。finalize函数是Java提供给程序员一个释放对象或资源的机会。但是,它会加大GC的工作量,因此尽量少采用finalize方式回收资源。

3.如果需要使用经常使用的图片,可以使用soft应用类型。它可以尽可能将图片保存在内存中,供程序调用,而不引起OutOfMemory.

4.注意集合数据类型,包括数组,树,图,链表等数据结构,这些数据结构对GC来说,回收更为复杂。另外,注意一些全局的变量,以及一些静态变量。这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。

5.当程序有一定的等待时间,程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。使用增量式GC可以缩短Java程序的暂停时间。

时间: 2024-08-07 19:03:41

深入理解 Java 虚拟机之学习笔记(2)的相关文章

(转)《深入理解java虚拟机》学习笔记5——Java Class类文件结构

Java语言从诞生之时就宣称一次编写,到处运行的跨平台特性,其实现原理是源码文件并没有直接编译成机器指令,而是编译成Java虚拟机可以识别和运行的字节码文件(Class类文件,*.class),字节码文件是一种平台无关的中间编译结果,字节码文件由java虚拟机读取,解析和执行,java虚拟机屏蔽了不同操作系统和硬件平台的差异性. 如今的java虚拟机已经称为一种通用平台,不但能够运行java语言,Groovy,JRuby,Jython等一大批动态语言也可以直接在Java虚拟机上运行,其原理也是这

深入理解 Java 虚拟机之学习笔记(3)

垃圾回收(Garbage Collection,GC ),GC的历史其实比Java久远,1960年诞生与MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.当Lisp还在胚胎时期时,人们就在思考GC需要完成的3件事情: 哪些内存需要回收? 什么时候回收? 如何回收? 问:经过半个多世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了“自动化”时代,为何我们还要去了解GC和内存分配呢? 答:当需要排查各种内存溢出.内存泄露问题时,当垃圾收集成为系统达到更高并发

深入理解 Java 虚拟机之学习笔记(1)

本书结构: 从宏观的角度介绍了整个Java技术体系.Java和JVM的发展历程.模块化,以及JDK的编译 讲解了JVM的自动内存管理,包括虚拟机内存区域的划分原理以及各种内存溢出异常产生的原因 分析了虚拟机的执行子系统,包括类文件结构.虚拟机类加载机制.虚拟机字节码执行引擎 讲解了程序的编译与代码的优化,阐述了泛型.自动装箱拆箱.条件编译等语法糖的原理 讲解了虚拟机的热点探测方法.HotSpot的即时编译器.编译触发条件,以及如何从虚拟机外部观察和分析JIT编译的数据和结果 探讨了Java实现高

《深入理解Java虚拟机》学习笔记(二)

垃圾回收的前提是判断对象是否存活,对象不再存活时将会被回收,下面是2种判断的方法. 引用计数法: 主流的Java虚拟机并没有使用引用计数法来管理内存,重要的原因就是循环引用的问题难以解决. 可达性分析法: 这个算法的基本思路是:通过一系列称为“GC Roots”的对象作为起始点,向下搜索,走过的路径称为引用链,当对象到GC Roots没有任何的引用链时,则认为对象是可以被回收的. Java中,可以作为GC Roots的对象包括: 1>虚拟机栈中引用的对象 2>方法区中静态属性引用的对象 3&g

《深入理解Java虚拟机》学习笔记(一)

JDK是支持Java程序开发的最小环境集,JRE是支持Java程序运行的标准环境,JRE是JDK的一部分. Java 1.0版本诞生于1995年,其使用的虚拟机是Sun Classisc VM,这款虚拟机已经不再使用.JDK1.3时,HotSpot VM成为了默认的虚拟机.其他较为出名的Java虚拟机还包括JRockit.J9等. JDK1.5中的java.util.concurrent包实现了一个粗粒度的并发框架,JDK1.7中的java.util.concurrent.forkjoin包则是

转《深入理解Java虚拟机》学习笔记之最后总结

编译器 Java是编译型语言,按照编译的时期不同,编译器可分为: 前端编译器:其实叫编译器的前端更合适些,它把*.java文件转变成*.class文件,如Sun的Javac.Eclipse JDT中的增量式编译器ECJ: JIT编译器:虚拟机的后端运行期编译器(Just In Time Compiler),它把字节码转变成机器码,如HotSpot VMd C1.C2编译器: AOT编译器:静态提前编译器(Ahead Of Time Compiler),它直接把*.java文件编译成本地机器码,如

(转)《深入理解java虚拟机》学习笔记10——并发编程(二)

Java的并发编程是依赖虚拟机内存模型的三个特性实现的: (1).原子性(Atomicity): 原子性是指不可再分的最小操作指令,即单条机器指令,原子性操作任意时刻只能有一个线程,因此是线程安全的. Java内存模型中通过read.load.assign.use.store和write这6个操作保证变量的原子性操作. long和double这两个64位长度的数据类型java虚拟机并没有强制规定他们的read.load.store和write操作的原子性,即所谓的非原子性协定,但是目前的各种商业

(转)《深入理解java虚拟机》学习笔记9——并发编程(一)

随着多核CPU的高速发展,为了充分利用硬件的计算资源,操作系统的并发多任务功能正变得越来越重要,但是CPU在进行计算时,还需要从内存读取输出,并将计算结果存放到内存中,然而由于CPU的运算速度比内存高几个数量级,CPU内的寄存器数量和容量有限,为了不让CPU长时间处于等待内存的空闲状态,在CPU和内存之间引入了速度接近CPU的高速缓存Cache作为CPU和内存之间的缓冲.计算机硬件并发的原理如下: Java虚拟机对并发的支持类似于计算机硬件,java虚拟机的并发支持是通过java虚拟机的内存模型

(转)《深入理解java虚拟机》学习笔记4——Java虚拟机垃圾收集器

Java堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法,年老代主要使用标记-整理垃圾回收算法,因此java虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器,JDK1.6中Sun HotSpot虚拟机的垃圾收集器如下: 图中如果两个垃圾收集器直接有连线,则表明这两个垃圾收集器可以搭配使用. (1).Serial垃圾收集器: Serial是最基本.历史最悠久的垃圾收集器,使用复制算法,曾经是JDK1.3.1之前新生代唯一的垃圾收集器. Serial是一个单线程的

(转)《深入理解java虚拟机》学习笔记3——垃圾回收算法

Java虚拟机的内存区域中,程序计数器.虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭:栈中的栈帧随着方法的进入和退出而进行入栈和出栈操作,每个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这三个区域的内存分配和回收都具有确定性.垃圾回收重点关注的是堆和方法区部分的内存. 常用的垃圾回收算法有: (1).引用计数算法: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不再被使用的,垃