java8之运行时数据

本文深入探讨Java运行时数据结构,重点讲解JVM内存分区,包括程序计数器、虚拟机栈、本地方法栈、堆、方法区及直接内存。详细分析对象创建、访问流程及Java8对PermGenSpace的移除,揭示对象生命周期和GC机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言
JDK8 的10个新特性总结如下:
Lambda Expressions
Pipelines and Streams
Date and Time API
Default Methods
Type Annotations
Nashorn JavaScript Engine
Concurrent Accumulators
Parallel operations
PermGen Space Removed
TLS SNI

第九条 PermGen Space Removed,移除永久代,why
1、减少OOM,而且开发人员并不总是知道PermSize和MaxPermSize设置为多少(真的不好判断~),java8开辟元空间(MetaSpace),放入Native Memory中,这样加载多少类的元数据不受设置参数影响
2、为了将来整合HotSpot 和 JRockit,JRockit一直都没有永久代
3、永久代为GC带来不必要的复杂度,并且回收效率不高(方法区功能决定了回收效率都不会高)
移除永久代带来的性能影响可以忽略(据说降低了不到1%的性能)

图片来自网络
图来自网络

java 运行时数据结构

在这里插入图片描述

  • jvm会在执行java的时候把它管理的内存分为几个区域,如上图所示
程序计数器
  • 线程私有
  • 当前线程所执行的字节码的行号指示器,由于CPU是分片执行,为了线程切换后能恢复到正确的线程,每个线程都要有自己独立的计数器
  • 唯一一个没有规定oom的区域
虚拟机栈
  • 线程私有
  • 生命周期与线程相同,每个方法执行的时候,都会创建一个栈帧(Stack Frame),用来存储局部变量、方法出口等信息
  • 栈帧分配多少内存基本上在类结构确定以后就定了,虽然编译器在运行期还会进行一些优化,但是大体认为该内存可知
  • 粗糙的说,java内存分为堆(heap)和栈(stack),栈 就是这里的虚拟机栈,或者说是局部变量表
  • 局部变量表,存储了编译期可知的各种类型,包括 基本类型(byte,int等),对象引用(reference类型),相当于一个指向对象起始地址的指针
  • 该区域的两种异常,1.栈的深度太长–stackOverflowError,2.oom
  • 栈的大小决定了
本地方法栈
  • 线程私有
  • 为native方法服务,sun的hotspot直接将本地方法和虚拟机栈合二为一
  • 抛出的异常同虚拟机栈决定了方法可达的深度
  • 线程共享
  • jvm中内存最大的一块
  • jvm启动时创建,目的就是存放java对象,“所有的对象实例和数组都要在堆上分配”,但也没有那么绝对了
  • 堆是GC负责的主要区域,从内存回收的角度来看,由于收集器基本都是采用 分代收集算法 ,所以java堆还可以细分为 年轻代老年代,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配
  1. Eden区 —— 新创建的对象(某些大对象特殊处理)都到了Eden,Eden区满了以后,这些对象会经历第一次minor GC ,仍然存活的被转移到Survivor,这些个对象每熬过一次Minor GC,可以认为增长了一岁,到了一定岁数(-XX:MaxTenuringThreshold来设置,默认15岁),就会被移动到老年代
  2. 每次Minor GC ,Survivor 的from to区会互换,最终到达老年代,具体的GC过程下一篇详解。
题外话:一个对象的这辈子

    我是一个刚刚出生的java对象,祖籍Eden区,这是是伊甸园,我认识了很多和我一样的小伙伴,我们还没有玩很久,有一天Eden区的人实在太多了,我被赶了出去。
    来到了Survivor区,和它叫的名字一样,这里是幸存者聚集地,幸存区也被分了两块,分别叫做 from和to,于是乎我开始了漂泊的生活,有时候在from,有时候在to,居无定所。
    直到我15岁的时候,他们说我老了,没什么用了,于是我就去了老年代养老,这里的哥们都看得开,该吃吃该喝喝,我们在这里愉快的生活了一段时间,然后光荣了被回收。

方法区
  • 线程共享
  • 存储已经被jvm加载的各个类的信息、常量、静态变量
  • 为与Java堆区分,方法区还有一个别名Non-Heap(非堆)
  • 有人称方法区为永久代,hotspot选择将GC扩展到方法区,但是其他虚拟机(JRockit,IBM J9)并没有永久代,1.8版本中已经彻底移除了永久代
  • 运行时常量池 ,存放编译期产生的各种字面常量和符号引用,1.7中将 字符串常量池 移出
  • 运行期间放入常量池,显示调用intern()方法
编外:直接内存
  • 直接内存不是运行时数据区的一部分,也成为堆外内存(direct memory),直接由操作系统控制,也有可能导致oom
  • JDK1.4中引入了NIO,New In/Out,可以直接分配堆外内存
  • 配置jvm参数时,如果忽略了直接内存,使得各个区域内存总和大于了机器的内存,导致oom也是有可能的
对象是如何创建的
  • 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,先执行相应的类加载过程
  • 接下来虚拟机将为新生对象分配内存,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来,这里有两种方式一种是“指针碰撞”(如果堆内存是绝对规整的),一种是“空闲列表”,选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定
  • 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头之中
  • 从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始——<init>方法还没有执行,所有的字段都还为零。所以,一般来说,执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来
对象是如何访问的
  • 这里基于最流行的hotspot来介绍
  • Object o = new Object();
  1. hotspot采取了直接指针访问方式, Object o 作为reference数据出现,reference中直接存储的是对象地址,如图
    加粗样式
  2. 优点是 速度快,节约了指针定位的时间
测试java8 常量池存储区域
String s = new String("abc"); //产生了两个对象
1.Class被CLassLoader加载时,"abc"被作为常量读入,在constant  pool里创建了一个共享的"abc"  
2.调用到new  String("abc")的时候,会在heap里创建这个new  String("abc"); 

ps 未经证实的,暂时的结论
关于 intern方法

  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());
        }
    }

通过以上代码测试,metaspace无增长,最终也异常也是heap溢出,1.7 1.8 均是heap溢出,1.6是PermGen 溢出,也就是说从1.7开始,常量池被转移到堆中
在这里插入图片描述

摘自一篇外文博客

Hotspot's representation of Java classes (referred to here asclass meta-data) is currently stored in a portion of the Java heap referred to as the permanent generation. In addition, interned Strings and class static variables are stored in the permanent generation. The permanent generation is managed by Hotspot and must have enough room for all the class meta-data, interned Strings and class statics used by the Java application.
The proposed implementation will allocate class meta-data in native memory and move interned Strings and class statics to the Java heap. Hotspot will explicitly allocate and free the native memory for the class meta-data. Allocation of new class meta-data would be limited by the amount of available native memory rather than fixed by the value of -XX:MaxPermSize, whether the default or specified on the command line.

永久代中的 class metadata 转移到了 native memory,永久代中的 interned Strings 和 class static variables 转移到了 Java heap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值