`
san_yun
  • 浏览: 2662047 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

JVM内存管理学习笔记

    博客分类:
  • jvm
 
阅读更多



 

内存管理

  在所有的语言中, 一般是实现了以下两种内存管理的方式之一。

第一种是以 c/c++ 为典型代表的,是需要程序员显示的管理内存,如 c malloc /free   c++ new delete 。优点非常明显,就是程序员对所有的内存具有完全的控制权,和那些语言层面实现了内存管理的语言相比,效率很高。但是缺点也很明显,就是一不小心,很容易内存泄露, 学习成本较高, 特别是新手,很容易出错。

第二种是以后出现的众多高级语言,例如 java python c# 等等, 这些语言都不需要主动去管理内存,而是语言层面已经帮你完成了这部分功能,帮你管理内存,对于新手来说,也不容易写出太差的代码。但是同样缺点也很明显,就是你不能很明确的控制什么时候回收内存块。

内存分配和内存的回收是 JVM gc 主要需要完成的事情, 我们只有通过详细的了解 gc 相关的触发机制,才可以写出更加建壮的程序。

java 的内存区域

1.堆区(head)

    存放着所有通过new生成的对象都放在这个区域,并通过一个指针指向对应的对象。堆区一般按对象的生命周期分为年轻代和老年代?等等。。。。。。,为什么要分这样呢?

 

    分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

 

    比如String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。试想,在不进行对象生命周期区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在, 后面的GC算法也会提到。

 

这里说说年轻代:

  所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制 过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时 存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空 的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

 

2.栈区(stack)

    存放线程调用方法时的局部对象和操作数栈。随线程而生,随线程而灭,栈中的帧随着方法进入、退出而有条不紊的进行着出栈入栈操作。

   -Xss256k 设置每个线程的堆栈大小 ,默认为512K(win32, jdk1.6),根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 下面这段代码测试堆栈大小的线程的影响。

 

package test;

/**
 * 测试-Xss对方法调用栈的影响
 * -Xss128k     2619次
 * -Xss256k     5895次
 * -Xss512k     7534次
 * -Xss1024k    25556次
 * 默认是 7534次
 * @author yunpeng.jiangyp
 */
public class TestStack {

    private static int count = 0;

    public static void test() {
        System.out.println(count);
        ++count;
        test();
    }

    public static void main(String[] args) {
        test();

    }
}
 

 

 

3.方法区(method)

存放类的元数据常量

关于方法区很多人认为是没有GC的,《Java虚拟机规范》中确实说过可以不要求虚拟机在这区实现GC,而且这区GC的“性价比”一般比较低:在堆中,尤其是 在新生代,常规应用进行一次GC可以一般可以回收70%~95%的空间,而永久代的GC效率远小于此。虽然VM Spec不要求,但当前生产中的商业JVM都有实现永久代的GC,主要回收两部分内容:废弃常量与无用类。这两点回收思想与Java堆中的对象回收很类 似,都是搜索是否存在引用,常量的相对很简单,与对象类似的判定即可。而类的回收则比较苛刻,需要满足下面3个条件:

  1.该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
  2.加载该类的ClassLoader已经被GC。
  3.该类对应的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。

  是否对类进行回收可使用-XX:+ClassUnloading参数进行控制,还可以使用-verbose:class或者-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载、卸载信息。

  在大量使用反射、动态代理、CGLib等bytecode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要JVM具备类卸载的支持以保证永久代不会溢出。

 

GC要解决的几个问题。

1. 什么时候回收?

什么情况下触发垃圾回收? 由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。

Scavenge GC(yong gc)

  一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

  Full GC

  对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

  · 年老代(Tenured)被写满

  · 持久代(Perm)被写满

  · System.gc()被显示调用

  ·上一次GC之后Heap的各域分配策略动态变化

 

2.哪些内存需要回收?

  在堆里面存放着Java世界中几乎所有的对象,在回收前首先要确定这些对象之中哪些还在存活,哪些已经“死去”了,即不可能再被任何途径使用的对象。

 

  引用计数算法(Reference Counting) 

    最初的想法,也是很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。
    客观的说,引用计数算法实现简单,判定效率很高,在大部分情况下它都是一个不错的算法,但引用计数算法无法解决对象循环引用的问题。举个简单的例子:对象A和B分别有字段b、a,令A.b=B和B.a=A,除此之外这2个对象再无任何引用,那实际上这2个对象已经不可能再被访问,但是引用计数算法却无法回收他们。

 

  根搜索算法(GC Roots Tracing)

      在实际生产的语言中,都是使用根搜索算法判定对象是否存活。算法基本思路就是通过一系列的称为“GC Roots”的点作为起始进行向下搜索,当一个对象到GC Roots没有任何引用链(Reference Chain)相连,则证明此对象是不可用的。在Java语言中,GC Roots包括:
  1.在VM栈(帧中的本地变量)中的引用
  2.方法区中的静态引用
  3.JNI(即一般说的Native方法)中的引用
   判定一个对象死亡,至少经历两次标记过程:如果对象在进行根搜索后,发现没有与GC Roots相连接的引用链,那它将会被第一次标记,并在稍后执行他的finalize()方法(如果它有的话)。这里所谓的“执行”是指虚拟机会触发这个 方法,但并不承诺会等待它运行结束。这点是必须的,否则一个对象在finalize()方法执行缓慢,甚至有死循环什么的将会很容易导致整个系统崩溃。 finalize()方法是对象最后一次逃脱死亡命运的机会,稍后GC将进行第二次规模稍小的标记,如果在finalize()中对象成功拯救自己(只要 重新建立到GC Roots的连接即可,譬如把自己赋值到某个引用上),那在第二次标记时它将被移除出“即将回收”的集合,如果对象这时候还没有逃脱,那基本上它就真的离 死不远了。根搜索算法具体可以分为:


  a.标记-清除算法(Mark-Sweep)
   如它的名字一样,算法分层“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象,整个过程其实前一节讲对象标记判定的时候已经基本介绍完了。说它是最基础的收集算法原因是后续的收集算法都是基于这种思路并优化其缺点得到的。它的主要缺点有两个,一是效率问题,标记和清理两个过程效率都不高,二是空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作。

  b.复制(Copying)
  为了解决效率问题,一种称为“复制”(Copying)的搜集算法出现,它将可用内存划分为两块,每次只使用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次过清理掉。这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,未免太高了一点。
 
   现在的商业虚拟机中都是用了这一种收集算法来回收新生代,IBM有专门研究表明新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块survivor,当回收时将eden和survivor还存活的对象一次过拷贝到另外一块survivor空间上,然后清理掉eden和用过的survivor。Sun Hotspot虚拟机默认eden和survivor的大小比例是8:1,也就是每次只有10%的内存是“浪费”的。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有10%以内的对象存活,当survivor空间不够用时,需要依赖其他内存(譬如老年代)进行分配担保(Handle Promotion)。


  c.Mark-Compact(标记-整理)

   没有搞明白。

3. 如何回收回收?

   没有研究。

 

GC调优。

1. 衡量尺度
    分配内存,GC的频率, GC导致应用暂停的时间 ,应用的响应时间和QPS。

 

2.经验值

   1 对于32位, JVM必须建议控制在2G以下。
   2 对于64位, 最好开启指针压缩的选项-XX:+UseCompressedOops
   3 根据应用的需求, 选择合适的垃圾收集器

   4 计算出可能存活的对象数量,并进行相应的设置。

 

3.GC的主要的应对策略
  一 降低FGC的频率 :
      1,很明显增大old
      2,降低从new->old的晋升频率。   
   二 降低FGC的暂停时间
     1 .减小Heap
     2 .换成CMS
     3.升级CPU(据说最靠谱)
   三降低FGC的频率
     1 增大新生代
   四降低FGC的暂停时间
     1减少新生代,可能造成频繁FGC
     2升级CPU
     3写出GC友好的代码
       1.尽量减少使用autobox, 因为在Java在将原始对类型变成对象的过程中, 需要new一个新的对象过程中,在某些时候,可能会使用大量堆的内存造成内存溢出。
       2.合理控制动态增长的数据结构的大小,很多时候内存的超出错误(OOM)都是这些动态数据结构造成, 他给我们带来方便的同时, 也同样带来了不可控制性。
       3.合理使用reference。
       4.不要使用finalize()来回收资源,据写sdk的人说,Finalizers并不能保证一定会被执行,在某些特殊的时刻,可能会被跳过,这样很容易导致内存泄漏。

 

参考文档:
0. 非常详细GC学习笔记  http://blog.csdn.net/fenglibing/article/details/6321453
1. 深入理解JVM—gc相关 http://sunshine-1985.iteye.com/blog/1132011
2. GC算法 http://jarit.iteye.com/blog/1010835
3. 优化JVM参数提高eclipse运行速度 http://www.iteye.com/topic/756538
4. Yourkit官方文档 http://www.yourkit.com/docs/kb/sizes.jsp
5. 理解Heap Profling名词-Shallow和Retained Sizes http://rdc.taobao.com/team/jm/archives/900

6. JVM 几个重要的参数 http://blog.sina.com.cn/s/blog_5465f7f20100tuur.html

7. Java虚拟机日志查看工具 gclogviewer| http://www.oschina.net/p/gclogviewer

  • 大小: 84.9 KB
分享到:
评论

相关推荐

    JVM工作原理学习笔记

    本文将深入探讨JVM的工作原理,包括内存管理、类加载机制、垃圾回收以及性能优化等方面。 首先,我们来理解JVM的内存结构。在JVM中,内存被分为堆内存和栈内存两大部分。堆内存主要用于存储对象实例,而栈内存则...

    JVM学习笔记

    ### JVM学习笔记 #### JVM内存模型 (JMM) JVM内存模型主要分为以下几个部分: - **Java堆**:这是所有线程共享的一块区域,在虚拟机启动时创建。主要用于存放对象实例,几乎所有的对象实例都在这里分配内存。 - *...

    JVM内存结构笔记.rar

    Java虚拟机(JVM)是Java程序运行的核心,它的内存结构对于理解和优化Java应用程序的性能至关重要。本笔记将深入探讨JVM内存的...这份"JVM内存结构笔记"将详细阐述这些内容,是学习和研究JVM内存管理的重要参考资料。

    JVM学习资料+笔记

    这个资料包不仅涵盖了理论知识,还包含个人的学习笔记,对于学习和掌握JVM的各个方面都将大有裨益。无论是初学者还是经验丰富的开发者,都可以从中找到提升自己技能的宝贵资源。通过深入学习和实践,可以更好地理解...

    jvm视频及笔记

    4. **垃圾收集**:JVM如何自动管理内存,理解不同垃圾收集器如Serial、Parallel、CMS、G1等的工作机制,以及新生代和老年代的概念。 5. **类加载器**:系统类加载器、扩展类加载器和应用程序类加载器之间的双亲委派...

    jvm学习笔记(jvm内存模型&垃圾收集算法&类加载机制)

    1. **JVM内存模型** - **方法区**:也称为“永久代”,存储虚拟机加载的类信息、常量、静态变量等,是线程共享的区域。在Java 8之后,这部分被元空间(Metaspace)取代。 - **运行时常量池**:是方法区的一部分,...

    jVM学习笔记.ppt

    垃圾回收是JVM内存管理的关键。它自动回收不再使用的对象,释放内存资源。新生代和旧生代采用不同的垃圾收集算法,例如Minor GC和Major GC,以确保高效且无内存泄漏的运行环境。通过调整JVM参数如-Xmx、-Xms、-Xmn等...

    JVM学习笔记2018-4-151

    堆内存是JVM内存管理的主要部分,它在所有线程之间共享,而栈内存则为每个线程单独分配。虚拟机通过垃圾收集机制自动回收不再使用的对象,从而优化内存使用。 总结来说,这篇JVM学习笔记涵盖了对象声明、内存分配的...

    深入Java虚拟机JVM类加载学习笔记

    ### 深入Java虚拟机JVM类加载学习笔记 #### 一、Classloader机制解析 在Java虚拟机(JVM)中,类加载器(ClassLoader)是负责将类的`.class`文件加载到内存中的重要组件。理解类加载器的工作原理对于深入掌握JVM以及...

    JVM学习笔记.docx

    本篇JVM学习笔记主要涵盖了以下几个核心知识点: 1. **运行时数据区**: - **程序计数器**:记录当前线程执行的字节码的行号,用于线程恢复执行时跳转到正确位置。 - **Java虚拟机栈**:每个方法执行时创建的栈帧...

    Java虚拟机JVM类加载学习笔记

    Java虚拟机(JVM)是Java程序的核心组成部分,它负责执行字节码并管理程序运行时的内存。本文主要探讨JVM的类加载机制,包括类加载、连接、初始化等关键过程,以及类的主动使用和被动使用的情况。 首先,我们要理解...

    JVM的学习笔记PDF版

    6. **内存调优**:理解和调整JVM内存参数(如-Xms, -Xmx, -Xss等)对于优化应用性能至关重要。了解内存泄漏和内存溢出问题,以及如何通过监控工具(如VisualVM或JConsole)进行诊断和分析。 7. **JVM性能监控和故障...

    jvm学习笔记.zip

    本文将深入探讨JVM的内存管理,特别是垃圾回收机制,以及相关的优化策略。 首先,我们要了解JVM的内存结构。Java内存主要分为堆内存和栈内存两大部分。堆内存主要用于存储对象实例,而栈内存则存储方法调用时的局部...

    JVM历史发展和内存回收笔记

    二、JVM内存回收机制 1. **堆内存**:所有对象都在堆内存中分配,包括实例变量和数组。Java的垃圾回收主要关注堆内存的管理。 2. **垃圾回收算法**: - **标记-清除(Mark-Sweep)**:首先标记出所有活动对象,...

    JVM性能学习笔记思维导图

    本文将根据"JVM性能学习笔记思维导图"的主题,详细阐述JVM的主要组成部分,性能调优的关键点以及相关的工具与实践策略。** 1. **JVM结构与内存模型** - **类装载器(ClassLoader)**:负责加载类文件,确保类在运行...

    jvm原理分析课程笔记

    "2JVM浅出笔记.pdf"可能进一步解释了JVM的关键特性,"8笔记10.pdf"到"7笔记7.pdf"、"5笔记5.pdf"、"6笔记6.pdf"、"4笔记4.pdf"、"3笔记.pdf"可能分别详细讨论了内存管理、类加载、垃圾收集、JIT编译以及JVM调优等多...

    JVM 学习笔记(Java虚拟机)

    **JVM学习笔记(Java虚拟机)** Java虚拟机(JVM)是Java语言的核心组成部分,它是Java程序运行的平台,负责解释和执行字节码。深入理解JVM对于优化Java应用程序性能至关重要。本笔记将从以下几个方面详细介绍JVM:...

    JVM笔记(阳哥).zip

    这份笔记涵盖了JVM的基础概念、内存管理、类加载机制、性能优化等多个方面,对于理解Java程序的运行机制以及提升开发效率具有重要的指导意义。 一、JVM基础 Java虚拟机(JVM)是Java程序运行的核心,它提供了平台...

    Java分布式应用学习笔记02再谈JVM

    #### JVM内存管理 JVM的内存管理是其最为关键的特性之一,主要包括堆内存和非堆内存。堆内存用于存储对象实例,包括类实例、数组等。非堆内存则用于存储元数据信息,如类定义、常量池、静态变量等。JVM还提供了垃圾...

Global site tag (gtag.js) - Google Analytics