jvm内存概况

JVM内存包含大致如下:
jvm内存详解-----详解------详解


Java内存≈Heap(堆内存)+PermGen(方法区)+Thrend(栈)
Heap(堆内存)=Young(年轻代)+Old(老年代)
Young(年轻代)=EdenSpace+From Survivor+To Survivor

设置jvm内存关键字

关键字 解释 关键字 解释
-Xms 堆内存最小空间 -Xmx 堆内存最大空间
-XX:NewSize 新生代最小空间 -XX:MaxNewSize 新生代最大空间
-XX:PermSize 永久带最小空间(方法区) --XX:MaxPermSize 永久带最大空间(方法区)
-Xss 设置每个线程栈的大小 -XX:SurvivorRatio Eden和其中一个Survivor的比值
-XX:InitialTenuringThreshol 晋升到老年代的对象年龄的最小值 -XX:MaxTenuringThreshold 晋升到老年代的对象年龄的最大值

没有直接设置老年代大小的参数,但是可以通过设置堆内存和新生代内存大大小来计算老年代的大小。jvm中新生代和From survivor和To survivor的比例是8:1:1所以
老年代内存大小=堆内存-新生代内存大小-(新生代 x 1/5)
从更高的维度来看内存之间的关系会得到如下结论,方法去和堆内存都是所有线程共享的内存区域。而java栈是线程私有的内存空间。

Java堆内存(Heap)解释

对于大多数应用程序来说,Java堆是Java虚拟机所管理的虚拟内存中最大的一块内存,Java堆是被所有线程共享的一块内存区域。在虚拟机启动时创建,此内存唯一目的是存放对象实例。
Java对内存垃圾收集器(GC)的主要工作对象。
jvm堆内存的最大空间和最小空间的微妙之处

-Xms指定堆内存初始化分配的空间,如果不指定默认是物理内存的1/64;
-Xmx指定堆内存的最大分配空间,如果不指定默认是无内存的1/4;
默认情况下堆内存可用空间小于40%时候,JVM会增加堆内存到最大内存或物理内存的1/4;
默认情况下堆内存可用空间大于70%时候,JVM会减小堆内存到最小内存或物理内存的1/64;
因此服务器设置的初始化内存和最大内存值为一样,是为了避免GC发生后不断调整堆的大小。建议堆的最大值是可用内存的80%

Java Heap中包括年轻代和老年代,其实还包括一个Permanent。
年轻代保存实例化的对象,当该区被填满时,GC会将对象移动到old区。Permanent区负责保存反射对象。

Eden、From survivor、To survivor

介绍下堆内存中的Eden、From survivor、To survivor直接微妙的联系

  1. 我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。
  2. HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
jvm内存详解-----详解------详解

一个对象的这一辈子

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。

方法区(PermGen)解释

各个线程共享的内存区域,用于存储以备虚拟机加载的类信息,常量、静态变量、即时编译器、编译后的代码等数据。又名Non-Heap、永久代。并不是说数据进入永久代就永久存在了。
这个区域的回收主要是针对常量池的回收和对类型的写在,一般来说这个区域的回收成绩比较难以令人满意,尤其是类型的卸载条件相当苛刻。
jvm非堆内存的最大空间和最小空间的微妙感觉

方法区用于存放Class和Meta信息,Class在被Load的时候被放入该区域。它和存放类实例的Heap区域不同,GC不会再主程序运行期间对方法去进行GC操作。
-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;
-XX:MaxPermSize设置非堆内存最大值,默认是物理内存的1/4;
因为对PermGen区的GC回收条件比较苛刻,所以如果你的APP一次性会LOAD很多Class的话,可能会出现PermGen space错误的。

GC线程

Jvm有2个GC线程,第一个线程负责回收Heap的Young区。第二个线程在Heap不足时,遍历Heap,使Young区升级为Older区,Older区的大小等于堆内存大小减去新生代大小。不能将堆内存最小值设置过大,因为第二个线程被迫运行会降低jvm的性能。
频繁发生GC的原因有如下几个原因:

  • 程序内调用了System.gc()或Runtime.gc()
  • 一些中间件调用自己的GC方法,此时需要设置参数禁止这些GC。
  • Java的Heap太小,一般默认的Heap值都很小。
  • 频繁实例化对象,Release对象 此时尽量保存并重用对象,例如使用StringBuffer()和String().
    如果你发现每次GC后,Heap的剩余空间会是总空间的50%,这表示你的Heap处于健康状态,许多Server端的Java程序每次GC后最好能有65%的剩余空间。
    --------以下是网友经验之谈-------
    1.Server端Jvm最好将-Xms和-Xmx设为相同值。为了优化GC,最好让-Xmn(新生代)与等于堆最大内存的1/3。
    2.一个GUI程序最好每10秒到20秒运行一次GC,每次在半秒之内完成。

    注意

    1.增加Heap的大小虽然会降低GC频率,但也增加了每次GC的时间。并且GC运行时,所有的用户线程将暂停,也就是GC期间,Java应用程序不做任何工作。
    2.Heap大小并不决定进程的内存使用量。进程的内存使用量要大于-Xmx定义的值,因为Java为其他任务分配内存,例如每个线程的Stack等

    内存溢出

    弄java得程序员肯定对内存溢出这个不陌生吧,对我来说确实是一个挺无奈的关键字,之前出现这个问题,直接给jvm加内存,可是治标不治本,因为不知道原理,通过近期的研究算是吧jvm的基本机制弄得明白了,来看看jvm内存溢出的几种情况。

    OutOfMemoryError:Java heap space

    这是jvm的堆内存溢出,在jvm中98%的内存都在等待GC回收,且Heap Size不足2%的可用空间时,将抛出此错误,对应的解决办法增加-xmx和-xms的大小。

    OutOfMemoryError:PermGen Space

    这是jvm的非堆内存溢出,可能情况有如下几种

  • 如果web app下用大量的第三方jar,其总大小超过非堆内存的最大值,会抛出此异常
  • 项目拥有太多的class文件,恰好maxPermSize设置的小于class总大小,会抛出此异常
  • tomcat部署的时候,不会清理前面加载的环境,只会将context更改为新部署的代码,所以非堆内存越来越多

    OutOfMemoryError:unable to new native thread:

    这是jvm无法创建新线程,这个错误比较少见,也比较奇怪。主要是jvm与内存的比例有关。这种怪事因为jvm已经被系统分配了大量的内存。并且它至少占用可用内存的一半空间。可以尝试重启tomcat或加大非堆内存对应的值。

看到这里还请给小编点个赞吧,码文字很辛苦。。。 本文章只是小编的自己理解,如有不对的地方,请码友指正,欢迎评论,小编在评论器等待大家的身影