java特种兵读书笔记(3-2)——java程序员的OS之虚拟机的板块

Heap区


Heap,堆,氛围Young和Old两个板块。两个板块在分配内存上和Heap很相似,但是回收差别很大。

通常Young空间会小很多。参考值Young大概是Old的1/4~1/3,

Young区


Young区被分为两个部分,三个板块。1个Eden区+2个Survivor区。

Eden区:使用new或者newInstance创建的对象默认都分配在Eden区,除非对象太大Eden区放不下,分配的时候直接进入Old区。

Survivor区:两个Survivor区通常称为S0和S1。理论上这两个区域一样大,可以通过一些参数设置来修改。

在不断创建对象的过程中,Eden区会满,如果Eden区满了,就开始做YoungGC,寻找Eden区还存活的对象,这些活着的对象向S0或者S1区域转移。假设第一次选择了S0,如果S0满了,剩下的活着的对象就会转移到Old区。接下来的动作是直接将Eden区清空。此时S1区是空的。

第二次Eden区满的时候,就将Eden区中活着的对象+S0区中活着的对象转移到S1区,如果S1区装不下,就转移到Old区。和上次一样,最后清空Eden区+S0区。

活着的对象


从根引用开始找对象,对象内部的属性可能也是引用,只要能被级联到的,都认为是活着的对象(包括CHeap区域的对象空间)。

根:本地变量引用,操作数栈引用,PC寄存器,本地方法栈引用,静态引用。

PC寄存器中可能包含了被从栈顶抛出的引用,这些引用正在被使用,只是它们不是本地变量,在声明周期内,不会被GC。

再比如new Thread().start(),虽然Thread本身没有java的引用指向它,但是只要这个线程没有结束,这个对象就会一直存在。

还有线程运行时,栈中的引用也是根,以及静态引用列表。

Survivor区


存活下来的对象很少——大多数java应用,如果代码没有问题,90%以上的对象,在创建之后都会被马上注销掉。比如客户端请求服务端的一段列表,服务器输出完毕之后,中间的list和map等已经没用了。包括容器,框架内在创建的对象也都没用了,除了一些需要自己cache住的对象,存活下来的对象很少。

因为存活下来的对象很少,所以即使Young区很大,那么找到这些活着的对象也很快。

因为活下来的对象很少,所以Survivor区(存放存活对象的地方)也不需要很大。

如果程序跑的很慢,对象存活的时间就会变长,被当做垃圾的概率就会变小,当Survivor区放不下的时候,就会进入Old区。所以GC不只和各个代的空间大小有关系,还与程序跑的快慢有关系

内存大块,或者很多内存用静态区引用,就很可能一直存活,进入Old区的概率也增大。

Old区


Old区和Young区不同,Old区有很多对象,认为它足够老,或者S0,S1空间不够了才会进Old区。

此时要找活着的对象比较困难,因为大部分的对象都是活着的(因为足够长命,后者不容易死的对象才会进入Old区)。JVM可以设置到10G或者更大,这时如果做FullGC,性能可想而知。

如果频繁的FullGC,而且这些对象在短时间内被注销的概率很低,那么会有越来越多的新的请求得不到响应或者响应很慢,它们所持有的内存无法释放掉,那么就会有更多的新的对象进入Old区,导致恶性循环。

Old区对象变多->FullGC->新的请求响应慢或者无响应->新的请求对应的内存无法释放->这部分内存进入Old区->继续FullGC->....

Old区清除机制


Old区不像Young区那样,95%以上的对象在GC时变为垃圾,所以JVM在设计上不会给Old区一块儿类似Survivor的空间存放活着的对象,那么Old区的清除操作就需要逐个遍历扫描,找到垃圾然后清除(Young区是转移还存活的对象到S0(或者S1),然后清空整个Eden区,S1(或者S1))。

FullGC的开销很大,所以它不是我们希望看到的GC。我们甚至希望它永远不发生。

FullGC因为有清除操作,因此会产生碎片(内存空间因为大小的原因无法被使用,所以内存不连续了),碎片太多会导致分配空间出现问题,就要做碎片整理(压缩)。或者的对象越多,碎片整理越困难,有可能要为了几个字节的碎片导致几百MB内存对象的移动(注意:JVM不会因为这里有几个字节的碎片,就找几个字节的对象填充在这里,它采用顺序移动的方式)。

对于多线程高并发的系统,每个请求对应的线程如果多几KB或者几MB,那么整体就会多出很多空间。

有些大对象在业务层面声明周期很短,但是因为参数设置等原因,进入了Old区。所以Survivor区的大小一定程度决定了Old区的增长速度以及做FullGC的频率。

Survivor区放不下,对象进入Old区叫做晋升。如果Old区空间不够,会先做一次FullGC,然后再晋升。

Perm


Perm是单独一个区,永久代,以后HotSpot VM是打算去掉这个区域的。

永久代的数据包括,常量池,加载的Class对象,static数据。

通过intern()调用一个String对象,那么这个对象会在Perm的常量池内分配空间,并通过eqauls保证值的唯一。如果大量不同的String通过intern()进入常量池,会逐步导致Perm区满,也会导致FullGC。在FullGC时,如果有引用指向常量区的String对象,就无法被回收,可能会导致OOM

Class本身也是对象,存放位置也在Perm区,如果加载大量对象,无法卸载的话,也会导致上述问题(ClassLoader下的任意class还有对象是活着的话,那么它就无法被卸载)。

为什么永久代以后可能会被删除掉


永久代的设计初衷,认为该区域几乎不会参与回收,就像我们通常认为Class只要被加载,就不会被改变一样。但随着字节码技术增强,intern可以向常量池中添加数据,使得永久代存在不确定因素。所以也会需要GC,管理起来类似于Heap了,所以可能没有单独划分的必要性了,未来可能删除。

静态数据


静态数据存储的位置是在方法区的内部,也就是Class所存在的位置内部。在Class初始化时,这块区域就被分配空间了。

静态引用的生命周期是从类的加载开始到类的卸载结束(包括进程退出)。

如果是final类型的静态引用,那么它引用的对象将与该引用同寿(因为该引用不可以再指向别的对象)。

非final的,对象将从创建开始一直存活,一直到这个静态引用的值发生变化,并且没有其他任何的引用指向该对象。

通常,如果是静态引用来引用对象,那么该对象往往是长寿的(经常做全局变动的,就没有用它的必要)。

方法与栈帧


程序进行方法调用时,JVM会为该方法分配栈帧。该空间是线程的私有栈,存放程序运行时的私有变量,后进先出栈等内容,在程序方法调用时分配。如果程序不断进行方法的深度调用,就会不断消耗栈空间,只要相应的方法完成后(return指令发生),才会释放对应的栈空间。如果程序中出现死递归,就会StackOverFlow


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值