JVM内存划分及GC

java内存模型

  1. 程序计数器:程序执行到的位置
  2. 虚拟机栈:虚拟机栈是线程私有的,描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于记录存储局部变量表、操作数栈、动态链接、方法出口等信息。

    局部变量表:一组变量值存储空间,用于存放方法参数和方法内局部变量,以变量槽为基本单位(Slot),在非静态上下文中,局部变量表的0索引位置是当前变量,也就是this指针,然后是方法参数变量,再是局部变量。为节省局部变量表空间,slot可以重用,当局部变量脱离作用域时,作用域中的局部变量占用的slot就可以重用了。一个方法内的对象若没有脱离作用域则在局部变量表中有其refrence,则不能GC掉该对象,极端情况下会影响内存,因此不使用的对象应当及时置为null并不是没有用的,这种情况只存在于解释执行中,当JIT即时编译执行时,赋值为null会被消除,同时GC Roots的枚举也不同,局部变量表中未脱离作用域的对象可以被回收掉。局部变量必须赋初始值,类变量自动在初始化阶段赋值。


操作数栈:先进后出的栈,存放方法中字节码指令要操作的数据,比如两个数的加减乘除,方法调用时,会从栈顶取元素进行方法调用,“基于栈的执行引擎”中“栈”就是指操作数栈。

动态链接:所属方法的直接地址
java属于静态多分派,动态单分派,例如Father father = new Son();,此处Father是静态类型,Son是实际类型,依赖静态类型而决定的分派是静态分派,如方法的调用取决于方法参数的静态类型。而依赖实际类型而决定的分派是动态分派,java中调用哪个类的方法,取决于实际类型,所以属于动态分派。方法的调用者和方法参数记做宗数,当决定分派的宗数是1时叫单分派,宗数大于1时叫多分派,java中调用哪个方法取决于方法的调用者的实际类型也取决于方法参数,因此是多分派,而java中调用哪个类的方法,只需要知道方法参数类型和方法名,而方法的调用者就是实际类型,因此是单分派。
动态语言是指动态单分派的语言,没有静态类型,只有实际类型,像javascript,只知道实际类型,没有静态类型这一说。

方法返回地址:当前栈帧对应的方法调用函数返回后的执行位置,正常返回就是方法调用前程序计数器的位置,调用方法有异常抛出则返回地址由异常处理器确定

附加信息:虚拟机规范允许在栈帧中添加一些规范中没有描述的信息,例如调试相关,实际开发中一般会把动态链接,方法返回地址和其他附加信息归为一类,成为栈帧信息

  1. 本地方法栈:不归虚拟规范管

  2. java堆 存放对象实例和数组,而栈上分配和标量替换优化技术导致所有对象在堆上分配变得不再绝对,这一点没看到,以后看。

  3. 方法区:类信息等编译期得到的常量池中的字符、.class 文件中的信息、JIT编译后的代码数据等。

    方法区中有一部分区域称为运行时常量池,它包括编译后的.class文件中常量池的常量和运行期间加入运行时常量池的常量,加入方法是String.intern()。

  4. 直接内存:NIO操作的内存

对象的内存布局

对象在堆中有三个部分:对象头(header)、实例数据(Instance data)、和对齐填充(padding)必须占8个字节的整数倍。
对象头包括运行时数据和类型指针(元数据信息),其中类型指针是可选的,当存在对象句柄时类型指针存在句柄中。占用8个或16个字节。运行时数据是指HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等和对象本身无关的信息。

对象的访问定位有两种方式:一种是有句柄池的,句柄中有类型指针和对象实例数据指针,另一种是直接在对象中有类型指针也有对象实例数据指针的。

GC收集器和内存分配策略

java使用可达性分析GC
引用分为四种:强软弱虚

  • 强引用:最常见的
  • 软引用:内存不够时回收
  • 弱引用:只要GC就回收
  • 虚引用:不对该对象的回收产生任何影响,只和RefrenceQueue一起来监控对象回收

没有覆盖finalize或者执行过finalize方法的不执行finalize,若被判定执行finalze方法则会把引用放到JVM维护的一个F-Queue中,由一个低优先级的线程进行执行finalize方法,类似RefrenceQueue的使用,不推荐使用finalize方法,回收资源更好的方法是try-finally

方法区的回收:
运行时常量池中的常量不被引用时就会被回收
类的回收需要满足三个条件:
1. 该类所有实例被回收
2. 该类的ClassLoader类被回收,因此系统自带的ClassLoader肯定不会被回收系统加载的类肯定不会被回收
3. Class对象没有被引用,无法通过反射访问到

GC算法:
- 标记-清除算法:顾名思义,标记所有存活对象和回收对象和未使用对象,将回收对象标记为未使用对象
- 复制算法:一次用一半内存,回收时将这一半中存活着的对象复制到另一半内存中,Hotspot就采用这个原理。
- 标记-整理(标记-压缩):将存活对象一定到内存一端,最后直接清理掉分界另一端的内存
- 分代收集算法:一般分为新生代和老年代,新生代中使用复制算法,老年代中使用标记清除或标记整理算法。

HotSpot的实现:
枚举根节点通过OopMap来记录,并不是每一个指令都会有OopMap,只在特定位置有它,在类加载时就记录了对象在什么offset上有什么数据类型,所以不需要检测对象上所有的数据来找GC Root。

OopMap所在的位置称为安全点,只有在安全点才能GC,安全点的选定以“是否具有程序长时间执行的特征”,最明显的是方法调用、循环跳转、异常跳转,

当GC发生时如何让所有的线程在安全点上停下来:
- 抢先式中断:把所有线程中断,若有线程没有到安全点,就让它执行到安全点,几乎没用JVM使用这种方式
- 主动式中断:在安全点进行标识轮询,若标识为真就主动中断挂起

安全区:这个区域内引用关系不会发生变化,例如Thread.sleep()

内存分配和回收策略:
1. 对象优先在Eden分配,
2. 大对象直接进入老年代
3. 长期存活的对象进入老年代
4. 动态年龄判断,当S区相同年龄的对象达到S区大小的一半,将大于等于这个年龄的对象放入老年代

首先检查老年代空间是否大于新生代所有对象空间,如果没有则可能Minor GC可能失败, 若允许Minor GC失败,则以以往晋升到老年代的对象容量平均值作为参照,如果老年代剩余空间大于这个则进行Minor GC,若小于则会进行Full GC;若不允许担保失败,则直接进行Full GC。此外若允许担保失败且大于平均值,但是Minor GC还是失败了也会重新进行Full GC
原则还是当老年代没有足够空间时会Full GC,并且尽量减少Full GC次数,因为Full GC耗时较大

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值