一. Java虚拟机的基本结构以及各个部分的作用
每个区域各司其职
- 1. 类加载系统负责从文件系统或者网络中加载class信息
- 2. 方法区存放加载的class信息
- 3. java堆存放几乎所有的对象实例。所有线程共享.
- 4. 直接内存可以用java nio库来分配,直接内存访问速度优于堆内存,但是分配速度会慢,如果频繁读写,那么可以使用它.
- 5. Java栈是线程独用的,线程创建时会分配.里面保存着局部变量,方法参数。
- 6. 本地方法栈用于本地方法调用
- 7. PC寄存器是每个线程的私有空间.如果一个线程正在执行的方法不是本地方法,它会指向当前正在被执行的指令.否则,它的值就是undefined.
二.java 堆结构
根据垃圾回收机制的不同,Java堆有不同的结构.最为常见的是java分为新生代和老年代.新生代又分为eden区,s0,s1区,s0,s1也被称为from和to区域,他们大小相等.
一个对象的分配过程可以这样来描述
- 1. new一个对象的时候会把它分配在eden区,
- 2. 进行一次回收后,如果这个对象还存在,会进入s0或者s1
- 3. 之后没进行一次垃圾回收,它的年龄就会+1。
- 4. 当它达到一定年龄时,就会进入老年代
三.函数如何调用
java栈是先进后出的数据结构,主要保存的数据结构是栈帧。
举例:假设函数1调用函数2函数2调用函数3,那么当函数1被调用时,入栈,函数2被调用时,入栈。以此类推, 一直执行,当前执行的函数对应的帧保存着当前函数对应 的局部变量,中间运算陈果.
Java又两种返回函数方式,return和抛异常.
一个栈帧中至少要包含局部变量表,操作数栈和帧数据.-Xss用来指定最大栈空间,它决定了函数调用最大深度.
局部变量表中的参数个数也决定了函数最大调用深度.用不断递归调用自身的方法可以使得栈空间溢出.
局部变量表
里面包含了索引(index ),名字(name),数据类型(descriptor),里面的每一行代表一个槽位,如果一个局部变量过了作用域,那么再它之后申明的变量会复用这个槽位.
举例说明: 加上这个参数查看gc情况. -XX:+PrintGC
public void local1(){
//数组被a引用,不会回收
byte[] a =new byte [1024*1024];
System.gc();}
public void local2(){
//byte失去引用,顺利回收
byte[] a =new byte [1024*1024];
a=null;
System.gc();
}
public void local3(){
//虽然离开作用域,a仍然存在局部表量表
{
byte[] a =new byte [1024*1024];
}
System.gc();
}
public void local4(){
//a的槽位被c占用
{
byte[] a =new byte [1024*1024];
}
int c= 1;
System.gc();
}
public void local5(){
//方法执行结束,栈空间弹出,栈帧被销毁
local1();
System.gc();
}
public static void main(String[] args) {
LocalVarTableTest l = new LocalVarTableTest();
l.local1();
}
栈上分配
栈上分配是一项优化技术.对于那些不会被其他线程访问到的对象,直接打散分配在栈上.函数调用结束后自行销毁,不需要垃圾回收.
它的一个技术基础是进行逃逸分析,判断对象的作用域是否能逃逸出函数体,例如,静态成员变量,以及return 返回.
例子:jvm参数-server -Xms10m -Xmx10m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:-UseTLAB -XX:+EliminateAllocations
可以去掉-XX:+DoEscapeAnalysis -XX:-UseTLAB -XX:+EliminateAllocations
public class StackRunawayTest {
private static class User{
public int id =0;
public String name="";
}
public static void alloc(){
User u = new User();//对象头暂居8个字节
u.id =1 ;//4个字节
u.name="ffff";//4个字节
//总共16个字节.
}
public static void main(String[] args) {
for(int i = 0;i<3;i++){
//创建1亿个对象,约等于1.5g,如果堆空间小于这个值可定会抛出异常
alloc();
}
}
}
试一试两种情况执行的效率以及GC的情况,你就会发现使用逃逸分析的效率明显高于不使用逃逸分析.
方法区
方法区在jdk1.6,1.7可以理解为永久区(Perm).默认是64M.用参数
-XX:PermSize -XX:MaxPermSize设置.如果系统使用了动态代理,那么就会出现大量的类把空间撑爆。
在jdk1.8中,取而代之的是元数据区.用参数-XX:MaxMetaspaceSize指定.这个属于堆外直接内存,如果不指定大小,会耗尽所有内存.