java 内存 新生代_Java内存区域

最近在看《深入理解Java虚拟机》,对以前不理解的java数据存储有了新的认识。

6b5e03e7a3a4

Java虚拟机运行时数据区

一、线程隔离区域

1. 程序计数器

我会把它理解成一种类似于指针的东西,每一个线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。它能够指向当前线程具体执行到了哪一个地方,并且虚拟机能够根据程序计数器来选取下一步的操作,比如分支、循环、跳转、异常处理等,并且由于它是线程私有的,所以也可利用它来进行线程之间的切换操作。

2. Java虚拟机栈

Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

3. 本地方法栈

和虚拟机栈功能类似,不同的是虚拟机栈是为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用的Native方法服务。

二、线程共享区域

1. Java堆

对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,JIT编译器上的逃逸分析和标量替换会让一些对象分配在虚拟机栈上

6b5e03e7a3a4

Java堆空间

Java堆空间可以细分为新生代和老年代,默认占用内存比例为:1:2(可修改)

Ⅰ 新生代

新生代主要用来存放新生的对象。一般占据堆空间的1/3。在新生代中,保存着大量的刚刚创建的对象,但是大部分的对象都是朝生夕死,所以在新生代中会频繁的进行MinorGC,进行垃圾回收。

新生代又可细分为:Eden空间、From Survivor空间、To Survivor空间,占用内存比例为8:1:1

Eden空间

Java新创建的对象绝大部分会分配在Eden区(如果对象太大,则直接分配到老年代)。当Eden区内存不够的时候,就会触发MinorGC(新生代采用的是复制算法),对新生代进行一次垃圾回收。

From 和 To空间

主要是为了处理GC而存在的,在GC刚开始的时候,对象只会存在于Eden区和From区,而To区是空的,一次MinorCG过后,Eden区和From区中存活的对象会被移动到To区中,然后Eden区和From区中的对象会被清空,并且存活的对象年龄加1,如果对象的年龄达到了15,则直接分配到老年代。MinorGC结束后,From区和To区功能互换,下一次MinorGC的时候,Eden区和To区中存活的对象移动到From区中,清空该区的对象,进行年龄控制

Ⅱ 老年代

老年代主要存放应用中生命周期长的内存对象。老年代比较稳定,不会频繁的进行MajorGC。而在MajorGC之前才会先进行一次MinorGc,使得新生的对象进入老年代而导致空间不够才会触发。当无法找到足够大的连续空间分配给新创建的较大对象也会提前触发一次MajorGC进行垃圾回收腾出空间。

在老年代中,MajorGC采用了标记—清除算法:首先扫描一次所有老年代里的对象,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长。因为要扫描再回收。MajorGC会产生内存碎片,当老年代也没有内存分配给新来的对象的时候,就会抛出OOM(Out of Memory)异常。

2. 方法区

用户存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

这是《深入理解Java虚拟机》中的原话,对此我产生了长时间的思考,那成员方法、静态方法又存在哪,为此我查阅到了两篇博客:

1、方法区中存放的都是在整个程序中永远唯一的元素,这也是方法区被所有的线程共享的原因。

2、类信息有哪些信息

方法区保存的就是一个类的模板,堆是放类的实例(即对象)的。栈是一般来用来函数计算的。随便找本计算机底层的书都知道了。栈里的数据,函数执行完就不会存储了。这就是为什么局部变量每一次都是一样的。就算给他加一后,下次执行函数的时候还是原来的样子。

3. 运行时常量池

运行时常量池是在方法区中的

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中。

解读一下:方法区中的常量池中可以抽象为两块内容(当然不是实际存在,实际就一块内容):

1、预置入常量,也就是上述所说的类信息。

2、还有一部分就是在程序运行时向运行时常量池放入的数据而产生的运行时常量池

与此相对应的,还存在一个静态余的空间,用于存放静态信息(静态属性、静态方法等),不过这部分内容我并没有在书中看见过

关于基本变量的存储位置

那么其实关于运行时常量池最经典的莫过于String类型,举个例子就是:

String a = new String("abc");

String b = "abc";

String c = new String("abc");

a==b? flase 它们在常量池中指向的内容是同一个,但它们不是同一个对象

String d = "def";

String f = b + d;

String g = "abc" + "def";

f==g? false b,d的值虽然已经存在运行时常量池中了,但是f在取它们的时候看见的引用而不是字面量

String e1 = "string";

String g1 = "str" + "ing";

e1 == g1? true

String h = (b+d).intern();

g == h? true intern()函数会在常量池里主动去找相同字符串

关于方法区,还有一个内容:

对于习惯在HotSpot虚拟机上开发,部署程序的开发者来说,很多人都更愿意把方法区称为“永久代”,,本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择二把GC分代手机扩展至方法区,或者说使用永久代来实现方法区而已

jdk1.6及jdk1.6以前,是有永久代这个概念的。

jdk1.7开始逐渐弃用永久代而使用元空间,比如原本放在永久代的字符串常量池被移出。

jdk1.8已经完全弃用永久代使用元空间

那么为什么要弃用永久代呢?

6b5e03e7a3a4

不想画图了qaq,图片来自知乎(https://zhuanlan.zhihu.com/p/111809384)

我不得不说这篇博文写的很好,想了解这一方面可以去细看一下:面试官 | JVM 为什么使用元空间替换了永久代?

下面是我的一些的总结

1、永久代的垃圾收集是和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收器。

2、元空间不再与堆连续,而且是存在于本地内存(Native memory)

3、当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。

4、默认情况下元空间是可以无限使用本地内存的,但为了不让它如此膨胀,JVM同样提供了参数来限制它使用的使用。

那么为什么要用元空间替代永久代?

1、避免OOM(OutOfMemoryError)异常,为此我们需要设定合理的上限值

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值