1.JVM运行时内存区域划分
线程独享区域:程序计数器,本地办法栈,虚拟机栈
线程共享区域:元空间(<=1.7办法区), 堆
**程序计数器**
:线程公有,是一块较小的内存空间,能够看做是以后线程执行的字节码指示器,也是惟一的没有定义OOM的区块**本地办法栈**
: 用于执行Native 办法时应用**虚拟机栈**
:用于存储局部变量,操作数栈,动静链接,办法进口等信息**元空间**
:存储已被虚拟机加载的类元信息,常量,动态变量,**即时编译器编译后的代码等数据仍旧存储在办法区中,办法区位于堆中**
**堆**
:存储对象实例
示例:
/** * @author: jujun chen * @description: 应用了CGLIB来动静生成类,元空间存储类信息,-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m * 如果只设置堆的大小,并不会溢出 * @date: 2019/4/7 */public class JavaMetaSpaceOOM { static class OOMObject{} public static void main(final String[] args) { while (true){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o,objects); } }); enhancer.create(); } }}
2.OOM,及SOE的示例、起因,排查办法
//OOM -Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryErrorpublic class OOMTest { public static void main(String[] args) { List<Object> objList = new ArrayList(); while(true) { objList.add(new Object()); } }}//SOE栈异样 -Xss125kpublic class SOETest() { static int count = 0; public static void main(String[] args) { try { stackMethod(); } catch(Error err) { err.printStackTrace(); System.out.println("执行count=" + count); } } private static void stackMethod() { count ++; stackMethod(); }}
OOM排查
:如果能看到日志,能够从打印的日志中获取到发送异样的代码行,再去代码中查找具体哪块的代码有问题。如果没有记录日志,通过设置的 -XX:+HeapDumpOnOutOfMemoryError 在产生OOM的时候生成.hprof文件,再导入JProfiler可能看到是因为哪个对象造成的OOM,再通过这个对象去代码中寻找SOE排查
:栈的深度个别为1000-2000深度,超过了深度或者超过了栈大小就会导致SOE,通过打印的日志定位错误代码地位,检测是否有有限递归,产生了死循环等状况,批改代码
3.如何判断对象能够回收或存活
判断是否能够回收,或者存活次要是看:
- 堆中是否存在该实例
- 加载该类的classloader是否曾经被回收
- 该类的java.lang.Class对象在任何中央没有被援用,也就是不可能通过反射办法获取该类信息
4.哪些对象能够作为GC ROOT 对象
- 虚拟机栈(栈帧中的本地变量表)中援用的对象
- 本地办法中JNI援用的对象
- 办法区中的动态变量和常量援用的对象
5.常见的GC算法
- 标记-革除算法:先标记出存活的对象,再革除没有被标记的对象,毛病是:标记革除过程效率不高;会产生内存碎片
- 复制算法:将内存划分成等同大小两块,只应用其中一块内存,当这一块的内存快用完后,将已存活的对象复制到另外一块内存,再对已应用的内存空间进行一次清理
- 标记-整顿算法:标记出已存活的对象,将对象挪动到内存一端,再对端以外的内存进行清理回收
- 分代收集算法:年老代应用复制算法,永恒代应用 标记-革除或者标记-整顿算法
6.常见的JVM性能监测剖析工具
- jps
可能查看正在运行的虚拟机过程,并显示虚拟机的执行主类及过程ID - jstat [option vmid [interval[s|ms] [count]] ]
能够显示本地或者近程虚拟机中的类装载、内存、垃圾收集、JIT编译等运行数据 - jinfo
实时查看和调整虚拟机的各项参数 - jmap
生成堆转储快照 jhat
生成页面剖析导出的堆存储快照
- jstack
用于生成虚拟机以后时刻的线程快照 jstatd
启动RMI服务端程序,代理本地的Java过程,供近程计算机连贯调式
- 查看以后JVM应用的垃圾收集器
java -XX:+PrintFlagFinal -version 或者 java -XX:+PrintCommandLineFlags -version
- jconsole
Java监督和治理控制台,可能监控内存,线程,类等
- jvisualvm
多合一监督工具
更多材料请学习官网:https://docs.oracle.com/en/ja...
7.JVM优化
- 响应工夫优先:年老代设的大些,直到靠近零碎的最低响应工夫限度。年老代设大,能够缩小达到年轻代的对象。对于永恒代的设置须要参考:永恒代并发收集的次数、年老代和永恒代回收工夫比例,调整达到一个适合的值
- 吞吐量优先:年老代设的大些,永恒代较小
8.什么时候会触发FullGC
- 永恒代空间有余
- 手动调用触发gc
9.类加载器有几种
- Bootstrap ClassLoader
负责加载JDK自带的rt.jar包中的类文件,它是所有类加载器的父加载器,Bootstrap ClassLoader没有任何父类加载器。 - Extension ClassLoader负责加载Java的扩大类库,也就是从jre/lib/ext目录下或者java.ext.dirs零碎属性指定的目录下加载类。
- System ClassLoader负责从classpath环境变量中加载类文件,classpath环境变量通常由"-classpath" 或 "-cp" 命令行选项来定义,或是由 jar中 Mainfest文件的classpath属性指定,System ClassLoader是Extension ClassLoader的子加载器
- 自定义加载器
10.什么是双亲委派模型?双亲委派模型的毁坏
一个类在加载的时候,首先会将加载申请委派给父加载器,只有当父加载器反馈无奈加载实现这个申请时,子加载器才会尝试本人加载
双亲委派模型的毁坏指的是不依照双亲委派模型来加载类,比方JNDI,它的代码由启动类加载器加载,但JDNI须要调用部署在ClassPath的JNDI接口,但启动类加载器是不晓得这些代码的,所以就有了线程上下文类加载器(Thread Context ClassLoader),能够通过java.lang.Thread类setContextClassLoader设置类加载器,通过这个父加载器就能够申请子类加载器实现类加载的动作。
11.类的生命周期
类的生命周期一个有7个阶段:加载、验证、筹备、解析、初始化、应用、卸载
- 加载:
加载阶段,虚拟机须要实现以下3件事 - 通过类的全限定名来获取此类的二进制字节流
- 将字节流所代表的动态存储构造转化为办法区的运行时数据结构
- 在内存中生成代表这个类的java.lang.Class对象,作为办法区这个类的各种数据拜访入口
验证:分4个验证
- 文件格式验证,验证是否合乎Class文件格式的标准,并且能被以后版本的虚拟机解决
- 元数据验证,对字节码形容的信息进行语义剖析,以保障其形容的信息合乎Java语言标准的要求
- 字节码验证,通过数据流和控制流剖析,确定程序语义是否非法、合乎逻辑
- 合乎援用验证,是对类本身以外的信息进行匹配性校验(常量池中各种合乎援用)
- 筹备:正式为类变量分配内存并设置初始值的阶段,这里设置初始值是数据类型的默认值
- 解析:虚拟机将常量池中的符号援用替换为间接援用的过程
- 初始化:执行类结构器的过程
12.强援用、软援用、弱援用、虚援用
- 强援用:大部分应用都是强援用,当内存不足时,会OOM,程序异样终止,也不会随便回收具备强援用的对象
- 软援用:内存足够时,不会革除对象,在内存不足时就会回收这些对象
- 弱援用:弱援用的对象,在产生GC的时候,就会被回收
- 虚援用:虚援用次要用来跟踪垃圾回收的流动,虚援用必须和援用队列联结应用。
13.编译器会对指令做哪些优化?
编译器优化分编译期和运行期
- 编译期:
1.标注查看,查看变量应用前是否已被申明、变量与赋值之间的数据类型是否可能匹配,对常量进行折叠
2.数据及控制流剖析,查看诸如程序局部变量在应用前是否有赋值、是否所有的受检异样都被正确处理等问题
3.将语法糖还原为根底的语法结构
4.生成字节码 - 运行期:
即时编译器JIT会把运行频繁的代码编译成与本地平台相干的机器码,并进行各种档次的优化
Client Compiler: 会进行局部性的优化,分三阶段:第一阶段,一个平台独立的前端将字节码结构成一种高级中间代码示意HIR,HIR应用动态单调配(SSA)的模式来代表代码值,在字节码上做办法内联,常量流传等根底优化;第二阶段,从HIR中产生低级中间代码,在这之前会做空值查看打消,范畴查看打消等。第三阶段,在LIR上调配寄存器,并在LIR上做窥孔优化,最初产生机器码
Server Compiler: 会执行无用代码打消、循环展开、循环表达式外提、打消公共子表达式、常量流传、基本块重排序、范畴检测打消、空值查看打消,另外还能依据解释器或Client Compiler提供的性能监控信息,进行一些不稳固的激进优化,比方守护内联、分支频率预测等 - 几种经典的优化技术:
- 公共子表达式打消
如果一个表达式E曾经计算过,并且从先前的计算到当初E中所有变量的值都没有发生变化,那么E的这次呈现就成为公共子表达式 - 数组范畴查看打消
编译期就判断数组是否在正当的范畴内,如果在,那就能够在循环中把数组的上下界查看打消。另外还有隐式异样解决,虚构机会注册一个Segment Fault信号的异样处理器,但如果代码常常为空,耗费工夫比判空慢,但虚构机会依据运行期收集到的信息抉择应用判空还是隐式异样解决 - 办法内联
一能够给“公共子表达式打消”等其余优化技术提供根底。虚办法即多态状况下,如果要确定是否能内联,虚拟机须要向“类型继承关系剖析”器查问,如果只有一个版本,那能够进行内联,但还会留一个“逃生门”,这种内联称为“守护内联”,如果继承关系发生变化,虚构机会通过“逃生门”退回到解释状态执行,或从新编译。
如果是多版本办法,虚构机会通过“内联缓存”,在第一次调用的时候将指标办法版本缓存起来,下次调用的时候查看版本是否统一,如果不统一就会勾销内联,查找虚构办法表进行办法分派 - 逃逸剖析
剖析对象动静作用域,对象是否作为调用参数传递到其余办法中(办法逃逸),是否有被其余线程拜访(线程逃逸)。如果没有以上状况,虚构机会做一些高效优化:栈上调配、同步打消(去掉同步措施)、标量替换(将对象成员变量复原到原始类型)
14.Serial、Parallel、CMS、G1收集器特点
Serial
单线程收集器,在进行垃圾收集时,必须暂停所有的工作线程直到完结,该收集器进展工夫长,-XX:+UseSerialGC 应用串行垃圾收集器
Parallel
采纳多线程来扫描并压缩堆,进展工夫短,回收效率高,-XX:+UseParNewGC 应用并发标记扫描垃圾回收器
CMS 基于“标记-革除”算法,一共分初始标记、并发标记、从新标记、并发革除,并发重置5个阶段
在初始标记、从新标记阶段须要STW,并且CMS收集器占用CPU资源较多,无奈解决浮动垃圾
并发重置阶段从新初始化CMS数据结构和数据,为下次垃圾回收做筹备
(-XX:CMSInitiatingOccupancyFraction 调整老年代占用多少触发回收;-XX:+UseCMSCompactAtFullCollection 默认开启,在行将触发FullGC前对内存碎片进行整顿;-XX:CMSFullGCsBeforeCompaction设置执行多少次不压缩的FullGC后,来一次带压缩的的碎片整顿)
- G1 能够跟用户程序并发进行垃圾收集;分代收集,将堆划分成多个大小相等的独立Region区域;空间整合,默认就会进行内存整理;可预测的进展,G1跟踪各个Region的回收取得的空间大小和回收所须要的经验值,保护一个优先列表;
15.JVM加载class文件的原理是什么
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时零碎组件。它负责在运行时查找和装入类文件的类。
Java中的所有类,都须要由类加载器装载到JVM中能力运行。类加载器自身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,咱们简直不须要关怀类的加载,因为这些都是隐式装载的,除非咱们有非凡的用法,像是反射,就须要显式的加载所须要的类。
类装载形式,有两种
(1)隐式装载,程序在运行过程中当碰到通过new 等形式生成对象时,隐式调用类装载器加载对应的类到jvm中,
(2)显式装载,通过class.forname()等办法,显式加载须要的类 ,隐式加载与显式加载的区别:两者实质是一样的。
Java类的加载是动静的,它并不会一次性将所有类全副加载后再运行,而是保障程序运行的根底类(像是基类)齐全加载到jvm中,至于其余类,则在须要的时候才加载。这当然就是为了节俭内存开销。
16. jvm最大内存限度多少
(1)堆内存调配
JVM初始调配的内存由-Xms指定,默认是物理内存的1/64;JVM最大调配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小 于40%时,JVM就会增大堆直到-Xmx的最大限度;空余堆内存大于70%时,JVM会缩小堆直到-Xms的最小限度。因而服务器个别设置-Xms、 -Xmx相等以防止在每次GC后调整堆的大小。
(2)非堆内存调配
JVM应用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
(3)VM最大内存
首先JVM内存限度于理论的最大物理内存,假如物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简略的说就32位处理器虽 然可控内存空间有4GB,然而具体的操作系统会给一个限度,这个限度个别是2GB-3GB(一般来说Windows零碎下为1.5G-2G,Linux系 统下为2G-3G),而64bit以上的处理器就不会有限度了。
(3)上面是以后比拟风行的几个不同公司不同版本JVM最大堆内存:
17.jvm是如何实现线程的
线程是比过程更轻量级的调度执行单位。线程能够把一个过程的资源分配和执行调度离开。一个过程里能够启动多条线程,各个线程可共享该过程的资源(内存地址,文件IO等),又能够独立调度。线程是CPU调度的根本单位。
支流OS都提供线程实现。Java语言提供对线程操作的同一API,每个曾经执行start(),且还未完结的java.lang.Thread类的实例,代表了一个线程。
Thread类的要害办法,都申明为Native。这意味着这个办法无奈或没有应用平台无关的伎俩来实现,也可能是为了执行效率。
实现线程的形式
A.应用内核线程实现内核线程(Kernel-Level Thread, KLT)就是间接由操作系统内核反对的线程。
内核来实现线程切换
内核通过调度器Scheduler调度线程,并将线程的工作映射到各个CPU上
程序应用内核线程的高级接口,轻量级过程(Light Weight Process,LWP)
用户态和内核态切换耗费内核资源
应用用户线程实现
零碎内核不能感知线程存在的实现
用户线程的建设、同步、销毁和调度齐全在用户态中实现
所有线程操作须要用户程序本人解决,复杂度高
用户线程加轻量级过程混合实现
轻量级过程作为用户线程和内核线程之间的桥梁
18.什么是Java内存模型
Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入何时对另一个线程可见。从形象的角度来看,JMM定义了线程和主内存之间的形象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个公有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的正本。
本地内存是JMM的一个抽象概念,并不实在存在。它涵盖了缓存,写缓冲区,寄存器以及其余的硬件和编译器优化。其关系模型图如下图所示: