`
406657836
  • 浏览: 4203 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

java也谈gc

    博客分类:
  • java
阅读更多
一直以来想写博客,而没有写。今天下定决心开始培养写博客的习惯。第一篇博客,不会写的太深。主要谈谈java关于gc方面的知识。

本文主要是针对Hotspot虚拟机讨论。其它的虚拟机可能有所不同,请读者自行区别呵。因为我们通常用的也是Hotspot。
众所周知,java对内存的回收是由gc自动回收。这就不得不说java中有哪些内存需要回收,那么就得先看看java有哪些内存区域。

java运行时的内存区域:

1.java堆,这是java程序中应用最多的一块内存区域。我们new一个对象的内存都在这里分配。

2.方法区,这是java程序的代码区域。类,方法,变量的信息都存在这个区域,当然常量池也在这个区域。

3.虚拟机栈,java程序是支持多线程的程序,也是基于栈解释的程序。所以程序调用方法的压栈,出栈就是指的这个虚拟机栈。虚拟机栈主要是分配对象的引用和基本类型。

4.本地方法栈,java程序是支持native方法的程序。那么native方法栈的空间将在这部分内存分配。

5.程序计数器,这个用到的内存极少,很多时候不需要考虑。当然这个是和一个线程绑定的。存放程序命令执行到的位置。

6.直接内存,需要强调这个不是在jvm进程的内存空间,而是用的操作系统的内存。之所以把它写到这里,是要提醒大家千万别忘了这块内存。这也是容易发生OOM的地方。特别是NIO的时候。下面会具体谈。

可以看出这些内存区域,1,2,6内存区域是全局共享的,也就是所有的线程都共用这些内存空间。3,4,5是线程绑定的,也就是说这些内存空间的生命周期是和线程息息相关的。那么3,4,5的内存释放就非常简单了。比如虚拟机栈,随着压栈内存分配,出栈内存释放。所谓压栈,出栈就是方法调用和方法退出。本地方法栈也一样。程序计数器随线程的回收而回收。下面重点谈谈1,2,6的内存回收。

关于java堆的内存回收:

毫无疑问这块内存是java中最活跃的内存区域。所有的java new一个对象都要在这里完成内存分配。当这块内存在发生gc后任然不能分配对象时,将发生OOM。java.lang.OutOfMemoryError: ......java heap space。这个通常是最容易产生的内存溢出。如果发生了这类OOM就要考虑是否堆内存设置得太小,或者程序是不是一直在分配内存并且持有强引用,使这些对象一直是强可及对象?好了,直接进入正题说这块内存是如何回收的。要知道如何回收先要知道关于任何gc都需要解决的问题。

1.内存中有哪些对象,哪些对象又是可以回收的,哪些对象是不能回收的

2.如何回收,怎么把需要回收的对象从内存中移除。
3,什么时候需要回收呢。

4.如何解决回收时程序的延迟,以及发生gc时的内存分配请求。特别是多线程程序,在发生gc时候必然还有很多线程在请求堆内存分配。怎么处理这些请求。

下面对这些问题一一解答。请记住问题的编号,下面会提到问题的编号。

关于第1个问题——java中找到对象是用的根搜索算法(不是引用计数)。那么java中的根如何判定呢。java中的根主要存在在这些地方:实例变量,静态变量,方法栈上的引用,本地方法栈的引用。也就是说java只能在这里找到现在程序还在使用的对象,不能回收的对象。那么怎么找到不需要的对象呢。嘿嘿,当然就是对内存中的所有变量进行标记,把需要使用的对象统统标记出来,打过标记的对象就是要用的,不要回收掉了。这里就产生了一系列问题呵,这些问题也是用来解答问题4的。java的内存可以分配N个G,那么这么多的对象,每次都标记,得要多久啊。况且java中有些对象是程序启动到结束一直都在堆里面的。每次都来标记它,然后又不清除,这多浪费啊!所以java的gc强大之处就展现出来了。

关于问题2如何回收——java采用分代回收。java中的堆内存分成两个代:新生代,老年代。

所谓新生代:  可以从字面上理解,就是那些还比较年轻的对象。就是"才"分配出来的对象。新生代的对象会根据一定的晋升策略进入老年代。晋升策略有(可能不全):

1,new一个对象这个对象足够大(默认是超过新生代内存的一半,这个可以配置),那么直接进入老年代。

2,当一个对象在新生代存活了一定次数(默认是15次,这个次数还是有点多呵,可以根据情况配置),就进入老年代。

3,在新生代发生gc时,“内存不足”(这里关系到新生代的内存回收策略,等会会讲到,也会说明为什么会不足),要老年代提供担保时。新生代多出的对象直接进入老年代。

新生代的回收策略: 新生代也分为两个区域:一部分叫做Eden区,乐透区,新生代会首先在这个区域分配对象。另一部分,由两块对等的区域组成,叫做Survivor。为什么会由两部分组成呢。这就不得不从新生代的特性说起了。在新生代请求分配内存最多的地方毫无疑问是方法栈中。通常随着方法的退栈,大多数对象都不是根可及对象了。所以这里的大多数对象都是可以回收的了。那么对这个些对象进行标记并且回收,那么回收率是非常高的。那采用哪种标记回收策略呢?这里主要有3种标记回收策略:

1,标记复制——标记可用对象然后复制到另一块内存区域,没有内存碎片。

2,标记清除——标记可用对象,然后对于不可用对象直接清除,这里必然产生内存碎片。

3,标记整理——标记可用对象,然后把可用对象往一段移动,记录可用对象区域,把可用对象另一端的对象清除掉。没有内存碎片。

显然对于新生代这种大多数对象在很快就可以被回收的情况,采用1,3种策略最合适,因为需要复制或移动的对象很少,同时不会产生内存碎片。GC在新生代通常采用的是标记复制算法。并且基于大多数对象可以被回收的假设,复制的两个区域大小不是对等的,即Eden和Survivor的大小不是对等的。默认配置是8:1。由于Survivor有两块对等的。所以新生代Eden区默认会占到五分之四。当发生GC时,会把Eden区和其中一块Survivor往另外一块Survivor复制。这个时候,另一块Survivor可能不能装下Eden和那一块Survivor的对象,如果这种情况发生就需要老年代担保,并直接进入老年代。新生代就这样来回复制,实现GC回收。在回收的过程中是要暂停内存的分配。所以这里是一个程序停顿点。通常年轻代的回收会很快,所以这里的停顿时间不会太长。但是年轻代的回收频率往往是较高的。当然年轻代的垃圾回收也可以配置单线程,多线程,可控制延时等回收器。

老年代的回收策略:这里主要讲cms的回收策略。至于G1或JDK1.5以前的一些老年代回收策略现在很少使用,这里暂时不解释。cms(concurrent-mark-sweep)从名字上来说,他是并行的老年代回收器。但是这不意味着所有老年代gc的过程都是可以和内存分配并行的。老年代回收分为下面几个过程:

1,初始化标记

2,并发标记

3,重新标记

4,并发清除

对于1,3过程对于内存的分配请求也是停顿的,对于2,4过程可以和内存的分配请求同时进行。老年代是用的标记清除算法,所以必然产生内存碎片。这可以开启内存碎片整理,在多少次GC后进行一次内存碎片整理(这是可以配置的)。这个整理过程是full GC 是让应用程序停顿的。

关于第3个问题——什么时候回收。对于新生代,通常是对象无法分配的时候会发生gc。对于老年代是到达一定的内存使用率发生一次gc。默认配置下的使用率是68%.这也是可以配置项。对于full gc可能在显示调用System.gc()后发生。当然有参数可以关闭显示调用gc。DisableExplicitGC参数设置成true,就不能显示的调用gc了。当然还有其它发生gc的情况,比如是否配置了允许担保失败呀。

关于第4个问题——分代回收是提升java性能的关键,cms的并行回收在一定程度上满足了gc和应用程序同时运行,减少了停顿时间。这也是java语言和虚拟机走向成熟,倍受关注,广泛使用的原因。当然很期待G1回收器的使用。这个并行,可以控制停顿时间,标记整理的老年代收集器,必然是java虚拟器的趋势。也是java语言走向更辉煌的一个期望。

关于方法区的内存回收:

一般来说方法区的内存分配和回收都不会像java堆那样频发。但这也不是真正意义上的永久代,这里还是会发生内存回收的。一般来说对于一个类的回收需要满足一下条件才能被回收(可能不全)

1,该类的所有实例都已经被回收

2,类的加载器已经被回收

3,类对应的class没有任何地方在使用,包括反射也不会用到。

这些条件其实比较苛刻了。在一般的应用程序中可能不会遇到。但是如果频繁使用cglib生成类或动态加载类或热部署jsp等应用程序里面还是会发生。

对于常量的回收,要满足常量没有任何强可及的引用。

关于直接内存的回收:

什么地方会使用到直接内存。一般来说是在NIO中。比如netty是默认是用的直接内存分配,mina在2.0后已经不是默认使用的直接内存了。之前已经强调了,直接内存不是使用的jvm进程的内存,而是使用操作系统的内存。这部分内存也可以在jvm启动参数里面配置。MaxDirectMemorySize。对于这块内存的回收往往要在full gc的时候才能回收。以前公司是有用netty的地方(Hbase的通信)发生了OOM,就是因为老年代的回收频率太低了,在直接内存溢出了也没有发生老年代回收。当然这里可以关闭DisableExplicitGC来解决。不过这也是一个危险的操作,意味着你允许了应用程序显示的调用System.gc(),所以在应用程序里面最好不要这么调用。关闭DisableExplicitGc,应该把相应的并行回收参数也打开,不然这里就是一个真正意义上的full gc,意味着程序是停顿的。至于具体参数可以查看jvm参数配置,这里不贴出来了。对于直接内存的回收还可以显示调用,DirectBuffer的cleaner的clean方法,具体可以参照另一个同学的博客http://blog.csdn.net/xieyuooo/article/details/7547435。里面介绍的很详细。

关于java的gc就简单介绍到这里了,当然java的gc还有很多复杂的应用。java的核心和优势之一就是强大的gc。平时可以多观察下java gc的情况,这对掌握应用程序的性能是非常有帮助的。整个jvm调优都是要基于gc的情况调整的。
0
2
分享到:
评论
3 楼 406657836 2013-06-26  
406657836 写道
baitian 写道
楼主,不严谨 ,提两点
1.
引用
当发生GC时,会把Eden区和其中一块Survivor往另外一块Survivor复制。

所谓的“其中一块Survivor”是何时被写入的?
2.
引用
当然这里可以打开DisableExplicitGC来解决。不过这也是一个危险的操作,意味着你允许了应用程序显示的调用System.gc(),所以在应用程序里面最好不要这么调用

恰恰相反,就是通过system.gc()去触发对直接内存的回收

嗯 谢谢指正呵。
对于第一个问题:
  对象分配首先会选择在Eden区(如果对象足够大,比如超过Eden区可用大小的一半,这个可以指定的,这里还和年轻代采用的回收器有关)就直接到老年代。如果是分配到年轻代会首先优先选择Eden区,也不排除有一部分进入survivor。在jvm启动的时候会选择任意选择其中一块survivor。如果Eden区不够分配了会触发minor gc。这时候就把之前选择的那块survivor和Eden的可用对象复杂到另一开survivor上去。

对于第二个问题:
  这个问题之前的表述确实存在很大的问题。应该改为"关闭DisableExplicitGC"(设为false),因为DisableExplicitGC的意思就是禁止显示调用gc。所以应该是要一个双重否定,让显示调用生效,已达到让System.gc()调用起作用,解决直接内存溢出问题。

不过仍然要注意,这个参数关闭掉是有性能风险的,至少要在程序中尽量少调用。直接内存的回收也是有其他办法的 比如 http://blog.csdn.net/xieyuooo/article/details/7547435 这个同学的博客讲的很清楚了。



补充下,任意时刻只有一块Survivor接受分配,另外一块是备用的,在minor gc的时候,接受可用对象的。这样彼此切换!
2 楼 406657836 2013-06-26  
baitian 写道
楼主,不严谨 ,提两点
1.
引用
当发生GC时,会把Eden区和其中一块Survivor往另外一块Survivor复制。

所谓的“其中一块Survivor”是何时被写入的?
2.
引用
当然这里可以打开DisableExplicitGC来解决。不过这也是一个危险的操作,意味着你允许了应用程序显示的调用System.gc(),所以在应用程序里面最好不要这么调用

恰恰相反,就是通过system.gc()去触发对直接内存的回收

嗯 谢谢指正呵。
对于第一个问题:
  对象分配首先会选择在Eden区(如果对象足够大,比如超过Eden区可用大小的一半,这个可以指定的,这里还和年轻代采用的回收器有关)就直接到老年代。如果是分配到年轻代会首先优先选择Eden区,也不排除有一部分进入survivor。在jvm启动的时候会选择任意选择其中一块survivor。如果Eden区不够分配了会触发minor gc。这时候就把之前选择的那块survivor和Eden的可用对象复杂到另一开survivor上去。

对于第二个问题:
  这个问题之前的表述确实存在很大的问题。应该改为"关闭DisableExplicitGC"(设为false),因为DisableExplicitGC的意思就是禁止显示调用gc。所以应该是要一个双重否定,让显示调用生效,已达到让System.gc()调用起作用,解决直接内存溢出问题。

不过仍然要注意,这个参数关闭掉是有性能风险的,至少要在程序中尽量少调用。直接内存的回收也是有其他办法的 比如 http://blog.csdn.net/xieyuooo/article/details/7547435 这个同学的博客讲的很清楚了。
1 楼 baitian 2013-06-25  
楼主,不严谨 ,提两点
1.
引用
当发生GC时,会把Eden区和其中一块Survivor往另外一块Survivor复制。

所谓的“其中一块Survivor”是何时被写入的?
2.
引用
当然这里可以打开DisableExplicitGC来解决。不过这也是一个危险的操作,意味着你允许了应用程序显示的调用System.gc(),所以在应用程序里面最好不要这么调用

恰恰相反,就是通过system.gc()去触发对直接内存的回收

相关推荐

    java gc调优

    在《浅谈JVM内存管理》的PPT中,可能包含了对上述概念的详细讲解,包括JVM内存模型的解析、GC算法的工作原理、如何配置和调整GC参数,以及通过实例分析GC调优的具体步骤。通过学习这个PPT,开发者可以深入理解JVM...

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

    ### Java分布式应用学习笔记02再谈JVM 在深入探讨Java虚拟机(JVM)时,我们再次聚焦于这个核心组件,它不仅是Java运行环境的心脏,也是构建分布式应用的关键技术之一。JVM作为Java语言的核心执行环境,其设计与...

    浅谈JAVA垃圾回收机制.pdf

    浅谈 JAVA 垃圾回收机制 Java 垃圾回收机制是 Java 语言中的一种自动内存管理机制,它可以自动回收内存中的垃圾,避免代码运行时由于忘记释放对象而带来的内存泄漏问题。 Java 中的垃圾回收机制主要通过两种算法来...

    浅谈关于Java的GC垃圾回收器的一些基本概念

    Java的垃圾回收(GC)是Java虚拟机(JVM)管理内存的重要机制,它自动识别并清理不再使用的对象,以防止内存泄漏。本文主要探讨Java GC的基本概念,涉及JVM内存模型以及不同的垃圾回收算法。 首先,让我们了解几种...

    Java面试宝典2017(Word版)

    7. **设计模式**:在高级面试中,设计模式是必谈的话题,例如单例模式、工厂模式、观察者模式、装饰器模式等,理解这些模式的应用场景和实现原理是提高代码质量的重要基础。 8. **JVM内存模型**:了解JVM的运行原理...

    java大公司面试题

    对JVM的理解也非常重要,面试者应熟悉Java内存模型(JMM)、堆内存和栈内存的分配、垃圾回收机制(GC)以及如何优化内存使用。深入理解JVM调优工具(如VisualVM、JProfiler)和常见的JVM参数也是必备技能。 网络...

    java软件工程师面试常见问题

    10. **Spring框架**:作为Java开发的主流框架,Spring的IoC(控制反转)和AOP(面向切面编程)是面试常谈话题。理解Bean的生命周期、依赖注入、事务管理以及Spring Boot和Spring Cloud的相关知识。 11. **数据库...

    阿里Java面试集锦

    谈到ClassLoader,Java的类加载器采用分层的结构,按照自顶向下的顺序加载类。包括引导类加载器(BootstrapClassLoader)、扩展类加载器(ExtensionClassLoader)和系统类加载器(SystemClassLoader)等。在加载过程...

    浅谈Java垃圾回收的实现过程

    4. 老年代GC(MajorGC):相对于Java垃圾回收过程,老年代是实例生命周期的最后阶段。MajorGC扫描老年代的垃圾回收过程。 5. 内存碎片:一旦实例从堆内存中被删除,其位置就会变空并且可用于未来实例的分配。这些空...

    JAVA互联网企业面试真题

    Spring框架是Java企业应用开发的主流框架,其IoC(控制反转)和AOP(面向切面编程)理念是面试常谈。面试者需熟悉Spring Boot、Spring Cloud等微服务相关技术,并理解依赖注入、事务管理、AOP的使用场景。 数据库...

    浅谈Java堆外内存之突破JVM枷锁

    浅谈Java堆外内存之突破JVM枷锁 本文主要介绍了Java堆外内存的概念,包括JVM内存分配、JVM垃圾回收、堆外内存的垃圾回收等相关内容。Java开发者都知道,Java中不需要手动申请和释放内存,JVM会自动进行垃圾回收;而...

    浅谈Java内存区域划分和内存分配策略

    "浅谈Java内存区域划分和内存分配策略" 本文将详细讲述Java内存区域划分和内存分配策略,涵盖程序计数器、虚拟机栈、本地方法栈、堆、方法区等内存区域的概念和作用,以及对象创建过程和内存分配策略。 程序计数器...

    Java面试宝典2018版【超全】很不错.zip

    除此之外,反射和注解也是面试常谈。反射机制可以动态获取类信息并操作对象,注解则提供了元数据,让代码更具可读性和可维护性。面试者应了解如何使用反射创建和调用类,以及注解的自定义和使用。 JVM(Java虚拟机...

    Java高级开发工程师面试笔记.zip

    6. **JVM**:垃圾回收机制(GC)、内存模型(堆、栈、方法区、本地方法栈)、类加载机制、性能调优技巧等都是面试常谈。 7. **数据库**:SQL优化、事务管理、索引原理、主从复制、分布式数据库解决方案如MySql、...

    Java虚拟机

    这本书的内容是帮你全面了解java虚拟机,本书第1版两年内印刷近10次,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的...

    浅谈Java内存泄露

    然而,当存在循环引用或者长生命周期对象持有短生命周期对象的引用时,即使短生命周期对象不再使用,由于GC Roots能间接访问到,也会导致这些对象无法被回收,从而产生内存泄漏。 例如,当一个静态集合如HashMap...

    浅谈Java引用和Threadlocal的那些事

    浅谈Java引用和Threadlocal的那些事 本文主要介绍了Java引用和Threadlocal的知识点,包括Java中的引用类型、Threadlocal的使用等。 Java中的引用类型: Java中有四种引用类型:强引用(Strong Reference)、软...

    浅谈java的守护线程与非守护线程

    浅谈java的守护线程与非守护线程 在Java中,有两类线程:UserThread(用户线程)和Daemon Thread(守护线程)。守护线程的作用是为其他线程的运行提供服务,比如说GC线程。它们的本质上来说没有区别,唯一的区别...

Global site tag (gtag.js) - Google Analytics