《深入理解Java虚拟机》第二三章摘要

Java内存区域与内存溢出

Java虚拟机中的内存分配图

各个区域的特性总结如下表:

补充说明:

  • 当多线程情形下,可能多个线程要在堆上分配内存,那么可能出现内存分配的同步问题,解决方案有两个,一个就是同步内存分配动作;另一个就是采用TLAB,即在Java堆中针对每个线程先预先分配一小块线程私有的本地线程分配缓冲。这样当线程需要分配内存时就在自己的TLAB上进行,从而避免同步的开销。但是当TLAB分配满重新分配TLAB时仍需要同步;
  • 判断一个类是否属于无用的类,“可以”被回收的条件为:1 该类所有的实例都已经被回收;2 加载该类的ClassLoader已经被回收; 3 该类对于的java.lang.Class对象没有在任何地方被引用。注意,只是可以,而不是要被回收。

内存分配方式:虚拟机使用哪种方式由内存是否规整决定,而内存是否规整则由回收算法决定。

  1. 指针碰撞:假设所有已经被分配的内存放在一边,而空闲的放在另外一边,二者中间则用一个指针来作为分界点,当需要分配内存时只需要移动该指针即可,这样的方式称为指针碰撞。使用此方法的有Serial、ParNew等带Compact的回收器
  2. 空闲列表:虚拟机中已分配的内存和空闲的内存并不规整,处于相互交错的情形,那么就需要通过维护一个列表来表示哪些内存是可用,当需要分配内存时则需要通过查询列表来查找可用大小的内存。这样的方式称为空闲列表。使用此方法的有CMS等这种基于Mark-Sweep算法的回收器

对象在HotSpot虚拟机中的内存布局如下表所示:

在Java规范中,对于reference类型只规定了一个指向对象的引用,但没有规定通过何种方式去访问引用的数据。因此对于不同的虚拟机也有不同的访问方式,主要有两种方式:

  1. 使用句柄:在Java堆中划出一块区域作为句柄池来存储句柄,一个句柄包括对象实例数据的指针以及对象数据类型的指针,reference中存储的就是对象的句柄的地址。reference通过句柄来间接访问对象。其好处在于:当对象移动时,只需要改动矩形中的指针即可,而相应的reference则不需要做变动;
  2. 直接访问:reference存储的是对象地址,通过reference就可以直接访问数据。Java对中的数据对象中含有到对象数据类型的指针,比如上面提到的类型指针。此种方式的好处就是访问速度快,相比使用句柄的方式而言少了一次指针定位的开销。HotSpotVM使用的是此种方式。

两种使用方式的图示说明如下图:图片来源 http://www.th7.cn/Program/java/201604/846729.shtml

垃圾回收算法

判断一个对象是否死去,不可能再被用的算法有两种:

  1. 引用计数算法:对于一个对象,其被引用的次数增加一次,则计数加1,当引用失效的时候就减1.当其计数为0时,则认为该对象死去。该算法的特点是效率高,但是很难解决对象之间的相互引用问题。使用此算法的有MS的COM技术以及Python等
  2. 可达性分析算法:该算法的核心就是从GC Roots开始,检测所引用的所有对象。若一个对象到GC Roots之间没有任何引用链,那么认为该对象已死。使用此算法的有Java、C#等。

可以作为GC Roots的对象有:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • Native方法中引用的对象

在Java中有四种引用强度

  • 强引用:强引用永远不会被垃圾回收器回收
  • 软引用:系统提供SoftReference类来表示,表示还有但是非必须的对象。其回收时机在于把若引用对象回收完之后内存还不够,则回收此类引用,若还不够,则OOM
  • 弱引用:通过WeakReference类来表示,此类引用只能生存到下一次垃圾回收之前。当垃圾收集器工作时,无论当前内存是否足够都会回收掉只被弱引用关联的对象。即只要发生了GC,弱引用必定被回收。其与已经没有到GC Roots的引用链的区别在于:还是可以通过弱引用来访问这些对象的,但是没有引用链的对象则永远不可能再被访问到了。
  • 虚引用:通过PhantomReference来实现,其对对象的生存时间没有任何影响,其存在的意义在于能在被虚引用引用的对象被回收的时候收到一个系统通知。

系统的GC工作流程如下图所示,总的来说,一个对象被回收可能会经过两次标记过程,并且可能在finalize方法中拯救自己以避免被回收。

几种典型的垃圾回收算法

HotSpot VM中的垃圾回收算法的具体实现细节:为了结果的准确,GC在扫描时是需要冻结所有线程的。目前主流的Java虚拟采取的都是准确式GC,即系统知道每个内存位置的数据到底是一个什么数据类型,以HotSpot为例,它采取的是一个叫做OopMap的数据结构来实现这样的映射记录。有了这样的信息,虚拟机就直接知道哪些地方存放着对象的引用,从而避免了对内存挨个的检查,加快了GC扫描的速度。程序的每条指令都可能导致引用关系或者内存数据的变化,即会导致OopMap的变化,这样的话,如果给每个指令都生成一个对应的OopMap数据那么是相当占用空间的,于是提出了安全点的概念(SafePoint),即只有当每个线程都运行到线程对应的安全点时才进行GC扫描,从而也只要给安全点上的指令生成OopMap即可,这样就减少了OopMap的数量。而安全点的选取要考虑到GC频率与系统性能的综合影响,一般选取方法调用、循环跳转、异常跳转等“具有让程序长时间运行的特征”的点。为了让线程都跑到安全点停顿下来以进行GC扫描,有抢先式中断和主动式中断两种方式。这里又有另外一个问题,如果遇到一个比如处于Sleep状态的线程,那么它是不会走动的,如果它恰好不是在一个安全点Sleep,那么意味着它永远不会走到安全点来,所以又提出了安全区域(SafeRegion)的概念。即在这个区域内的点都是安全点。线程进入安全点之后会标志自己进入了安全区域,且必须等GC执行完了才会离开安全区域。

各个垃圾回收器

几条最普遍的内存分配规则

  • 对象优先分配在Eden区:当Eden区内存不够的时候,系统将发起一次速度较快的Minor GC
  • 大对象直接进入老年代:对于较长的数组以及字符串等大对象,直接把他们分配在老年代区。因此,对于那种生命周期较短的大对象,很容引起GC,应该尽量避免。
  • 长期存活的对象将进入老年代:一个若在Survivor区经历多次Minor GC还存活,默认15次,则将被移入老年代区。
  • 动态对象年龄判定:此条是结合上一条的,要是Survivor中相同年龄的所有对象的大小和超过Survivor的一半,那么年龄大于或者等于该年龄的对象将移入老年代区,无需等到15次
  • 空间分配担保:针对新生代的复制收集算法。若参数容许,当执行Minor GC之后,若存活的对象无法全部放在Survivor中,那么将把多的对象直接放入老年代区。若老年代也没足够的空间,那么将发生Full GC以获得更多空间

Java内存以及GC的更多相关文章

  1. java 内存回收(GC)的方式

    java内存的管理其实就是对象内存的管理,其中包括对象的分配和释放 对应程序员来说分配对象使用new关键字,而释放一个对象只需要让它等于null,让程序不能再访问这个对象,这时对象是不可达的,GC负责 ...

  2. 浅谈Java内存及GC

    目录: 1.JAVA虚拟机规范与JAVA虚拟机 2.JVM结构图 3.GC策略与原理 4.垃圾收集算法 5.回收的时机 6.垃圾搜集器 一.JAVA虚拟机规范与JAVA虚拟机 内存,是指程序运行时的数 ...

  3. Java 内存区域和GC机制分析

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

  4. Java 内存区域和GC机制

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

  5. Java系列笔记(3) - Java 内存区域和GC机制

    目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Garbage Collection, ...

  6. 【转载】Java系列笔记(3) - Java 内存区域和GC机制

    Java系列笔记(3) - Java 内存区域和GC机制 转载:原文地址http://www.cnblogs.com/zhguang/p/3257367.html 目录 Java垃圾回收概况 Java ...

  7. Java内存分配以及GC

    转自http://www.cnblogs.com/hnrainll/archive/2013/11/06/3410042.html  写的太棒了,简单易懂 Java垃圾回收概况 Java GC(Gar ...

  8. 转载:Java 内存区域和GC机制

    原文链接:http://www.cnblogs.com/hnrainll/archive/2013/11/06/3410042.html 目录 Java垃圾回收概况 Java内存区域 Java对象的访 ...

  9. Java内存区域划分和GC机制

    Java 内存区域和GC机制   目录 Java垃圾回收概况 Java内存区域 Java对象的访问方式 Java内存分配机制 Java GC机制 垃圾收集器 Java垃圾回收概况 Java GC(Ga ...

随机推荐

  1. lintcode循环数组之连续子数组求和

    v 题目:连续子数组求和 II 给定一个整数循环数组(头尾相接),请找出一个连续的子数组,使得该子数组的和最大.输出答案时,请分别返回第一个数字和最后一个数字的值.如果多个答案,请返回其中任意一个. ...

  2. WebAPi添加常用扩展方法及思维发散

    前言 在WebAPi中我们通常需要得到请求信息中的查询字符串或者请求头中数据再或者是Cookie中的数据,如果需要大量获取,此时我们应该想到封装一个扩展类来添加扩展方法,从而实现简便快捷的获取. We ...

  3. ASP.NET MVC之从控制器传递数据到视图四种方式(一)

    前言 本系列开始着手讲述ASP.NET MVC,也算是自己对基础的回顾以及进一步深入学习,保证每天发表一篇该系列文章,关于基本知识则不再叙述,园子有关文章不胜枚举,这一系列若有叙述不当或错误之处,欢迎 ...

  4. DOM-Node类型

    DOM(文档队形模型)是针对HTML和XML文档的一个API(应用程序编程接口).DOM描绘了一个层次化的节点树,允许开发人员添加,移除和修改页面的一部分.DOM可以讲任何HTML和XML文档描绘成一 ...

  5. c# 实现简单的socket通信

    服务端 using System.Net.Sockets; using System.Net; using System.Threading; namespace SocketServer { cla ...

  6. php使用js对表格进行排序

    <!DOCTYPE> <html> <head> <meta http-equiv="Content-type" content=&quo ...

  7. 多种方法实现Loading(加载)动画效果

    当我们ajax提交一个按钮的时候,给那个按钮来个Loading效果会高端很多,体验也会上升个层次. 既能让用户知道正在提交中,也能防止二次提交,好处多多呢.

  8. C++指针和动态内存分配

    指针和动态内存分配 数组与指针 数组 数组名是一个指针常量. 数组名传递数据时,传递的是地址. 数组作为函数参数时不指定第一维大小. 对象数组 A a[2] = {A(1,2)}; 执行时先调用有参数 ...

  9. [Java 进阶]Java中的国际化

    背景知识 现代软件开发,往往做出的应用程序不止给一个国家的人去使用.不同国家的人往往存在语言文字不通的问题.由此产生了国际化(internationalization).多语言(multi-langu ...

  10. mybatis入门基础(三)----SqlMapConfig.xml全局配置文件解析

    一:SqlMapConfig.xml配置文件的内容和配置顺序如下 properties(属性) settings(全局配置参数) typeAiases(类型别名) typeHandlers(类型处理器 ...