JVM内存模型以及类加载过程分析
写在前面JVM内存模型可以说是面试中常客了足见其重要性本文就一起来看下以求下次遇到这样的面试题可以把面试官按在地上摩擦摩擦我的滑板鞋霍霍1JVM内存模型先来看一下JVM内存模型的图其中线程共享的区域有方法区堆。线程独享的区域有程序计数器虚拟机栈本地方法栈。我们按照线程独享还是共享来分别看下。1.1线程独享1.1.1:程序计数器用来存放线程执行字节码位置的区域因为只是存储一个位置所以是一块很小的内存空间。也是唯一的一块不会发生内存溢出的区域。1.1.2:虚拟机栈线程执行方法时分配线程栈的区域每个线程一个线程栈每次方法的调用都会生成对应的栈帧并压入线程栈中如下图每个栈帧中包含了执行方法所必须的内容主要是局部变量表操作数栈异常表等如下图通过参数-Xss来设置虚拟机栈的大小。1.1.3:本地方法栈同虚拟机栈不过是用来执行native方法的底层通过C语言编写。1.2线程共享1.2.1堆堆是JVM中最大的一块内存区域通过-Xmssize,-Xmxsize来设置堆内存的最小值和最大值一般最好二者设置为一致这样可以避免扩容时申请内存对程序的影响。堆分为年轻代和年老代其中年轻代又分为Eden和survivorsurvivor又分为from和to相关主要参数如下-Xmssize:设置堆内存最小值 -Xmxsize:设置堆内存最大值 -Xmnsize:设置年轻代大小 -XX:NewRatioratio设置年老代和年轻代比例优先级低于-Xmn。 例如-XX:NewRatio3表示老年代:年轻代 3:1年轻代占堆的1/4。如果设置了-Xmn则此参数失效。适合通过比例调整不关心精确大小。 -XX:SurvivorRatioratioeden和单个survivor的比例 例如-XX:SurvivorRatio4表示Eden:From 4:1Eden占年轻代的4/6。Survivor过小可能导致对象过早晋升过大则浪费空间。以1.8为例看下结构图注意图中的元空间不属于堆属于本地内存1.2.2方法区这里方法区是一个抽象的概念是JVM规范中的一部分内容永久代和元空间都是其具体的实现永久代是1.8之前对于方法区的实现而元空间是1.8版本对于方法区的实现用来替换永久代。方法区主要用来存储类的元信息如类成员变量类方法信息运行时常量池等。2类加载到执行过程分析2.1类加载为了更加清晰的展示类加载整个过程我们通过一个实际的类在不同的加载过程中对于jvm内存模型的影响来看下如下类packagecom.demo.xx;publicclassJVMCase{// 常量publicfinalstaticStringMAN_SEX_TYPEman;// 静态变量publicstaticStringWOMAN_SEX_TYPEwoman;publicstaticvoidmain(String[]args){StudentstunewStudent();stu.setName(nick);stu.setSexType(MAN_SEX_TYPE);stu.setAge(20);JVMCasejvmcasenewJVMCase();// 调用静态方法print(stu);// 调用非静态方法jvmcase.sayHello(stu);}// 常规静态方法publicstaticvoidprint(Studentstu){System.out.println(name: stu.getName(); sex:stu.getSexType(); age:stu.getAge());}// 非静态方法publicvoidsayHello(Studentstu){System.out.println(stu.getName()say: hello);}}classStudent{Stringname;StringsexType;intage;publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.namename;}publicStringgetSexType(){returnsexType;}publicvoidsetSexType(StringsexType){this.sexTypesexType;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.ageage;}}假定执行了程序new JVMCase(),正式开始加载过程首先执行第一个阶段加载会通过类加载器app classload将字节码加载到方法区中这没什么好说的。接着看连接阶段连接阶段又分为验证准备解析首先是验证阶段验证文件的合法性即格式是否符合JVM的要求等。然后是准备阶段准备阶段会为静态变量申请内存并赋予初始值对于常量public final static String MAN_SEX_TYPE man;会直接赋值man对于静态变量public static String WOMAN_SEX_TYPE woman;会赋予String的默认值null如下图接着是解析解析阶段是将符号引用如字面量com.xx.Stu#sayHi解析为实际引用com.xx.Stu#sayHi对应的可以直接使用的指针地址。到这里连接阶段就结束了进入初始化阶段,该阶段会执行clinit方法该方法是在编译阶段编译器收集类中的静态变量静态代码块动态生成的方法本例中clinit方法如下仅示意:function clinit() { WOMAN_SEX_TYPE woman; }初始化结束后WOMAN_SEX_TYPE就会被赋值为woman了如下图2.2main方法执行执行main首先会为main线程在虚拟机栈中分配一个线程栈然后创建main方法的栈帧压入线程栈开始执行main方法执行代码Student stu new Student();后就会在堆中创建stu对象在栈帧的局部变量表中引用堆中的对象如下同样的执行JVMCase jvmcase new JVMCase();会在堆中创建对应的对象接着执行实例方法jvmcase.sayHello(stu);和静态方法print(stu);,此时如下图图示可能不是百分百的正确知道大概的意思就行有时候不用抠的那么细。我觉得知道核心的流程就可以了如果真的需要再详细的了解细节也不迟写在后面参考文章列表多知道一点clinit方法这是在编译阶段编译器通过收集静态变量静态代码块而构成的一个方法代码的顺序是程序的自然顺序会在初始化阶段来执行另外如果有父类的话会先执行父类的clinit方法如下类packagecom.demo.xx;publicclassFather{static{fatherStatic0;System.out.println(father 静态代码块);}privatestaticintfatherStatic1;publicstaticvoidmain(String[]args){}}packagecom.demo.xx;publicclassSonextendsFather{privatestaticintsonStatic1;static{sonStatic0;System.out.println(son 静态代码块);}publicstaticvoidmain(String[]args){System.out.println(sonStatic最终值是sonStatic);}}运行father 静态代码块 son 静态代码块 sonStatic最终值是0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2418776.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!