JVM:永久区浅学

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM:永久区浅学相关的知识,希望对你有一定的参考价值。

参考技术A 起因:公司不让用idea破解版开发了,需要转移到idea社区版。项目转移之后启动时遇到一个oom异常:java.lang.OutOfMemoryError: PermGen space
搜到的解决方法(确实可用,但不具体):-Xms1024M -Xmx2048M -XX:PermSize=128M -XX:MaxPermSize=256M

先了解一下参数的含义:

报着学习的态度,我对PermGen space进行了搜索。大部分人都说是堆内存中的一片区域,用于存放类的class文件。(事实上不是这样的)
因此我学到了两个知识点:1.PermGen space是堆内存(下面证实是错误的) 2.存放class文件
很开心
然后我想,既然在堆内,我只要扩大堆内存就好了,为什么要设置非堆内存?这不是多余么,于是我把后面的去掉了,仅仅扩大了堆内存,即参数调整为:-Xms1024M -Xmx2048M
java.lang.OutOfMemoryError: PermGen space又出现了。
我懵了,开始继续搜索学习JVM内存结构,堆内存结构。随后发现,我搜索堆内存结构的时候,有的提到了永久代,有的没有提到,有的说永久代不在堆中。
我开始实验,看仅设置非堆内存时,是否能解决此oom异常。于是设置参数:-XX:PermSize=128M -XX:MaxPermSize=256M
结果是解决了。
所以得出结论,PermGen space在非堆区内存中!下面对PermGen space做出详细介绍。

PermGen space只存在于java1.7及之前版本。提到PermGen space,默认场景中java版本为1.7
PermGen space又称:永久代、持久代、方法区、非堆区
是一块独立于Java堆的内存空间。

导致方法区内存溢出java.lang.OutOfMemoryError: PermGen space的场景:

-XX:PermSize 非堆区初始内存分配大小(默认值是20.75M)
-XX:MaxPermSize 非堆区内存最大值(32位机器默认是64M,64位机器模式是82M)

性能测试三十四:jvm内存结构(栈堆永久代)

 

Java内存管理机制

 

Java采用了自动管理内存的方式
Java程序是运行在Jvm之中的
Java的跨平台的基于Jvm的跨平台特性
内存的分配和对象的创建是在Jvm中
用户可以通过一系列参数来配置Jvm

 

Jvm运行时区域

做性能测试经常接触到的三个地方:方法区、虚拟机栈、堆

技术分享图片

jvm主要内存结构(简化版):

永久代即上面的方法区

占用内存大小排序:堆内存 > 永久代 > 栈内存

技术分享图片

一:栈内存(虚拟机栈)

特点:

1、线程私有

  每个线程都有一块自己独有的内存,如果有10个线程,则会产生10个栈内存,100个线程则100个栈内存


2、生命周期和线程相同

  线程的生命周期:当执行到main方法的时候,就会给这个线程分配一块栈内存,然后这个main方法会一直拥有这块内存,当main方法里面的代码执行完毕,线程就结束了,分配的这块内存就被回收了

技术分享图片

 

3、主要存放内容
  1.基本数据类型(int,char,float,double…)
  2.对象的引用,指向了对象在堆内存中起始地址,凡是定义的变量和new的对象的变量,都在栈内存里面放着

技术分享图片

  3.通过-Xss参数配置

    因为机器内存是固定的,所以如果这个参数配的越大,就导致并发线程数越少(如内存1G,每个线程配1M,则最多只能起1024个线程),所以一般栈内存的单位是K,如128K、256K都可以,取决于线程里面代码的长度,代码越长就需要越多的内存

 

 

二:堆内存-heap

堆内存构成
  新生代:包括三块区域,eden、from survivor(s0)、to survivor(s1)
  老年代:old gen

Object o = new Object()
其中,o存放在栈内存中,new Object()存放在堆内存中,变量o是Object对象的引用,o上存放了Object对象占用内存的起始地址

 

1.堆内存是Jvm中空间最大的区域
2.所有线程共享堆
3.所有的数组以及内存对象的实例都在此区域分配
4.堆内存大小通过参数进行配置
  -Xmx:最大堆内存
  -Xms:最小堆内存

堆内存是公有的,存放真正的对象,因为真正的对象有很多属性和信息,比较大,而栈内存比较小,所以放在堆内存中

技术分享图片

技术分享图片

New generation:新生代 / 年轻代

  新创建的对象,就放在eden区里面,当多线程运行起来以后,每秒钟都会有大量的对象被创建,而eden的容量有限,总有被放满的时候,当eden区满之后,会扫描eden+s0或者eden+s1,看有没有还存活(有用的,代码还没执行完,需要用到)的对象(这里以eden+s1为例),扫描完成后,会把eden+s1里面还有用的、存活着的对象复制到s0里面,并清空eden+s1,达到释放内存的目的,此时eden区任然每秒钟会产生大量的对象,过一段时间又会满,此时,s1是空的,则会扫描eden+s0,将eden+s0里面还存活的对象复制到s1里面,清空eden+s0······一直如此循环,所以s0和s1永远有个区域是空的,用于准备存放存活着的对象

  如果以10为单位的话,一般eden:s0:s1的比例是8:1:1

 

Old generation:老年代生代 / 老生代

   由于eden+s0/s1占了9/10,而要把存活下来的对象放到s0/s1,也就是说要把9/10里面的一部分放到1/10的空间里面,所以很有可能放不下,这个时候,新生代里面的空间已经不足了,所以会把存活的对象放到老年代里面,清空整个新生代空间(eden+s0+s1)

   在新生代做迭代扫描的时候,会给每个对象打个标记,当同一个对象的标记达到15次都还是存活状态,则此对象不再参与新生代的迭代,直接放到老年代空间(如,jmeter运行5分钟,脚本里面的前置条件,则会存活5分钟到结束才失效)

  所以老年代里面放的都是长期有效的对象

   当老年代空间也满的时候,会触发一次整个堆内存的清理(新生代+老年代),同样的,把有用的对方放在老年代,其他空间全部清空,达到释放空间的目的

 

 三:永久代-PermGen(方法区)(jdk1.8以后叫元空间)

永久代也叫(Method Area):

只要程序一运行起来,永久代里面的数据就已经固定了,比如一个类里面,定义了多少静态变量、常量,是固定的
各线程共享,主方法区要存放类信息、常量、静态变量,如:public static int a = 10
永久代里面放的都是些固定的、不变的数据,所以垃圾回收行为比较少见,当进程结束的时候,才做回收操作

 

 

Jvm结构总结


年轻代 = Eden+Survivor
Survivor = From Space(s0) + To Space(s1)
年轻代 = Eden+From Space+To Space
堆内存 = 年轻代+老年代
堆内存=Eden+From Space+To Space+老年代
jvm=栈+堆+永久代

 
























以上是关于JVM:永久区浅学的主要内容,如果未能解决你的问题,请参考以下文章

性能测试三十四:jvm内存结构(栈堆永久代)

JVM 1.8 永久代---元空间 的变动

JVM1.7和1.8的永久代和元空间的变化

永久区

JVM : 6 JVM分代模型:年轻代老年代永久代

JVM中的堆的新生代老年代永久代