JVM学习之旅

一.JVM内存模型

JVM学习之旅_第1张图片

    以上是java虚拟机的整体内存图,其中栈,本地方法栈,程序计数器会跟随线程的创建而创建,是线程独享的,而堆和方法区是线程共享的区域。下面对每一块区域做深入剖析。

   1.栈

    首先先从栈说起,当线程在执行的时候会在内存中分配一块内存区域,用于存放栈帧,栈帧可以理解为对方法调用的一种数据结构,每当执行一个方法的时候就会向当前线程栈中压入栈帧,比如,A调用B,B又调用C,那么栈帧自上而下顺序为C>B>A,C就为当前栈帧。栈帧中存储着局部变量表、操作数栈、动态链接、方法出口。

JVM学习之旅_第2张图片

以上是栈的内存模型,局部变量表顾名思义,存放的是方法内的基本数据类型的变量、对象的引用;操作数栈是对数的加减乘除运算,下面以一段简单的代码来理解操作数栈

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

 以上通过反编译指令,简述了操作数栈和局部变量表间的工作流程的理解。

     方法出口中存着上一个栈帧中的位置,这样程序才知道在当前栈帧执行完毕后,该回到上一个栈帧中的什么位置。

2.本地方法栈

    当线程执行的时候,会在内存中分配一块区域,用于存储c语言函数调用栈帧,java中native修饰的方法都叫本地方法。

3.程序计数器

    程序计数器事实上是记录了程序中指令的执行位置,如下图

 程序计数器记录的是指令前面的地址,在单核处理器的机器上,假如存在两个线程,其中A线程睡眠,B线程执行,假如没有程序计数器的话,当线程切回A线程的时候,就不知道程序执行到什么位置,因此,程序计数器就是记录了程序执行的位置。程序计数器中的值是实时变化的,仅仅存放一个数字,所占内存很小,因此不会存在内存溢出。

二.JVM类加载机制

1.类加载过程

    类加载分为几大步骤:加载->验证->准备->解析->初始化->使用->卸载

    加载:jvm是一种懒加载,只有在类使用到的时候才会从硬盘把class文件通过IO读取到内存,并把类信息存到方法区;

    验证:校验字节码信息的正确性;

    准备:给类的静态变量分配内存,并赋予默认值;

    解析:将符号引用替换为直接引用,其实就是把指针指向堆中真实的内存的地址;

    初始化:对类的静态变量初始化为指定的值,执行静态代码块;

2.类加载器

    启动类加载器(BootstrapLoader):主要负责加载jre下lib目录下的核心类库;

    扩展类加载器(ExtClassLoader): 主要负责加载jre下lib目录下ext扩展目录下的核心类库;

    应用类加载器(AppClassLoader):主要加载自己定义的类

    自定义加载器:加载用户指定路径下的类

3.双亲委派机制

    何为双亲委派机制,一开始听到这个名词,完全很懵。在不考虑自定义加载器的情况下,所有的类加载都是从应用类加载器开始,然后这其中应用类加载器会去判断是否有父级加载器,会一级一级向上委托,直到启动类加载器,如果依然没找到,则开始向下委托,如下所示

JVM学习之旅_第3张图片

 可以从源码角度来分析下,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中相同的包,如

JVM学习之旅_第4张图片

 执行结果:

     其实在类加载的过程中,会通过类的包名java.lang.String去找这个类是否被加载过,如果被加载,就不会再次被加载,直接使用,而被加载的这个类是jdk下的rt.jar里面的类,在这个类中根本不存在main方法,因此会报出上面的错误。

2.避免类的重复加载:当父级类加载器加载过后,子级加载器就不会再加载了,保证类的唯一性。

以上是自己对jvm的浅见,后续有空会继续更新,始终相信厚积薄发,最终会变成自己期望的样子!

  

你可能感兴趣的:(Java,java-ee,java)