字节码指令和虚拟机栈、栈帧
每个栈中包括局部变量表、操作数栈、指向运行时常量池的引用、方法返回地址和附加信息。
局部变量表:方法中定义的局部变量以及方法的参数存放在这张表中
操作数栈:以压栈和出栈的方式存储操作数的
动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用
方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理
源码
每一个字节码指令都能解释源码中做了什么
字节码指令宝典:
运行时数据区的相互指向
栈指向堆
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左右。
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更
大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。