Java虚拟机内存分配机制与启动参数说明

 

    JVM按照其存储数据的内容将所需内存分配为堆区非堆区两个部分:所谓堆区即为通过new的方式创建的对象(类实例)所占用的内存空间非堆区即为代码、常量、外部访问(如文件访问流所占资源)等。java垃圾回收器GC专门用于回收堆内存,而对于非堆区的资源就束手无策了,非堆区只能由开发人员管理。

 

JVM启动内存分配一般由两组参数可配置,配置堆区:-Xms 、-Xmx、-XX:newSize、-XX:MaxnewSize、-Xmn;  配置非堆区:-XX:PermSize、-XX:MaxPermSize

 

配置堆区:-Xms 、-Xmx、-XX:newSize、-XX:MaxnewSize、-Xmn;
-Xms :表示java虚拟机堆区内存初始内存分配的大小,通常为操作系统可用内存的1/64大小即可,但仍需按照实际情况进行分配。
-Xmx: 表示java虚拟机堆区内存可被分配的最大上限,通常为操作系统可用内存的1/4大小。但是开发过程中,通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。(PS:当初始堆占满后,会尝试进行GC,如果GC之后还不能得到足够的内存,那么就会扩展堆,如果-Xmx设置的太小,扩展堆就会失败,导致OutOfMemoryError错误提示)
 一般来讲对于堆区的内存分配只需要对上述两个参数进行合理配置即可,但是如果想要进行更加精细的分配还可以对堆区内存进一步的细化,那就要用到下面的三个参数了-XX:newSize、-XX:MaxnewSize、-Xmn。当然这源于对堆区的进一步细化分:新生代、中生代、老生代。java中每新new一个对象所占用的内存空间就是新生代的空间,当java垃圾回收机制对堆区进行资源回收后,那些新生代中没有被回收的资源将被转移到中生代,中生代的被转移到老生代。而接下来要讲述的三个参数是用来控制新生代内存大小的。
-XX:newSize:表示新生代初始内存的大小,应该小于 -Xms的值;
-XX:MaxnewSize:表示新生代可被分配的内存的最大上限;当然这个值应该小于 -Xmx的值;

-Xmn:至于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置,也就是说如果通过-Xmn来配置新生代的内存大小,那么-XX:newSize = -XX:MaxnewSize = -Xmn,虽然会很方便,但需要注意的是这个参数是在JDK1.4版本以后才使用的。

 

 

配置非堆区:-XX:PermSize、-XX:MaxPermSize
-XX:PermSize:表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)
-XX:MaxPermSize:表示对非堆区分配的内存的最大上限。
这里面非常要注意的一点是:在配置之前一定要慎重的考虑一下自身软件所需要的非堆区内存大小,因为此处内存是不会被java垃圾回收机制进行处理的地方。并且更加要注意的是最大堆内存与最大非堆内存的和绝对不能够超出操作系统的可用内存。
如DDW项目启动Tomcat内存参数:
JAVA_OPTS="-server -Xms4096m -Xmx5096m  -XX:MaxNewSize=128m -XX:PermSize=256M -XX:MaxPermSize=512m -Djava.awt.headless=true -Dfile.encoding=UTF-8"

 

详细讲解某个GC日志:先有JVM配置,再有日志分析。

JAVA_OPTS="-Dfile.encoding=UTF-8 -server -Xms6192m -Xmx6192m -XX:+PrintGCDetails -Xloggc:/log/tomcat/gc.log -Dcom.sun.management.jmxremote=true -Djava.rmi.server.hostname=192.168.1.140  -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=9002"

第一条:63.971: [GC (Allocation Failure) [PSYoungGen: 31073K->4210K(38400K)] 31073K->4234K(125952K), 0.0049946 secs] [Times: user=0.05 sys=0.02, real=0.01 secs]

  • 63.971:gc发生时,虚拟机运行了多少秒。
  • GC (Allocation Failure) : 发生了一次垃圾回收,这是一次Minor GC 。注意它不表示只GC新生代,括号里的内容是gc发生的原因,这里的Allocation Failure的原因是年轻代中没有足够区域能够存放需要分配的数据而失败。
  • PSYoungGen: 使用的垃圾收集器的名字。
  • 31073K->4210K(38400K)指的是垃圾收集前->垃圾收集后(年轻代堆总大小)
  • 31073K->4234K(125952K),指的是垃圾收集前后,Java堆的大小(总堆125952K,堆大小包括新生代和年老代),因此可以计算出年老代占用空间为125952k-38400k = 87552k
  • 0.0049946 secs:整个GC过程持续时间
  • [Times: user=0.05 sys=0.02, real=0.01 secs]分别表示用户态耗时,内核态耗时和总耗时。也是对gc耗时的一个记录。

第二条:106.840: [GC (System.gc()) [PSYoungGen: 130695K->5216K(472576K)] 228903K->121835K(644608K), 0.0342911 secs] [Times: user=0.13 sys=0.00, real=0.03 secs]
  先对于第一条的(Allocation Failure) 变成了(System.gc()),说明这是一次成功的垃圾回收。
第三条:83.783: [Full GC (System.gc()) [PSYoungGen: 15361K->0K(372224K)] [ParOldGen: 83468K->98200K(172032K)] 98829K->98200K(544256K), [Metaspace: 9989K->9989K(1058816K)], 0.3036213 secs] [Times: user=1.03 sys=0.00, real=0.30 secs]
  对于Full GC:

  • [PSYoungGen: 15361K->0K(372224K)] :年轻代:垃圾收集前->垃圾收集后(年轻代堆总大小)
  • [ParOldGen: 83468K->98200K(172032K)]  :年老代:垃圾收集前->垃圾收集后(年老代堆总大小)
  • 98829K->98200K(544256K), :垃圾收集前->垃圾收集后(总堆大小)
  • [[Metaspace: 9989K->9989K(1058816K)], Metaspace空间信息,同上
  • 0.3036213 secs:整个GC过程持续时间
  • [Times: user=1.03 sys=0.00, real=0.30 secs] 分别表示用户态耗时,内核态耗时和总耗时。也是对gc耗时的一个记录。

第四条:71.601: [Full GC (Ergonomics) [PSYoungGen: 20555K->0K(367616K)] [ParOldGen: 104080K->83460K(172032K)] 124636K->83460K(539648K), [Metaspace: 9959K->9959K(1058816K)], 0.2146493 secs] [Times: user=0.64 sys=0.00, real=0.21 secs]
  这里可以到full gc的reason是Ergonomics,是因为开启了UseAdaptiveSizePolicy,jvm自己进行自适应调整引发的full gc。
 

 

Java内存模型与GC介绍(2017年2月28日补充)

进过博主再次学习(重点学习了一遍非常好的文章:http://www.importnew.com/13954.html),对JVM的内存结构,GC工作方式有了更加清楚的认识,再次大家共享.

首先上一份JVM的内存模型(JDK 1.7 1.8都有)

 

再做一些名词解释

堆区(heap):即为通过new的方式创建的对象(类实例)所占用的内存空间,包括新生代与老年代.
非堆区():即为代码、常量、外部访问(如文件访问流所占资源)。
PermGen(持久带):Java7中特有的,包含了虚拟机中所有可通过反射获取到的数据,比如Class和Method对象。Java SE库中的类和方法也都存储在这里。
Compress Class Space(类指针压缩空间):只有是64位平台上启用了类指针压缩才会存在这个区域。对于64位平台,为了压缩JVM对象中的_klass指针的大小,引入了类指针压缩空间。
Metaspace(元空间):元空间包含类的其它比较大的元数据,比如方法,字节码,常量池等。ClassLoader会用此内存空间
Code Cache(代码缓存):用于编译和保存本地代码(native code)的内存。
新生代(Eden和S0/S1):新生代是用来保存那些第一次被创建的对象。

GC回收哪些内容:堆区和非堆区Metaspace(Java7叫PermGen). 注意:GC在工作的时候会发生SWT(Stop The Word),导致整个应用不在响应任何请求,Stop-the-world会在任何一种GC算法中发生。

GC回收思想:

大部分的对象在生成后马上就变成了垃圾,很少有对象能活得很久。分代垃圾回收利用该经验,在对象中导入了“年龄”的概念,经历过一次GC后活下来的对象年龄为1岁。我们把刚生成的对象称为新生代对象,到达一定年龄的对象则称为老年代对象。针对不同代的对象使用不同的GC算法。对存活时间长一些的对象,可以减少扫描“垃圾”的时间,以减少GC频率和时长。GC执行的频率和STW两者是不能兼得的.

Java GC类型与执行过程

结合Java自带工具VisualVM(jvisualvm.exe),JConsole来查看Java运行时的内存结构

 

 


项目中走过的坑----CPU很高忙于GC,虚拟机内存在5秒内由1.5G变成9G
问题现象:公司项目在某个时刻CPU200%,都是在做GC操作,JVM堆内存在3秒内由2G升到6G,然后再1秒内回到2G,然后又3秒内升到6G,如此往复,导致应用反应巨慢,现象截图如下:


分析与结论:非常可惜这个问题始终无法在测试环境复现,但是在那段时间,会时不时的出现(有时3天有时5天间隔),在代码日志和业务方面排查也无任何进展,
最终分析可能存在的原因是权限问题,楼主业务系统由于前期权限设计不合理,导致权限表有300W数据,而这300W数据是会项目启动时一次性载入内存,并常驻内存的,
后面有用户注册、角色增加,就会往这300W的Map中增加数据,唯一可能性就是这常驻内存的300W的Map,Java的Map在达到一个阈值的时候,会进行扩容,
扩容是新建一个比以前更大的Map,并把以前的Map数据copy过来,然后丢弃以前的老Map,能够在3秒内吃掉6G内存,业务代码是做不到的,肯定是Java虚拟机自己做的。
后面楼主将权限重构,常驻内存也就2W左右的Map,重构之后就再也没有出现过那种问题。


项目中走过的坑----CPU很高忙于GC,虚拟机内存一直很高,最后OutOfMemoryError: unable to create new native thread
问题现象:公司项目在某个时刻CPU很高200%,都是在做GC操作,内存占用很高,系统接口反应巨慢,最后直接导致系统无响应
分析与结论:通过几千个日志的排查,最终发现有一个Dao接口没有做分页,导致数据库一次捞出25W个POJO,List<POJO>,然后Service层返回List.get(0);
一个下午这个接口被调用了412次,最终导致系统CPU很高,一直GC,应用宕机,血淋淋的教训啊

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值