jvm(二) 内存模型


在这里插入图片描述

字节码指令和虚拟机栈、栈帧

	每个栈中包括局部变量表、操作数栈、指向运行时常量池的引用、方法返回地址和附加信息。
	局部变量表:方法中定义的局部变量以及方法的参数存放在这张表中
	操作数栈:以压栈和出栈的方式存储操作数的
	动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用
	方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理

在这里插入图片描述
源码
在这里插入图片描述
在这里插入图片描述

每一个字节码指令都能解释源码中做了什么
字节码指令宝典:

运行时数据区的相互指向

栈指向堆

Object obj=new Object()
obj是引用,在栈中,new Object()在堆中

方法区指向堆

方法区存放常量,静态变量
private static Object obj=new Object();

堆指向方法区

方法区会存放类的信息,堆中有对象,怎么知道该对象是哪个类实例化出来的
java对象内存布局中,对象头中有class point,标识该对象实例是哪个类的。

java对象内存布局

一个java对象分成三个部分,对象头,实例数据,对齐填充区

在这里插入图片描述

之前并发编程中也有提过,对象头的mark word 里会保存锁的级别。
而对象实例也通过class point指向方法区,获得类信息

内存模型

	堆一般分为两部分:老年代和年轻代
	年轻代又分为两块:Eden 、Survivor区(S0+S1) 比例是8:1:1
	s0和s1一样打,也叫from区或者to区

在这里插入图片描述

对象创建所在区域

一般创建的对象都放在eden区,特殊的大对象会直接放老年代

年轻代Survivor区详解

survivor一般分为两块s0,s1,同一个时间点上,只有一个是有对象的,另一个是空的
GC是整个年轻代gc,每进行一次GC,对象年龄就大一岁,超过15时,就转移到老年代。
每次GC回收时,eden和from区没有回收的对象都放到to区,保持eden和from区空间连续
下次回收时,eden未回收的对象和有对象的s区再把对象放到空的s区
这样做是为了防止内存分配不连续,最大化的使用内存。
年轻代的回收叫做Minor GC。

老年代详解

超过一定年龄的对象存放的区域,老年代的回收叫做Major GC

永久代

永久代是HotSpot虚拟机特有的区域,是方法区的一种实现,其他的虚拟机都没有
1.8中被移除,变成了与堆不相连的本地内存--元空间
永久代的对象在full GC时进行垃圾收集。

对象的一辈子理解

	我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有
一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在
Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯
了。
于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次
GC加一岁),然后被回收。

在这里插入图片描述

常见的问题

如何理解Minor/Major/Full GC

Minor GC:新生代
Major GC:老年代
Full GC:新生代+老年代

为什么需要Survivor区?只有Eden不行吗?

Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16
次Minor GC还能在新生代中存活的对象,才会被送到老年代。

为什么需要两个Survivor区?

最大的好处就是解决了碎片化。
也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区。假设
现在只有一个Survivor区,我们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循
环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的
存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。
永远有一个Survivor space是空的,另一个非空的Survivor space无碎片

体验与验证

使用jvisualvm查看,模拟垃圾回收
jvisualvm
在这里插入图片描述
堆内存溢出
在这里插入图片描述

运行结果
Exception in thread "http-nio-8080-exec-2" java.lang.OutOfMemoryError: GC overhead limit
exceeded

方法区内存溢出

依赖和class
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>

在这里插入图片描述

代码

在这里插入图片描述

运行结果

java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_191]
at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_191]

虚拟机栈溢出(递归调方法)

在这里插入图片描述

运行结果

在这里插入图片描述

理解和说明

Stack Space用来做方法的递归调用时压入Stack Frame(栈帧)。所以当递归调用太深的时候,就有可能耗尽Stack
Space,爆出StackOverflow的错误。
-Xss128k:设置每个线程的堆栈大小。JDK 5以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线
程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有
限制的,不能无限生成,经验值在3000~5000左右。
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更
大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值