目录
JVM类加载器有几种类型,分别加载什么东西,用到什么设计模式?
JVM篇
通常使用什么工具监控JVM
jconsule, jvisualvm
JVM类加载流程
loading加载:class文件从磁盘加载到内存中
verification验证:校验class文件,包括字节码验证,元数据验证,符号引用验证等等
preparation准备:静态变量赋默认值,只有final会赋初始值
resolution解析:常量池中符号引用,转换成直接访问的地址
initializing初始化:静态变量赋初始值
JVM类加载器有几种类型,分别加载什么东西,用到什么设计模式?
-
BootStrap ClassLoader 启动类加载器,加载<JAVA_HOME>\lib下的类
-
Extenstion ClassLoader 扩展类加载器,加载<JAVA_HOME>\lib\ext下的类
-
Application ClassLoader 应用程序类加载器,加载Classpath下的类
-
自定义类加载器
这里是用到了双亲委派模式,从上往下加载类,在这过程中只要上一级加载到了,下一级就不会加载了,这麽做的目的
-
不让我们轻易覆盖系统提供功能
-
也要让我们扩展我们功能。
JVM组成,以及他们的作用
运行时数据区:
-
堆:存放对象的区域,所有线程共享
-
虚拟机栈:对应一个方法,线程私有的,存放局部变量表,操作数栈,动态链接等等
-
本地方法栈:对应的是本地方法,在hotspot中虚拟机栈和本地方法栈是合为一体的
-
程序计数器:确定指令的执行顺序
-
方法区:存放虚拟机加载的类的信息,常量,静态变量等等,JDK1.8后,改为元空间
执行引擎:
-
即时编译器,用来将热点代码编译成机器码(编译执行)
-
垃圾收集,将没用的对象清理掉
本地方法库:融合不同的编程语言为java所用
在JVM层面,一个线程是如何执行的
线程执行,每个方法都会形成一个栈帧进行压榨保存到虚拟机栈中,方法调用结束就回出栈。调用过程中创建的变量在虚拟机栈,对象实例存放在堆内存中,栈中的变量指向了对中的内存。当方法执行完成就出栈,创建的变量会被销毁,堆中的对象等待GC。
程序内存溢出了,如何定位问题出在哪儿?
增加启动参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\ 可以把内存溢出的日志输出到文件,然后通过JVM监视工具VisualVM来分析日志,定位错误所在。在linux服务器也可以使用命令: jmap -dump 来下载堆快照。
垃圾标记算法
垃圾标记算法有:引用计数和可达性算法
-
引用计数 : 给每一个对象添加一个引用计数器,每当
有一个地方引用它时,计数器值加1
;每当有一个地方不再引用它时,计数器值减1
,这样只要计数器的值不为0,就说明还有地方引用它,它就不是无用的对象. 这种算法的问题是当某些对象之间互相引用时,无法判断出这些对象是否已死 -
GC Roots :找到一个对象作为 CG Root , 当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,就说明此对象是不可用的
垃圾回收算法
-
标记清除算法 :分为标记和清除两个阶段,首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象 ;缺点:标记和清除两个过程效率都不高;标记清除之后会产生大量不连续的内存碎片。
-
复制算法 :把内存分为大小相等的两块,每次存储只用其中一块,当这一块用完了,就把存活的对象全部复制到另一块上,同时把使用过的这块内存空间全部清理掉,往复循环 ,缺点:实际可使用的内存空间缩小为原来的一半,比较适合
-
标记整理算法 :先对可用的对象进行标记,然后所有被标记的对象向一段移动,最后清除可用对象边界以外的内存
-
分代收集算法 :把堆内存分为
新生代和老年代
,新生代又分为Eden区、From Survivor和To Survivor。一般新生代中的对象基本上都是朝生夕灭的,每次只有少量对象存活,因此新生代采用复制算法
,只需要复制那些少量存活的对象就可以完成垃圾收集;老年代中的对象存活率较高,就采用标记-清除和标记-整理算法
来进行回收。
垃圾回收器有哪些
-
新生代:Serial :一款用于
新生代的单线程收集器,采用复制算法进行垃圾收集
。Serial进行垃圾收集时,不仅只用一条线程执行垃圾收集工作,它在收集的同时,所有的用户线程必须暂停(Stop The World -
新生代:ParNew : ParNew就是一个Serial的多线程版本`,其它与Serial并无区别。ParNew在单核CPU环境并不会比Serial收集器达到更好的效果,它默认开启的收集线程数和CPU数量一致,可以通过-XX:ParallelGCThreads来设置垃圾收集的线程数。
-
新生代:Parallel Scavenge(掌握) Parallel Scavenge也是一款用于新生代的
多线程收集器
,与ParNew的不同之处是,ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间,Parallel Scavenge的目标是达到一个可控制的吞吐量
.Parallel Old收集器以多线程,采用标记整理算法进行垃圾收集工作。 -
老年代:Serial Old ,Serial Old收集器是Serial的老年代版本,同样是一个单线程收集器,采用标记-整理算法。
-
老年代CMS收集器是一种以最短回收停顿时间为目标的收集器,以“最短用户线程停顿时间”著称。整个垃圾收集过程分为4个步骤
-
初始标记:标记一下GC Roots能直接关联到的对象,速度较快
-
并发标记:进行GC Roots Tracing,标记出全部的垃圾对象,耗时较长
-
重新标记:修正并发标记阶段引用户程序继续运行而导致变化的对象的标记记录,耗时较短
-
并发清除:
用标记-清除算法清除垃圾对象
,耗时较长
整个过程耗时最长的并发标记和并发清除都是和用户线程一起工作,所以从总体上来说,
CMS收集器垃圾收集可以看做是和用户线程并发执行的。
-
-
老年代:Parallel Old ,Parallel Old收集器是Parallel Scavenge的老年代版本,是一个
多线程收集器,采用标记-整理算法。可以与Parallel Scavenge收集器搭配,可以充分利用多核CPU的计算能力
。 -
堆收集:G1 收集器, G1 收集器是jdk1.7才正式引用的商用收集器,现在已经成为
jdk1.9默认的收集器
。前面几款收集器收集的范围都是新生代或者老年代,G1进行垃圾收集的范围是整个堆内存
,它采用“化整为零”的思路,把整个堆内存划分为多个大小相等的独立区域(Region)
在每个Region中,都有一个Remembered Set来实时记录该区域内的引用类型数据与其他区域数据的引用关系(在前面的几款分代收集中,新生代、老年代中也有一个Remembered Set来实时记录与其他区域的引用关系),在
标记时直接参考这些引用关系就可以知道这些对象是否应该被清除,而不用扫描全堆的数据
Jdk1.7.18新生代使用Parallel Scavenge,老年代使用Parallel Old
Minor GC和Full GC
新生代的回收称为Minor GC,新生代的回收一般回收很快,采用复制算法,造成的暂停时间很短 ,而Full GC一般是老年代的回收,并伴随至少一次的Minor GC,新生代和老年代都回收,而老年代采用
标记-整理算法,
这种GC每次都比较慢,
造成的暂停时间比较长`,通常是Minor GC时间的10倍以上。尽量减少 Full GC
JVM优化的目的是什么?
优化程序的内存使用大小,以及减少CG来减少程序的停顿来提升程序的性能。
堆怎么调,栈怎么调
-Xms : 初始堆,1/64 物理内存
-Xmx : 最大堆,1/4物理内存
-Xmn :新生代大小
-Xss : 栈大小
GC的分类:
新生代 GC(Minor GC / Scavenge GC):发生在新生代的垃圾收集动作。因为 Java 对象大多都具有朝生夕灭的特性,因此 Minor GC 非常频繁(不一定等 Eden 区满了才触发),一般回收速度也比较快。在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用复制算法来完成收集。 老年代 GC(Major GC / Full GC):发生在老年代的垃圾回收动作。Major GC 经常会伴随至少一次 Minor GC。由于老年代中的对象生命周期比较长,因此 Major GC 并不频繁,一般都是等待老年代满了后才进行 Full GC,而且其速度一般会比 Minor GC 慢10倍以上。另外,如果分配了 Direct Memory,在老年代中进行 Full GC 时,会顺便清理掉 Direct Memory 中的废弃对象。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清除”算法或“标记-整理”算法来进行回收
JVM中什么地方不会产生垃圾?
在栈中和程序计数器中,因为在栈中方法执行完毕就会做一个出栈的操作,所以栈中不会有垃圾