以上是java虚拟机的整体内存图,其中栈,本地方法栈,程序计数器会跟随线程的创建而创建,是线程独享的,而堆和方法区是线程共享的区域。下面对每一块区域做深入剖析。
首先先从栈说起,当线程在执行的时候会在内存中分配一块内存区域,用于存放栈帧,栈帧可以理解为对方法调用的一种数据结构,每当执行一个方法的时候就会向当前线程栈中压入栈帧,比如,A调用B,B又调用C,那么栈帧自上而下顺序为C>B>A,C就为当前栈帧。栈帧中存储着局部变量表、操作数栈、动态链接、方法出口。
以上是栈的内存模型,局部变量表顾名思义,存放的是方法内的基本数据类型的变量、对象的引用;操作数栈是对数的加减乘除运算,下面以一段简单的代码来理解操作数栈
public class TestClass {
public static void main(String[] args) {
int a=1,b=2;
int c=a+b;
int d=c+a;
}
}
通过指令javap -verbose TestClass.class 反编译如下
Code:
stack=2, locals=5, args_size=1
0: iconst_1 向操作数栈中压入常量1
1: istore_1 从操作数栈中弹出并存入局部变量表,solt索引为1的位置
2: iconst_2 向操作数栈中压入常量2
3: istore_2 从操作数据中弹出并存入局部变量表,solt索引为2的位置
4: iload_1 从局部变量表solt为1的位置加载变量值
5: iload_2 从局部变量表solt为2的位置加载变量值
6: iadd 加运算操作
7: istore_3 从操作数栈中弹出并存入局部变量表,solt索引为3的位置
8: iload_3 从局部变量表solt为3的位置加载变量值
9: iload_1 从局部变量表solt为1的位置加载变量值
10: iadd 加运算操作
11: istore 4 从操作数据中弹出并存入局部变量表,solt索引为4的位置
13: return 返回
LineNumberTable:
line 13: 0
line 14: 4
line 15: 8
line 16: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
2 12 1 a I
4 10 2 b I
8 6 3 c I
13 1 4 d I
以上通过反编译指令,简述了操作数栈和局部变量表间的工作流程的理解。
方法出口中存着上一个栈帧中的位置,这样程序才知道在当前栈帧执行完毕后,该回到上一个栈帧中的什么位置。
当线程执行的时候,会在内存中分配一块区域,用于存储c语言函数调用栈帧,java中native修饰的方法都叫本地方法。
程序计数器事实上是记录了程序中指令的执行位置,如下图
程序计数器记录的是指令前面的地址,在单核处理器的机器上,假如存在两个线程,其中A线程睡眠,B线程执行,假如没有程序计数器的话,当线程切回A线程的时候,就不知道程序执行到什么位置,因此,程序计数器就是记录了程序执行的位置。程序计数器中的值是实时变化的,仅仅存放一个数字,所占内存很小,因此不会存在内存溢出。
类加载分为几大步骤:加载->验证->准备->解析->初始化->使用->卸载
加载:jvm是一种懒加载,只有在类使用到的时候才会从硬盘把class文件通过IO读取到内存,并把类信息存到方法区;
验证:校验字节码信息的正确性;
准备:给类的静态变量分配内存,并赋予默认值;
解析:将符号引用替换为直接引用,其实就是把指针指向堆中真实的内存的地址;
初始化:对类的静态变量初始化为指定的值,执行静态代码块;
启动类加载器(BootstrapLoader):主要负责加载jre下lib目录下的核心类库;
扩展类加载器(ExtClassLoader): 主要负责加载jre下lib目录下ext扩展目录下的核心类库;
应用类加载器(AppClassLoader):主要加载自己定义的类
自定义加载器:加载用户指定路径下的类
何为双亲委派机制,一开始听到这个名词,完全很懵。在不考虑自定义加载器的情况下,所有的类加载都是从应用类加载器开始,然后这其中应用类加载器会去判断是否有父级加载器,会一级一级向上委托,直到启动类加载器,如果依然没找到,则开始向下委托,如下所示
可以从源码角度来分析下,jvm默认会初始化sun.misc.Launcher类加载器,然后执行AppClassLoader的类加载器方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//初始化扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//初始化启动类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
//执行启动类加载器的loadClass方法
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
public Class> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
//直接调用父类ClassLoader中的类加载方法
return super.loadClass(var1, var2);
}
}
public abstract class ClassLoader {
...省略代码
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1.首先检测该类是否早已被加载过
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
/**2.调用父级加载器的类加载方法,用断点跟下就知道这时parent为
ExtClassLoader**/
c = parent.loadClass(name, false);
} else {
/**3.如果父级加载器为空就用启动类加载器去加载**/
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
...省略代码
}
说到这到底为何要有这种双亲委派的机制呢?
1.安全机制:避免核心类被恶意篡改,我们可以做一个测试,自定义一个和jvm中String中相同的包,如
其实在类加载的过程中,会通过类的包名java.lang.String去找这个类是否被加载过,如果被加载,就不会再次被加载,直接使用,而被加载的这个类是jdk下的rt.jar里面的类,在这个类中根本不存在main方法,因此会报出上面的错误。
2.避免类的重复加载:当父级类加载器加载过后,子级加载器就不会再加载了,保证类的唯一性。
以上是自己对jvm的浅见,后续有空会继续更新,始终相信厚积薄发,最终会变成自己期望的样子!