JVM

1、Java内存分配

通俗来讲,分为堆内存和栈内存,细化来说,可分为以下几块:

程序计数器

记录当前线程执行的指令的地址。因为CPU同一时间只能执行一条指令,但CPU执行时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址。

存储基础数据类型(int,long,double,float,boolean,byte,char,short)以及对象引用。基础数据类型直接将值保存在栈中,引用类型的变量保存的是指向堆的指针。方法一旦结束,栈中的数据立刻销毁。类中的方法本身是指令的一部分,保存在栈中。栈的内存管理是顺序分配的,而且定长,不存在内存回收问题

本地方法栈

未执行本地方法服务(native method),部分JVM将其和栈合在一起。

存储对象本身,如被new出的对象,JVM中只有一个堆,为所有线程共有的内存区域,GC主要管理的就是这一块内存,当栈中没有变量指向堆中的对象时,GC回收扫描时就会将它销毁。堆的内存管理是随机分配内存,不定长度,存在内存分配和回收的问题

方法区(也在堆中?独立于堆中?)

所有线程共享,用于存储被虚拟机加载的类信息,常量,静态变量(静态变量在栈中,因此方法区可能也在栈中?),编译器编译后的代码等数据。方法区是堆的一个逻辑组成;该区域的数据很少被回收。常量池在方法区中。

注意:程序计数器、栈、本地方法栈的生命周期随线程的生命周期,这三块区域是线程专享;堆和方法区是线程共享.

JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指 定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。

JVM Exception

StackOverflowError:如果线程请求的栈内存超过虚拟机允许的大小,则抛出此异常。
OutOfMemoryError:如果虚拟机可以动态扩展栈(现在基本所有JVM都可扩展,但是规定了扩展的大小),但扩展时无法申请到足够的内存,则抛出该异常。

GC 垃圾回收算法

GC的作用是回收没用的对象,清理内存中的碎片。
垃圾回收要做的两件事:判断哪些对象可回收,回收被无用对象占用的内存空间,使该空间可再次被分配

垃圾是否可回收判断方法

判断是否对象可回收:GC 回收时,会先判断堆中的对象是否还在被引用,常见的判断方法为

引用计数法

给对象添加一个计数器,每当有一个地方引用它时,计数器就加1;引用失效时,计数器就减1;当计数器为0时,则认为该对象是不可能再被引用了,可以被回收了

可达性分析法

找一个对象作为Root(正在执行的Java程序可以访问的引用变量的集合(包括局部变量、参数、类变量)),向下搜索,搜索走过的路径称为引用链。当一个对象没有任何引用链可到达Root节点,则认为该对象可以被回收了。

GC垃圾回收算法

分配内存时、回收内存时,内存空间变得碎片化、不连续,需要清理,碎片清理将占用的内存移动堆的另一端:

标记清除法

先标记出堆中要回收的对象,然后回收被标记的对象占用的空间;该方法会造成较多的内存碎片

复制法

将内存空间分为均等的两块,先使用其中的一块,当这一块的内存用完了,就将这块中存活的对象复制到另一块空闲的内存中,然后将原先的内存全部清理掉,这样可以避免内存碎片。缺点是使得可用内存较少了一半。

标记整理法

和标记清除类似,也是先标记要回收的对象,然后将存活的对象向一端移动,使得内存分为两块,一端是存活的对象,一端是需回收的对象,然后再清理要回收的这一端,这样就不会产生内存碎片。

分代收集算法

将内存划分成三块:年轻代,年老代,持久代(堆内存=持久代大小+年轻代大小+年老代大小)

持久代

用于存放静态文件,如今Java类、方法等,持久代对垃圾回收没有显著影响。但是有些应用可能动态生成或者调用一些class,这时候需要设置一个较大的持久代空间来存放这些可能新增的类。持久代一般固定大小为64m。

年轻代

新生成的对象都是放到年轻代里。年轻代的目的是为了快速的回收生命周期短的对象。年轻代一般分为一个Eden区,两个或多个Survivor区。对象一般在Eden区中生成,当Eden区满了时,GC将清除非存活对象,将Eden区中还存活的对象移动到Survivor区(做了一次Scanvenge GC),当这个Survivor区满了时,将其中的存活对象移动到另一个Survivor区中(做了一次Scanvenge GC)。以此类推,当最后一个Survivor区满了时,找出这个Survivor区中还存活的且是从第一个Survivor区中移动过来的对象,将他们移动到年老代中(做了一次Scanvenge GC)。增加Survivor区的数量,可以增加对象在年轻代中的存在时间。每次Scanverge GC,该对象年龄增大一岁,当到达指定岁数时,可移到年老代中;当还没到达年龄时,年轻代又满了时,会优先将年龄大的移到年老代中。

年老代

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。年老代中可以认为存放的都是一些生命周期较长的对象。

常见设置:

-Xmx1024m:设置JVM最大可用内存为1024m
-Xms1024m:设置JVM初始内存为1024m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xss128k:设置每个线程分配的堆栈大小。该值越小,可分配的线程数越多,但OS一般对线程数有最大限制。
-Xmn1024m:设置年轻代大小为1024m。
-XX:MaxPermSize=16m:设置持久代大小为16m。持久代一般默认为64m。
-XX:NewRatio=4:设置年老代与年轻代(包括Eden和两个Survivor区,除去持久代)的大小比值。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

GC 分类

GC分为主GC和次GC

主GC

即FullGC,对整个堆进行整理,会暂停所有其他线程,应尽量减少主GC的次数。
触发条件:年老代被写满;持久代被写满;System.gc()被显示调用。

次GC

即Scanvenge GC,主要是在年轻代中出现。
触发条件:当Eden区或Survivor区满了时会被触发,清理非存活对象,将存活对象移动到Survivor区或年老代中。

减少GC的方法:

a、不要显示的调用 System.gc();该方法会建议JVM进行主GC,在有些情况下会触发主GC,增加程序停顿的次数。
b、尽量减少临时对象的使用。
c、对象不用时最好显示的置为 null;null 的对象会被当做垃圾处理,显示置为 null 利于垃圾回收。
d、尽量使用 StringBuffer,StringBuilder替代 String;String 累加字符串会创建多个对象。
e、尽量少用静态变量;静态变量不会被GC,一直会占用内存。
f、能用基础数据类型(int,long),就不用封装类型(如Long,Integer),占用空间更少。
g、尽量不在短时间内大量创建应用或删除应用

回收器选择(to be continue…)

JVM提供三种方案:串行收集器、并行收集器、并发收集器。串行收集器只适用于小数据量的情况,一般的JVM主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。

静态方法和非静态方法

当一个 class 文件被 ClassLoader load 进入 JVM 后,方法指令保存在 Stack 中,此时 Heap 区没有数据。然后程序技术器开始执行指令,如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问 Heap 数据区的(因为没有 heap 的地址指针);非静态方法则不同,非静态方法中隐含了指向 heap 的指针的隐含参数,用来指向方法对应的对象。如果是非静态方法,由于隐含参数没有值,会报错。因此在非静态方法执行前,要先 new 对象,在 Heap 中分配数据,并把 Stack 中的地址指针交给非静态方法,这样程序技术器依次执行指令,而指令代码此时能够访问到 Heap 数据区了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值