JVM:java虚拟机,java可以跨平台的原因
JVM内存结构:
程序计数器:记录当前线程执行到哪一条字节码指令的地址,没有规定任何OutOfMemoryError情况
虚拟机栈:
栈帧:用于存储局部变量表,操作数栈,动态链接,方法出口等,每个方法执行时都会创造一个栈帧
局部变量表:存放编译期可知的各种基本数据类型,引用类型
本地方法栈:为虚拟机执行native方法服务(native方法,为c++编写,给java调用,Theard类里面)
Java堆:存放对象实例,分为新生代(Eden 与Survivor Space 组成 1:2)和老年代
方法区:存储虚拟机加载的类信息(类的版本、字段、方法、接口),常量,静态常量,存放编译后的代码数据
四种引用:
强引用:在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
弱引用:用来描述非必需对象的,使用WeakReference类来实现弱引用,被弱引用关联的对象只能生存到下一次垃圾收集发生之前
软引用:用来描述一些还有用但并非必需的对象,使用SoftReference类来实现软引用,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收
虚引用:是最弱的一种引用关系,使用PhantomReference类来实现虚引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
如何判断对象是否死去
引用计数法:给对象添加一个计数器,当对象被引用时+1,引用失效时-1,是很简单也很方便的的算法,但是由于java内部有很多循环引用的对象,引用计数法无法在这种情况下使用,所以java默认没有使用引用计数法
可达性分析:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相时,则证明此对象是不可用的,已经死去
垃圾收集算法
标记-清除算法:利用可达性分析,将存活的对象打上标记,删除吊没有标记的对象(容易产生比较多的内存碎片,没有连续的大量分配的内存空间,导致GC更加频繁)
标记-复制算法:将对象的内存分为2份,只使用其中的一份,当使用的内存部分快满的时候,利用可达性分析,将其中存活的对象复制到另外一份中,删除没有标记的对象(只使用了50%的内存,浪费内存)
标记-整理算法:标记-清除算法的基础上,会将存活的对象进行整理,但是会增加耗时
分代回收机制:
新生代:有大批量的对象会在垃圾收集时被判断为死亡,当执行Full GC后,新生代中活下来的对象会全部放入老年代中,采用标记-复制算法
老年代:对象的存活率很高,采用标记-清除算法
永久代:FULL GC时进行,使用标记
调优本质:减少FULL GC的产生频率,因为频繁的FULL GC会导致应用程序性能下降跟响应时间延长
调优思路:让Young GC后每次的对象小于Survivor的50%,让对象留在新生代而不进入老年代
调优理论:
1.大对象与长期存活对象留在老年代
2.对象动态年龄判断机制
3.老年代的空间担保机制
新生代中包含Eden区和Survivor区,当Eden区内对象满了之后,会执行Young GC,而当Eden中存活的对象数目小于老年代的容量时,会执行Full GC,讲存活得到对象全部放入到老年代中,如果此时老年代中死去的对象小于新加入的对象,那么老年代的的空间就会见效,当然如果此时新生代的垃圾收集器为Serial和ParNew,并且设置了-XX:PretenureSizeThreshold参数,当对象大于这个参数值时,会被认为是大对象,从而将它放入老年代中
垃圾回收器
1.Serial 收集器
2.ParNew 收集器
3.Parallel Scavenge 收集器
4.Serial Old 收集器
5.Parallel Old 收集器
6.CMS 收集器
7.G1收集器
类加载顺序:加载,校验,准备,解析,初始化
类加载器
启动类加载器:
扩展类加载器:
应用程序类加载器:
自定义加载器:
双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,会抛出异常,让子类加载器去尝试加载
双亲委派机制的作用:保证java基础类在不同的环境还是同一个Class对象,避免出现了自定义类覆盖基础类的情况,导致出现安全问题。还可以避免类的重复加载
如何打破双亲委派机制:使用SPI打破双亲委派机制;使用自定义类加载器打破双亲委派机制,按照自己的逻辑,而不是每次都是委托父类启动器先加载
为什么要打破双亲委派机制:因为在很多情况下,双亲委派机制不能满足当前的需求,需要被打破,如tomcat容器部署多个web服务时,可能需要不同版本的jar,而按照双亲委派机制则无法满足该需求,所以需要打破双亲委派机制
Tomcat的类启动器
Bootstrap ClassLoader:
System ClassLoader:
Common ClassLoader:
WebappX ClassLoader:Tomcat隔离多个web服务的地方
Tomcat类加载过程
1)首先本地缓存 resourceEntries,如果已经被加载过则直接返回缓存中的数据。
2)检查 JVM 是否已经加载过该类,如果是则直接返回。
3) 检查要加载的类是否是 Java SE 的类,如果是则使用 BootStrap 类加载器加载该类,以防止 webapp 的类覆盖了 Java SE 的类。
例如你写了一个 java.lang.String 类,放在当前应用的 /WEB-INF/classes 中,如果没有此步骤的保证,那么之后项目中使用的 String 类都是你自己定义的,而不是 rt.jar 下面的,可能会导致很多隐患。
4)针对委托属性 delegate 显示设置为 true、或者一些特殊的类(javax、org 包下的部分类),使用双亲委派模式加载,只有很少部分使用双亲委派模型来加载。
5)尝试从本地加载类,如果步骤5中加载失败也会走到本步骤,这边打破了双亲委派模型,优先从本地进行加载。
7)走到这,代表步骤6加载失败,如果之前不是使用双亲委派模式,则在这边会委托给父类加载器来尝试加载。
8)走到这边代表所有的尝试都加载失败,抛出 ClassNotFoundException。