当前位置: 首页 > 编程日记 > 正文

Java8内存模型—永久代(PermGen)和元空间(Metaspace)

 

一、JVM 内存模型

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

1、虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值,我们通过下面这段程序可以测试一下这个结果:

栈溢出测试源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.paddx.test.memory;
public class StackErrorMock {
    private static int index = 1;
    public void call(){
        index++;
        call();
    }
    public static void main(String[] args) {
        StackErrorMock mock = new StackErrorMock();
        try {
            mock.call();
        }catch (Throwable e){
            System.out.println("Stack deep : "+index);
            e.printStackTrace();
        }
    }
}

代码段 1

运行三次,可以看出每次栈的深度都是不一样的,输出结果如下。

至于红色框里的值是怎么出来的,就需要深入到 JVM 的源码中才能探讨,这里不作详细阐述。

虚拟机栈除了上述错误外,还有另一种错误,那就是当申请不到空间时,会抛出 OutOfMemoryError。这里有一个小细节需要注意,catch 捕获的是 Throwable,而不是 Exception。因为 StackOverflowError 和 OutOfMemoryError 都不属于 Exception 的子类。

2、本地方法栈:

这部分主要与虚拟机用到的 Native 方法相关,一般情况下, Java 应用程序员并不需要关心这部分的内容。

3、PC 寄存器:

PC 寄存器,也叫程序计数器。JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是 JVM 的方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native 方法,则PC寄存器中为空。

4、堆

堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。下面我们简单的模拟一个堆内存溢出的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class HeapOomMock {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<byte[]>();
        int i = 0;
        boolean flag = true;
        while (flag){
            try {
                i++;
                list.add(new byte[1024 1024]);//每次增加一个1M大小的数组对象
            }catch (Throwable e){
                e.printStackTrace();
                flag = false;
                System.out.println("count="+i);//记录运行的次数
            }
        }
    }
}

代码段 2

运行上述代码,输出结果如下:

注意,这里我指定了堆内存的大小为16M,所以这个地方显示的count=14(这个数字不是固定的),至于为什么会是14或其他数字,需要根据 GC 日志来判断,具体原因会在下篇文章中给大家解释。

5、方法区:

方法区也是所有线程共享。主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。 关于方法区内存溢出的问题会在下文中详细探讨。

二、PermGen(永久代)

绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。我们现在通过动态生成类来模拟 “PermGen space”的内存溢出:

1
2
3
4
package com.paddx.test.memory;
public class Test {
}

代码段 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.paddx.test.memory;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class PermGenOomMock{
    public static void main(String[] args) {
        URL url = null;
        List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
        try {
            url = new File("/tmp").toURI().toURL();
            URL[] urls = {url};
            while (true){
                ClassLoader loader = new URLClassLoader(urls);
                classLoaderList.add(loader);
                loader.loadClass("com.paddx.test.memory.Test");
            }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码段 4

运行结果如下:

本例中使用的 JDK 版本是 1.7,指定的 PermGen 区的大小为 8M。通过每次生成不同URLClassLoader对象来加载Test类,从而生成不同的类对象,这样就能看到我们熟悉的 "java.lang.OutOfMemoryError: PermGen space " 异常了。这里之所以采用 JDK 1.7,是因为在 JDK 1.8 中, HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。下面我们就来看看 Metaspace 与 PermGen space 的区别。

三、Metaspace(元空间)

其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。我们可以通过一段程序来比较 JDK 1.6 与 JDK 1.7及 JDK 1.8 的区别,以字符串常量为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock {
    static String  base = "string";
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i=0;i< Integer.MAX_VALUE;i++){
            String str = base + base;
            base = str;
            list.add(str.intern());
        }
    }
}

这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存。我们通过 JDK 1.6、JDK 1.7 和 JDK 1.8 分别运行:

JDK 1.6 的运行结果:

JDK 1.7的运行结果:

JDK 1.8的运行结果:

从上述结果可以看出,JDK 1.6下,会出现“PermGen Space”的内存溢出,而在 JDK 1.7和 JDK 1.8 中,会出现堆内存溢出,并且 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。因此,可以大致验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代的结论。现在我们看看元空间到底是一个什么东西?

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

现在我们在 JDK 8下重新运行一下代码段 4,不过这次不再指定 PermSize 和 MaxPermSize。而是指定 MetaSpaceSize 和 MaxMetaSpaceSize的大小。输出结果如下:

从输出结果,我们可以看出,这次不再出现永久代溢出,而是出现了元空间的溢出。

四、总结

  通过上面分析,大家应该大致了解了 JVM 的内存划分,也清楚了 JDK 8 中永久代向元空间的转换。不过大家应该都有一个疑问,就是为什么要做这个转换?所以,最后给大家总结以下几点原因:

1、字符串存在永久代中,容易出现性能问题和内存溢出。

2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

4、Oracle 可能会将HotSpot 与 JRockit 合二为一。

转载于:https://www.cnblogs.com/williamjie/p/9563766.html

相关文章:

web server大全之GoAhead移植(转载)

转自:http://linux.chinaunix.net/techdoc/develop/2009/06/19/1119124.shtml 注&#xff1a;最近在做goAhead web server和移植其到TI芯片linux上&#xff0c;这里先转一篇相关的文章来学习下&#xff0c;希望有所帮助。。。 ******************************* * web server大全…

tomcat苹果版安装步骤_Mac系统安装和配置tomcat步骤详解

一:下载打开Apache Tomcat官网,选择需要的版本下载:二:存放到本地文件夹重名民为ApacheTomcat,放到/Users/计算机名/Library/目录下三:启动Tomcat打开Terminal,进入ApacheTomcat所在目录的bin目录下$:cd /Users/mymac/Library/ApacheTomcat/bin1启动Tomcat$:./startup.sh1如果出…

Oracle 11g 数据类型

Oracle 11g Datatypes VARCHAR2(size [BYTE | CHAR]) 可变长度的字符串的最大长度有大小字节或字符。最大尺寸是4000字节或字符&#xff0c;最低为1个字节或1个字符。您必须指定VARCHAR2大小。 字节表示该列将有字节长度语义。 CHAR列表示将字符语义。 NVARCHAR2(size) 可变长度…

《DSP using MATLAB》Problem 6.3

天不亮又醒了&#xff0c;拍了张景象&#xff0c;这就是黎明前的黑暗吗 转载于:https://www.cnblogs.com/ky027wh-sx/p/9569038.html

进程间通信--命名管道

几个术语 二义性&#xff1a;当我们往一个管道里面写端写数据的时候&#xff0c;比如写一个hello的时候&#xff0c;当我们写到he的时候&#xff0c;读端就已经开始读取数据了&#xff0c;所以这是不对的&#xff0c;这就是二义性临界资源&#xff1a;多个流可以访问的一个共同…

websocket心跳链接代码_WebSocket原理与实践(五)--心跳及重连机制

在使用websocket的过程中&#xff0c;有时候会遇到网络断开的情况&#xff0c;但是在网络断开的时候服务器端并没有触发onclose的事件。这样会有&#xff1a;服务器会继续向客户端发送多余的链接&#xff0c;并且这些数据还会丢失。所以就需要一种机制来检测客户端和服务端是否…

【转载】Asp.Net 全生命周期

用三张图片详解Asp.Net 全生命周期 此文是转载阳阳多的博客内容&#xff0c;特此声明。 下面我们使用三张图片解析ASP.net的整个生命周期&#xff0c;我总感觉使用图片更加的清楚的说明这种问题&#xff0c;所以使用的这样方式 说明&#xff1a; 1 第一张图片从全局说明从客户…

Context-Based Access Control (CBAC) 基于上下文的访问控制 理论知识

CBAC即基于上下文的访问控制协议&#xff0c;通过检查防火墙的流量来发现管理TCP和UDP的会话状态信息。这些状态信息被用来在防火墙访问列表创建临时通道。通过在流量一个方向上配置ip inspect列表&#xff0c;放行其返回流量。被允许会话是指来源于受保护的内部网络会话。它不…

week6 10 后端backend server和mongoDB通信

0 之前我们maogoDB用的是在线的mlab 在线他们帮我们做好了model 也就是那个schma 其实python也有类似的包 帮我们定义这些model 但是呢 我们自己来做吧 用一个传统的意义上mongoDB 就是 insert select来操作数据库 就是在不用其他类库 定义的model 我们如何操作数据库 我们这次…

Linux网络端口

Linux下端口个数 首先简单介绍一下Linux下的端口的函数&#xff0c;当Linux各个主机之间进行通信的时候我们需要将某些数据进程传输&#xff0c;这个时候就需要将数据传入到某一个特定 的主机&#xff0c;这个时候就使用了TCP/IP协议&#xff0c;IP地址是用来标识互联网的唯一…

汉字书写解码_《汉字解码学》   第一部分

《汉字解码学》第一部份汉字与世界密码文字的关系文字是人类语言的书写符号&#xff0c;是人类用来进行传递信息以便进行交流的可见符号系统。文字是人类文明的标识和产物。我们的祖先是如何来到中国的&#xff1f;世界科学研究关于人类基因的研究的最新成果表明&#xff1a;世…

WCF中服务继承多个契约的使用

服务继承多个契约其实也就是服务类实现了多个接口&#xff0c;主要是在配置中需要添加多个endpoint&#xff0c;各个endpoint之间的address不同、contract不同 契约&#xff1a; [html] view plaincopyprint?[ServiceContract] public interface IReportService { [Ope…

Pentaho平台上加仪表盘插件步骤

Pentaho平台上加仪表盘插件步骤 下面我将最近学习的在Pentaho平台上加仪表盘插件的步骤稍微整理了一下&#xff0c;希望对有需要的朋友有些帮助。 1. 解压插件包&#xff1a;cdf-de_0.2.tar.bz2&#xff08;这个插件压缩包我即将上传&#xff09; &#xff0c;得到cdf- de_0.2 …

2018.09.01 poj3071Football(概率dp+二进制找规律)

传送门 概率dp简单题。 设f[i][j]表示前i轮j获胜的概率。 如果j,k能够刚好在第i轮相遇&#xff0c;找规律可以发现j,k满足&#xff1a; (j−1)>>(i−1)(j−1)>>(i−1)^1(k−1)>>(i−1)1(k−1)>>(i−1)。 简单举个例子&#xff1f; 假设有八个人&a…

循环冗余检验CRC

CRC简介 循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数&#xff0c;主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。 在数据传输过程中&#xff0c…

弹性碰撞后速度方向_$1.1.1 弹性碰撞经典例题1——力学及运动学

[ph1] 质量为2m的木块放置于质量为m的长木板上&#xff0c;木块与模板之间的动摩擦系数为 μ &#xff0c;木板与地面的摩擦忽略不计。木块和木板以速度V0向右运动&#xff0c;在右侧足够远处有刚性墙壁&#xff0c;木块与墙壁发生完全弹性碰撞后向左运动&#xff0c;木板有足够…

一个简单实用的,基于EF的三层架构

到底什么样的框架才是好框架呢?或许不同人有不同的看法.我个人觉一个好的框架,最重要的要是简单实用,能快速适开发,可维护性高(不会出现复制黏贴的代码),并能快速响应各种业务场景的变化的框架,同时性能不会太差.我觉的这样的框架,就是一个好的框架.而且,我觉的做框架,千万不能…

转:中国互联网十五年的22个创新模式

中国互联网十五年的22个创新模式 今天&#xff0c;看网上有人推荐《沸腾十五年》&#xff0c;讲中国互联网从发源到现今。 有人有如此梳理&#xff0c;自己本来也想梳理一下中国互联网这么多年&#xff0c;到底是哪些公司出来了&#xff0c;为什么会是他们出来了。他们的…

2018 蓝桥杯省赛 B 组模拟赛(一)-年龄

今天蒜头君带着花椰妹和朋友们一起聚会&#xff0c;当朋友们问起年龄的时候&#xff0c;蒜头君打了一个哑谜&#xff08;毕竟年龄是女孩子的隐私&#xff09;说&#xff1a;“我的年龄是花椰妹年龄个位数和十位数之和的二倍”。 花椰妹看大家一脸懵逼&#xff0c;就知道大家也不…

C#中用ILMerge将所有引用的DLL和exe文件打成一个exe文件

今天做了一个软件,想发布的时候才发现调用的类没几个,就像把它们都跟EXE文件打包在一起,以后复制去别的地方用也方便,于是上网搜了一下,发现网上大部分都是用ILMerge实现的,于是也自己试了一下,不过网上都没有详细的步骤演示,我就花点时间做了个教程,方便以后再有人想打包自己的…

IPC--消息队列

消息队列的特点 消息队列是生命周期是随进程的&#xff0c;同时消息队列可以实现的是消息的传递方向是双向的。接受者和发送者时通过发送一个数据块来进性消息传递的&#xff0c;每个消息的数据类型不一样依次来进行区分我们该取哪个数据。消息队列是基于消息的&#xff0c;并…

sqlinesdata教程_如何将Oracle数据导入MySQL

Manager进程&#xff1a;需要源端跟目标端同时运行&#xff0c;主要作用是监控管理其它进程&#xff0c;报告错误&#xff0c;分配及清理数据存储空间&#xff0c;发布阈值报告等Extract进程&#xff1a;运行在数据库源端&#xff0c;主要用于捕获数据的变化&#xff0c;负责全…

关于PHP.ini文件的设定

php.ini文件中记录了php的配置&#xff0c;因此正确读取此配置文件对于php的部署实施很重要。 windows平台中&#xff0c;有2种常用的方法。 第一种方法&#xff1a;把php.ini复制到c:\windows目录中。 第二种方法&#xff1a;配置apache服务器&#xff0c;在..\Apache Softwar…

PAT (Advanced Level) 1132~1135:1132 模拟 1133模拟(易超时!) 1134图 1135红黑树

1132 Cut Integer&#xff08;20 分&#xff09; 题意&#xff1a;将一个含K&#xff08;K为偶数&#xff09;个数字的整数Z割分为A和B两部分&#xff0c;若Z能被A*B整除&#xff0c;则输出Yes&#xff0c;否则输出No。 分析&#xff1a;当A*B为0的时候&#xff0c;不能被Z整除…

poj 1523(无向联通图的割点)

结合tarjan算法思想&#xff0c;这题终于写了出来。 同样用dfs将图变成为一颗树&#xff0c;这样可以提供许多有用的性质。 对于一个无向连通图&#xff0c;dfs后的树为只有回边&#xff08;回边Euv,v是u的祖先&#xff09;和生成树的边的图。 那么在遍历到一个点u的时候&#…

IPC--信号量

信号量概念理解 信号量本质上 是一个计数器&#xff0c;用来统计临界资源申请资源的个数。其中的二元信号量的 值是0或者是1&#xff0c;即是要么是有&#xff0c;要么是无。信号量本身也是临界资源&#xff0c;所以一定要保证其原子性。信号量的工作原理&#xff1a;两个进程…

7 自动开启网卡_淘汰的旧手机别扔掉,这样设置变身4G上网卡

很多人都用过usb无线上网卡&#xff0c;把手机SIM卡插到上网的卡槽内&#xff0c;然后把usb上网卡插到电脑usb口&#xff0c;电脑安装好驱动程序后&#xff0c;即可畅游网络世界。当初3G上网卡价格不菲&#xff0c;随着更新换代4G快要过去&#xff0c;5G开始试商用&#xff0c;…

Struts2 的stream result用法

2019独角兽企业重金招聘Python工程师标准>>> <action name"download" class"com.unmi.action.DownloadAction"> <result name"success" type"stream"><!--type 为 stream 应用 StreamResult 处理-->…

Largest Rectangle in a Histogram

ps&#xff1a;单调栈&#xff0c;注意红色部分的代码。 int n;stack<P> s;inline void upd(LL &x, LL y) { (x < y) && (x y); }int main() {while(sc(n) ! EOF && n) {while(!s.empty()) s.pop();LL ans 0;Rep(i, 1, n) {int x;sc(x);if (s.e…

IPC--共享内存

有了之前的学习经验&#xff0c;共享内存对我们学习起来相对简单一些了&#xff0c;这里简单说说共享内存的一些&#xff0c;然后对于函数的分析直接在代码里面的注释部分有说明&#xff0c;如果还是不懂&#xff0c;可以先看看前面的关于IPC–信号量还有IPC–消息队列的讲解 …