JVM 内存管理

JVM 通过 垃圾收集-GC 自动管理内存堆中对象内存的分配和回收。JVM 通常采用分代垃圾收集器,以便于整理内存碎片。分代垃圾收集器就是基于对象不同生命周期,将堆分成不同的内存区域,然后组合使用不同的垃圾收集算法,可简单认为分为两部分组成:

  • Young Generation:年轻代,由Eden和两个相等的Survivor空间组成,其中一个Survivor始终为空,用来复制Minor GC后在Eden和另一个Survivor存活的对象。
  • Old Generation:老年代,对象生命周期比较长。

内存回收

内存回收主要考虑两个问题:

  • 如何判断对象可被回收,判断策略:

    • Tracing GC,跟踪收集,也叫可达性分析算法,其思想是从某些根对象引用(GC roots)出发总能找到一个到一组存活对象的引用链。
    • Reference counting引用计数法,不能解决循环引用。
    • Escape analysis逃逸分析,可以将堆分配转为栈分配,动态编译优化手段,减轻GC压力。
  • 采用何种方式进行回收,垃圾收集算法:
    • Copying,复制,将存活对象从一块内存复制到另一块内存,不产生内存碎片,由于要保留一个空的备份内存,所以空间利用率较低。
    • Mark-Sweep,标记清理,从GC roots出发标记所有存活对象,然后清理所有未标记的对象,会产生内存碎片。
    • Mark-Compact,标记整理,标记清除后,会压缩内存,避免内存碎片。

内存分配

对象实例首先在 Eden 区分配,为了快速分配,Hotspot JVM 采用的是一种 bump-the-pointer线性分配方法。分配时一般都有大块连续内存可用,此方法就是检查剩余内存是否足够,给对象分配内存,然后更新指针偏移量和初始化对象。

线性分配效率固然高,但对多线程程序来说,分配内存的操作必须是线程安全的。可以使用全局锁但会影响性能,Hotspot JVM 采用的是一种 Thread-Local Allocation Buffers (TLABs) 的方法,为每个线程分配一个缓冲区,当TLAB满了,再加锁去申请,在线程内部就能使用bump-the-pointer,进而提高分配的吞吐量。

分配内存时,一些大对象有可能直接在老年代分配,在年轻代经过几轮Minor GC存活,达到一定年龄的对象,会被提升复制到老年代。

Hotspot JVM

Hotspot JVM 自JDK 6u23开始支持逃逸分析,采用 Tracing GC 追踪堆中存活对象,显然,GC roots就是堆外的对象引用

  • 栈帧中局部变量或方法参数的对象引用
  • 类引用类型静态成员变量
  • JNI 本地方法局部变量,参数和JNI 全局引用

需要注意GC roots是一组对象引用而不是引用对象

Hotspot JVM 提供了多个垃圾收集器让分代收集器组合使用:

  • 串行收集器,单个GC线程执行所有垃圾收集工作

    • Serial,年轻代,使用复制算法DefNew
    • Serial Old,老年代,使用标记压缩整理算法Tenured
  • 并行收集器,吞吐量收集器,多个GC线程加速垃圾回收
    • ParNew,年轻代,使用复制算法,可看做并行的Serial,ParNew
    • Parallel Scavenge,年轻代,使用复制算法,与 ParNew 的区别,它可以动态调节最大停顿时间和吞吐量,PSYoungGen
    • Parallel Old,老年代,使用标记压缩整理算法,通常与Parallel Scavenge配合使用,ParOldGen
  • 并发收集器,看重响应时间而不是吞吐量
    • CMS, Concurrent Mark Sweep,低停顿,采用标记清除算法,会有内存碎片,Java堆空间需求比较大,CMS
    • G1, Garbage-First,相比CMS,压缩内存,G1

常用JVM参数指定GC组合:

  • -XX:+UseSerialGC:Serial + Serial Old
  • -XX:+UseParNewGC:ParNew + Serial Old
  • -XX:+UseParallelGC:Parallel Scavenge + Serial Old
  • -XX:+UseParallelOldGC:Parallel Scavenge + Parallel Old
  • -XX:+UseConcMarkSweepGC:ParNew + CMS + Serial Old
  • -XX:+UseG1GC:G1

64位JVM 默认配置,以JDK 8为例

使用命令java -XX:+PrintFlagsFinal -version或者jmap -heap <pid>可以查看默认配置。

JVM堆最小为物理内存的 1/64,64位至少2G,即最小32M;最大为物理内存的 1/4,如128G内存,默认JVM最大32G,可以使用-Xms-Xmx指定初始和最大大小。年轻代默认比例为NewRatio=2,即占总堆的 1/3,可使用-Xmn指定大小,Survivor空间比例为SurvivorRatio=8,即每个Survivor占Eden的 1/8,因为有两个,所以占年轻代的 1/10。

JDK 8 默认垃圾收集器是ParallelOldGC,其中Parallel Scavenge默认打开AdaptiveSizePolicy,自适应调整各种参数,默认的SurvivorRatio 配置需要手动指定才能生效。比如指定堆大小为270M,那么各区域大小如下:

图 1 并行垃圾收集,指定SurvivorRatio=8,堆空间大小

永久代, Permanent Generation

JVM 中方法区的实现,不同版本之间存储内容存在差别:

  • JDK 6:类元数据、类静态变量、intern字符串
  • JDK 7:将intern字符串移到堆中
  • JDK 8:移除永久代,新增一个元空间存储类元数据,将类静态变量和intern字符串移到堆中

永久代逻辑上是堆的组成部分,64位JVM默认大小为82M,但最大大小难以估计,因为程序里面有多少类,有多少方法以及常量池大小都很难估算,此外永久代垃圾收集与老年代绑定,任一区域满了都会触发Full GC,所以对永久代的调优很困难。

在JDK 8中,移除永久代,类元数据分配在本地内存中,默认情况下,可用内存不受限制,可使用MaxMetaspaceSize设置可用的内存上限。Hotspot JVM 显式管理这部分内存,从OS申请空间,然后分成块,一个块绑定到一个特定的类加载器,类元数据就在这些块中分配,当类卸载或加载器标记被回收,这些块被释放重用或返回OS。

小结

JDK 8中 GC cause有:年轻代内存分配失败,引起的Minor GC;年轻代提升到老年代,而老年代空间不足,引起的Full GC;元数据空间达到阈值,引起的GC。JDK 7之前,由于永久代空间不足引起的Full GC。不管是永久代或者元数据空间,也都会存在内存泄漏,比如web应用,当应用程序被卸载,那么它的war包中的所有类都是垃圾,如果不移除就会有内存泄漏。

在分析内存回收时,一定要谨记 GC roots 是一组对象引用或者说指针,拿废弃常量来说,完全可以通过GC root来识别,进而回收。

参考

?

时间: 2024-10-02 08:24:10

JVM 内存管理的相关文章

Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确实很低,一方面,Java语言采用面向对象思想,这也决定了其必然是开发效率高,执行效率低.另一方面,Java语言对程序员做了一个美好的承诺:程序员无需去管理内存,因为JVM有垃圾回收(GC),会去自动进行垃圾回收. 其实不然: 1.垃圾回收并不会按照程序员的要求,随时进行GC. 2.垃圾回收并不会及时

JVM内存管理和垃圾回收机制介绍

http://backend.blog.163.com/blog/static/20229412620128233285220/ 内存管理和垃圾回收机制是JVM最核心的两个组成部分,对其内部实现的掌握是Java开发人员开发出高质量的Java系统的必备条件.最近整理了一些关于JVM内存管理和垃圾回收方面的知识,这里梳理一下,分享给大家,希望能够对Java虚拟机有更深入的了解. 1. JVM内存管理 首先,JVM将内存组织为主内存和工作内存两个部分.主内存中主要包括本地方法区和堆.每个线程都有一个工

JVM内存管理(二)

JVM内存管理 JVM在执行java程序的过程中,会把内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁. 程序计数器 程序计数器:当前线程所执行字节码的行号指示器. 由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令.为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程之间计数器

JVM内存管理及垃圾回收

一.JVM内存的构 Java虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法.总体分为下面几个部分: 程序计数器(Program Counter Register).JVM虚拟机栈(JVM Stacks).本地方法栈(Native Method Stacks).堆(Heap).方法区(Method Area) 如下图: 1.程序计数器(Program Counter Register) 这 是一块比较小的内存,不在Ram上

JVM内存管理的机制

1.JVM内存管理的机制 内存空间划分为:Sun JDK在实现时遵照JVM规范,将内存空间划分为堆.JVM方法栈.方法区.本地方法栈.PC寄存器. 堆: 堆用于存储对象实例及数组值,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中对象所占用的内存由GC进行回收,在32位操作系统上最大为2GB,在64位操作系统上则没有限制,其大小可通过-Xms和-Xmx来控制,-Xms为JVM启动时申请的最小Heap内存,默认为物理内存的1/64但小于1GB:-Xmx为JVM可申请的最大He

java jvm内存管理/gc策略/参数设置

1. JVM内存管理:深入垃圾收集器与内存分配策略 http://www.iteye.com/topic/802638 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项技术当做Java语言的伴生产物.事实上GC的历史远远比Java来得久远,在1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.当Lisp还在胚胎时期,

JVM内存管理及GC机制

一.概述 JavaGC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.经过这么长时间的发展,javaGC机制已经日臻完善,几乎可以自动的为我们做绝大多数的事情. 虽然java不需要开发人员显示的分配和回收内存,这对开发人员确实降低了不少编程难度,但也可能带来一些副作用: 1. 有可能不知不觉浪费了很多内存 2. JVM花费过

现代JVM内存管理方法的发展历程,GC的实现及相关设计概述(转)

JVM区域总体分两类,heap区和非heap区.heap区又分:Eden Space(伊甸园).Survivor Space(幸存者区).Tenured Gen(老年代-养老区). 非heap区又分:Code Cache(代码缓存区).Perm Gen(永久代).Jvm Stack(java虚拟机栈).Local Method Statck(本地方法栈). HotSpot虚拟机GC算法采用分代收集算法: 1.一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直

java基础---JVM内存管理以及内存运行机制学习总结

自己从网上搜资料拼接了一张JVM内存图:如下图所示: 我们思考几个问题: 1.jVM是怎么运行的? 2.JVM运行时内存是怎么分配的? 3.我们写的java代码(类,对象,方法,常量,变量等等)最终存放在哪个区? VM运行时数据区域: 1.程序计数器(program Counter Register):   是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的 方式去实 现),字节码解释器工作时就是通过改

java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任. 对于Java程序员来说,不需要在为每一个new操作去写配对的delete/free,不容易出现内容泄漏和内存溢出错误,看起来由JVM管理内存一切都很美好.不过,也正是因为Java程序员把内存控制的