赞
踩
java虚拟机主要由四部分组成
(1)ClassLoader:按特定格式加载class文件到内存中
(2)runtime data area:jvm内存空间模型
(3)execution engine:命令解析器
(4)native interface:融合不同开发语言的原生库供java使用
(1)编译器把.java文件编译成.class文件
(2)ClassLoader将.class文件加载到内存中,生成class<T>对象
(3)jvm根据class<T>生成对应的实体类对象
ClassLoader负责将系统外部的class二进制数据流加载到jvm中,再由jvm进行连接/初始化。ClassLoader是一个抽象类,通过loadClass()方法加载类,ClassLoader的一般实现类有四个,即
(1)BootStrapClassLoader:由C++编写的基础类加载器,用于加载java.*下的核心类,加载路径是jre\lib\rt.jar或者系统变量Xbootclasspath下的类。源码不可见
(2)ExtClassLoader:由java编写的用于加载java扩展类javax.*下的类,加载路径是jre\ext\*.jar或系统变量Djava.ext.dirs下的类。 可以查看源码
(3)AppClassLoader:由java编写,用于加载工程目录下的类,加载路径是CLASSPATH或系统变量Djava.class.path下的类。 可以查看源码
(4)自定义ClassLoader:用户自己编写的(需继承ClassLoader),定制加载
在加载一个类时,步骤如下:
先判断此类是否已被加载,已加载则直接返回,未加载则从下至上继续判断
(1)判断是否已被自定义ClassLoader加载
(2)若未被自定义ClassLoader加载,则判断是否被AppClassLoader加载
(3)若未被AppClassLoader加载,则判断是否被ExtClassLoader加载
(4)若未被ExtClassLoader加载,则判断是否被BootStrapClassLoader加载
若全部加载器判断完,均未加载此类则由BootStrapClassLoader开始从上至下尝试加载
(1)尝试通过BootStrapClassLoader加载
(2)若未能被BootStrapClassLoader加载,则尝试通过ExtClassLoader加载
(3)若未能被ExtClassLoader加载,则尝试通过AppClassLoader加载
(4)若未能被AppClassLoader加载,则尝试通过ClassLoader加载
(5)若所有加载器都未能成功加载,抛出classNotFound异常
先说下类装载和加载的基础知识,类的加载分为隐式加载(new)和显式加载(loadClass和forName),java类的装载过程分为三个阶段。
(1)加载:通过ClasLoader加载class字节码文件,生成class对象
(2)连接:连接分为三个子步骤1校验,校验class文件的正确性和安全性。2准备,为类变量分配存储空间并设置初始值(注意:这里说的类变量是static类型变量,初始值是默认值,比如变量static int a = 111,在此步骤只给赋值0,因为int默认为0,111是在下一步赋值的)。3解析,将jvm常量池中的符号引用转为直接引用。
(3)初始化:进行类变量的复赋值和执行静态代码块
loadClass方法只是实现了上诉第一步,即加载
forName方法实现了全部三个步骤
由线程私有的:本地方法栈,虚拟机栈,程序计数器
线程公有的:元空间,堆
5部分组成,如图
(1)程序计数器:标识字节码文件执行行号,告诉机器执行哪行代码
(2)虚拟机栈:是java方法执行的内存模型,程序运行时每执行一个方法内存就会分配一定空间作为栈帧(Stack Frame是用于支持虚拟机进行方法调用和方法执行的数据结构),这些栈帧存放在虚拟机栈中,方法执行结束会自动按后进先出的策略释放这些栈帧,因此无需回收。栈帧由局部变量表和操作数栈组成,局部表量表存放方法中的各种局部变量,操作数占是描述方法执行中变量的计算/赋值过程
(3)本地方法栈:与虚拟机栈类似,只不过是针对native方法的栈
(4)元空间:存储class相关信息(class的方法,属性信息等)jdk7及以前,这部分属于永久代,元空间和永久代的本质区别是元空间使用本地内存,永久代使用jvm内存,使用元空间替代永久代有以下如下好处:消灭了outOfMerroryError:permGen space这个异常,之前永久代放在堆中,增加了GC的复杂度,而且类和方法信息大小难以确定,不方便指定永久代大小,永久代设置过大容易造成老年代内存溢出,永久代设置过小容易造成永久代内存溢出。再一点就是orcale有意将hotspot虚拟机和其他JVM集成,但永久代是hotspot特有的,为了方便后续集成去掉永久代
(5)堆:对象实例空间和GC管理的空间
因为调用方法会创建栈针并压如虚拟机栈,递归会不断创建栈针增加栈的深度,而线程的虚拟机栈深度固定,超出就会导致栈溢出
-Xms:堆的初始值
-Xmx:堆的最大值,在堆内存不足时,会自动扩容至-Xmx的大小,在实际中通常把此值设跟-Xms一样大,因为java堆扩容会发生内存抖动,非常影响机器的稳定性
-Xss:线程虚拟机栈大小,通常256K就够了,此参数影响并发线程数的大
-XX:NewRatio:老年代和年轻代内存大小比例,通常为2,即老年代和年轻代大小比例2:1
静态存储:编译时即能确定运行时的存储空间需求(对应方法区,存class对象,类的static变量)
栈式存储:编译时不能确定,但运行时通过程序入口可确定的存储需求(对应栈,存和方法中的基本类型变量和对象类型的引用)
堆式存储:编译和运行时程序入口都不能确定空间的存储需求(对应堆,存new出来的对象类型)
联系:访问对象/数组时,会在栈中创建一个引用变量指向堆中对象/数组,退出方法时引用变量销毁但堆中对象仍然存在,堆中对象若一定时间内没有被引用变量指向则会被回收
区别:1.栈比堆小 2.栈自动释放堆需要GC 3.栈产生的碎片小于堆 4.栈支持静态分配和动态分配,堆只支持动态分配 5.栈的效率比堆高
方法作用:返回字符串在常量池中地址并赋给调用该方法的对象。
jdk6实现:从字符串常量池中查找,若不存在则在常量池中创建并返回
jdk6以后实现:从字符串常量池中查找,若不存在则进入堆中查找,若堆中存在则在常量池中创建该对象的引用并返回,若堆中也不存则在常量池中穿件并返回
引申练习题:请问下面两段代码输出结果是啥?
- String s1=new String("a")+new String("b");
- s1.intern();
- String s2 = "ab";
- System.out.println(s1==s2);//jdk6:false jdk7/jdk8:true
- ------------------------------------------
- String s3=new String("a")+new String("b");
- String s4 = "ab";
- s1.intern();
- System.out.println(s3==s4);//false
- ------------------------------------------
- String s5=new String("a");
- s1.intern();
- String s6 = "a";
- System.out.println(s5==s6);//false
(1)引用计数法:原理是记录对象被引用的次数,被一个对象引用则计数+1,引用被回收则-1,归0后被回收。优点是计算简单效率高,缺点是无法计算互相引用等场景
(2)可达性分析算法:判断程序中对象的引用链,当对象顺着引用链可被查到时,则认为“可达”,否则将被回收
(1)标记-清除算法:标记可达的对象,在回收时回收未标记的对象,优点效率高,缺点容易产生内存碎片
(2)标记-整理算法:标记可达的对象,在回收时将可达对象移动到集中的内存区域再进行回收,有点是相比标记清除算法解决了内存碎片的问题,但效率不如标记清除算法
(3)复制算法:将内存分为相等的两块区域,将可达对象标记后复制到另一块区域,然后全量清除原区域空间,优点是避免内存碎片问题,缺点是需要冗余一份内存空间,可达对象越少,采用复制算法越高效,因为复制对象会消耗cpu
(4)分代收集算法:是上诉算法的组合拳,把JVM分为年轻代和老年代,年轻代采取复制算法,老年代采取标记清除/整理算法
年轻代:执行minor GC,分为Eden区和Survivor区(Eden区是大部分对象创建时所在区域,除非内存很大的对象会被直接创建到老年代,Survivor区又分为from区和to区,这三个区的默认大小比例是8:1:1可在jvm参数-XX:SurvivorRatio设置Eden区和Survivor的比例),采用复制算法,具体执行过程为
第一步:标记Eden区和from区中可达对象
第二步:将上诉两区可达对象复制到to区,并将标记“年龄”加1,年龄超过15则存入老年代(15是默认,可在jvm参数-XX:+PretenuerSizeThreshold中配置)。当to区装不下复制结果时,也会将一部分对象直接放到老年代
第三步:将Eden区和from区清空,将原from区标记为下一次执行GC的to区,原to区反之
老年代:存放生命周期较长的对象,执行Full GC和Major GC,采用标记清除/标记整理算法
软引用:主要用途是做内存缓存,内存不足时回收,缓解内存不足导致OOM的问题
弱引用:比软引用更弱些,用于标记些偶尔使用的对象,会跟随GC被回收
虚引用:最弱的引用,用于跟随对象被垃圾回收器回收,哨兵作用
(1)Servier:启动较慢,但稳定运行后速度快
(2)Client:启动较快,但运行稳定后速度慢
(1)年轻代垃圾收集器
Serial收集器:采用复制算法,单线程收集,通过-XX:+UseSerialGC设置,此收集器简单高效,注重缩短stop-the-world的时间,提升用户体验,是Client模式下默认的年轻代收集器
ParNew收集器:Serial收集器的多线程版本(通过-XX:+UseParNewGC设置),特点与Serial收集器相同,在单核环境下效率不如Serial收集器,与Serial收集器是唯二的可以跟cms收集器共同工作的年轻代收集器,默认线程数是系统核数,可通过jvm参数修改
Parallel Scavenge收集器:通过-XX:+UseParallelGC设置,同样是多线程的采用复制算法的收集器,该收集器更注重提高系统吞吐量,适合后台跑定时任务之类的应用,是Server模式下默认的年轻代收集器
(2)老年代垃圾收集器
Serial Old收集器:采用标记-整理算法,单线程收集,通过-XX:UseSerialOldGC设置,Client模式下默认的老年代收集器,可搭配任何年轻代收集器使用
Patralled Old收集器:采用标记-整理算法,多线程收集,吞吐量优先,搭配年轻代的Parallel Scavenge使用,通过-XX:+UseParalledOldGC设置
CMS收集器:采用标记-清除算法,通过-XX:+UseConcMarkSweepGC设置,是常用的老年代收集器。
G1(Garbage First)收集器:同时用于年轻代和老年代,特点是会将整个java堆内存划分成多个大小相等的Regin,年轻代和老年代不再物理隔离
G1有如下优势:
1.并发和并行:G1可以用多个CPU来缩短stop-the-world
2.分代收集:虽然年轻代和老年代不再物理隔离,但仍然独立处理新对象和存活已久的老对象
3.空间整合:因为基于标记-整理算法,解决了很多其他老年代收集器内存不连续问题
4.可预测的停顿:因为支持配置垃圾收集所占用时间在N毫秒内
(1)对象过大,直接创建到老年代
(2)可达性算法计数年龄达到阈值(默认15)
(3)动态对象年龄判定:当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄
(1)老年代空间不足
(2)永久代(如有)空间不足
(3)gc 担保失败,逻辑如下图
(4)程序中的Sysyem.gc()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。