说一下Java类加载器
都知道JVM要把这个类的字节码加载到内存中,但类的原始信息在classpath路径下。类加载器的工作就是把.class文件的内容加载到内存,进行处理并返回字节码。
Java有哪些类加载器
引导类加载器:负责加载位于JRE的lib目录下的核心类库
扩展类加载器:负责加载位于JRE的lib目录下的ext扩展目录中的JAR包
应用程序类加载器:负责加载ClassPath路径下的类包,主要是加载你自己写的哪些类。
自定义加载器:负责加载自定义路径下的类包
说一下类加载器父类委托机制
双亲委派机制,就是加载某个类时会现委托父加载器寻找目标类,找不到就从父类的父类加载器寻找。如果所有父类加载器都没有在自己的加载类路径下找到,则在自己的类加载器路径中查找
源码:
首先,检查指定名称的类是否加载过。加载过了,就直接返回。
没加载过,在判断是否有父加载器,有就由父加载器加载。或者调用bootstrap类加载器加载
如果父加载器bootstrap加载器没找到,就调用当前类加载器的findCLass方法
自定义类加载器时覆盖loadClass还是findClass?
覆盖findClass,因为覆盖loadClass会打破父类委托机制,因为loadClass封装了父类委托机制的代码,loadClass方法内部会调用findClass实现类加载。
自定义类加载器:编写一个类继承ClassLoader,然后覆盖findClass,实现累加器的代码。
如何判断一个对象是否存活
引用技术(java未采用)
一个对象被引用一次,在当前对象头上递增一次引用次数。如果引用次数为0,代表对象可回收。
无法解决循环引用问题,会引发内存泄漏
可达性分析算法(Java采用)
存在一个根节点GC ROOTS,引出指向下一个节点。形成引用链。当节点不在引用链时,并不会马上回收!当被标记可回收并发生GC时,会先判断这个对象是否执行了Finalize方法,没执行,就会先执行这个方法,可以设置当前对象与GC Roots产生关联,执行完毕后,GC会判断对象是否可达,不可达被回收。可达不回收。
finalize方法只会执行一次。
哪些可以作为GC Roots:
虚拟机栈中引用的对象,本地方法栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用对象
JVM内存模型
JVM内存模型:堆,栈,方法区,程序计数器,本地方法栈
堆:对象,采用分代机制。堆由年轻代和老年代组成。年亲代由Eden和survivor组成
方法区:放类的信息,动态代理
栈:放的是局部变量
栈帧:局部变量表,操作数栈
程序计数器:下一条指令的地址
本地方法栈:
分代算法
Java8中的堆分成新生代和老年代【1:2】;java7中还存在一个永久代。
新生代由分为Eden区(伊甸园区),from区(s0区),to区(s1区)
新生代产生GC为MinorGC(Young GC)
老年代产生GC为FullGC(Old GC)
工作机制:
当创建一个对象的时候,这个对象会分配到新生代的Eden区。当Eden区内存满了,会触发YongGC
当进行YongGC后,Eden区存活的对象会被移动到from区,并且该对象的age计数器加1(年龄加1),清空Eden区
当在一次触发YongGC时,会把Eden区存活下来的对象和from区的对象,移动到to区中,这些对象年龄加1,清空Eden区和from区
当在一次触发YongGC时,会把Eden区存活的对象和to区对象,移动到from区,这些对象年龄加1,清空Eden区和to区。
一直这样循环
对象何时进入老年代
对象的年龄达到了某个限定值(默认未15,CMS为6岁),这个对象就会进入老年代
大对象,需要大量连续的内存空间的对象。
如果Survivor区中所有相同年龄对象大小之和超过Survivor空间一半时,年龄大于或等于该年龄的对象直接进入老年代
当老年代满了之后,触发FullGC,FullGC同时回收新生代和老年代,当前只会存在一个FullGC线程进行执行,其他线程挂起。
垃圾收集算法
标记清除算法
根据可达性分析算法得出垃圾并标记
对标记的内容进行回收
缺点:效率低,标记清除出来内存,碎片化较为严重,因为回收的对象在内存不同的地方,所以清理起来不连贯。
标记整理算法
标记垃圾
需要清除的垃圾向右走,不需要清除的向左走
清除边界以外的垃圾
解决了碎片化问题,但多了一步对象移动内存位置的步骤,效率会受其影响。
复制算法
将内存区域一分为二,每次操作其中一个
当进行GC时,将正在使用内存区域的存活对象移动到未使用的内存区域。当移动完这部分内存区域后再一次性清除
一直循环下去
优点:垃圾对象较多时,效率高;清理后无内存碎片
缺点:内存区域一分为二,内存使用率低
分区收集
将堆划分为很多个小格子,每个小格子自己收集。用在G1垃圾收集器中。
JVM常见参数
-Xms:设置堆的初始可用大小,默认物理内存的1/64
-Xmx:设置堆的最大可用大小,默认物理内存的1/4
-Xmn:新生代大小
-XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。
-XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。
垃圾收集器
Serial收集器
串行垃圾收集器,是指使用单线程进行垃圾回收,作用与新生代。新生代为复制算法。
对于交换性较强的应用而言,这种垃圾收集器是不能接受的。
Serial Old收集器
运行于老年代的单线程serial收集器,采用标记整理算法,主要是给Client模式下虚拟机使用
ParallelNew收集器
并行垃圾收集器实在串行垃圾收集器的基础之上做的改进。缩短垃圾回收时间。作用于新生代。JDK8默认使用此垃圾回收器
CMS垃圾收集器
Concurrent Mark Sweep,使用标记清除算法,作用于老年代。以最短回收的停顿时间为目标的收集器,停顿实践越短,用户体验越好。
执行过程:
初始标记:指标及GC Roots能直接引用的对象,需要暂停其他所有线程stw(Stop the world)
并发标记:找gc root间接触及的对象(追踪引用链的过程),可以和用户线程并发执行
重新标记:就是为了修正并发标记期间漏掉的对象标记记录。停顿时间比初始标记长,比并发标记时间短。会产生stw
并发清除:清除标记为回收的对象,可以和用户线程并发执行。
并发重置:重置本次GC过程中已经标记的数据
Parallel Scavenge收集器
作用于新生代,目标是达到一个可控的吞吐量,高效率利用Cpu时间。适合再后台运算而不需要太多交互的任务
Parallel Old收集器
作用于老年代的并行收集器,采用标记整理。再注重吞吐量及cpu资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old
G1垃圾收集器(重点)
同时应用于新生代和老年代,采用标记整理算法、软实时,低延迟,可设定目标。代替CMS,适用于较大的堆。JDK9之后默认使用G1
G1的设计原则是简化JVM性能调优,开发人员的步骤:
第一步,开启G1垃圾收集器
第二步,设置堆的最大内存
第三步,设置最大的停顿时间
JVM参数调优
jvm调优主要是调整年轻代,年老大,元空间的内存空间大小将垃圾回收器类型
设置堆的初始大小和最大大小。防止垃圾收集器再初始大小和最大大小之间收缩堆而产生额外的时间,通常把最大,最小设置为相同的值。
设置年轻代Eden区和两个Survivor区的大小比例。官方是增大Eden区大小,来减少Yong GC次数。但Eden区满了,占用空间又大,导致释放慢,SWT时间长。
年轻代与老年代的默认比列为1:2,可以调整二者比例
线程堆栈的设置:每个线程默认会开启1M的堆栈。
一般一天超过一次FullGC会有问题,出现内存泄漏就调整代码,没有就调整jvm参数
JVM调优总结
初始堆值和最大堆内存越大,吞吐量就越高
让初始堆的大小和最大堆的大小一样,减少GC次数
最好使用并行收集器,因为并行收集器速度比串行吞吐量高,速度快
设置堆内存新生代的比列和老年代的比列最好为1:2或者1:3
减少GC堆老年代的回收
如果使用G1收集器
不断调优暂停时间指标
不要设置新生代和老年代大小,逻辑划分,自动调整
设置堆内存大小为4G-6G