JVM基础知识
1. JVM位置
2. JVM体系结构
大部分垃圾产生在方法区和堆,所以JVM调优也是在这里。
3. 类加载器(classLoader):
作用:加载.class文件
几种类加载器:
- Bootstrp loader
Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath
参数指定的路径以及%JAVA_HOME%/jre/classes
中的类。 - ExtClassLoader Bootstrp
loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrp
loader.ExtClassLoader是用Java写的,具体来说就是
sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext
,此路径下的所有classes目录以及java.ext.dirs
系统变量指定的路径中类库。 - AppClassLoader Bootstrp
loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为
ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是
sun.misc.Launcher$AppClassLoader
,另外我们知道ClassLoader中有个getSystemClassLoader
方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
为什么要有三个类加载器?
一方面是分工,各自负责各自的区块,另一方面为了实现委托模型。
4. 双亲委派机制:
类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类
1.类加载器收到类加载的请求
2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
3.启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
4.重复步骤3
为什么要使用这种双亲委托模式呢?
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们可以试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
5. 沙箱安全机制
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java
代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?
CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
6. native关键字
凡是带了native 关键字的,说明java的作用范围达不到了,会去调用底层C语言的库!
调用本地方法本地接口JNI
JNI(Java Native Interface)作用:扩展Java的使用,融合不同的编程语言为Java所用!
Java诞生的时候C、C++横行,想要立足,必须要有调用c、C++的程序
它在内存区域中专门开辟了一块标记区域:Native Method Stack
,登记native 方法
在最终执行的时候,加载本地方法库中的方法通过JNI
7. Native Method Stack
它的具体做法是Native Method Stack 中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。【本地库】
8. PC寄存器
程序计数器: Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
9. 方法区
Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。
10. 三种JVM
- Sun 公司 HotSpot
- BEA公司的JRockit(原来的 Bea JRockit)电脑软件,系列产品是一个全面的Java运行时解决方案组合。
- IBM公司的J9 VM 是一个高性能的企业级 Java 虚拟机。
11. 堆(Heap)
栈是运行时的单位,而堆是存储的单位。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;
堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
1.新生代
HotSpot JVM把新生代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1。
一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次轻 GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次M轻 GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到老年代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法
。
复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold
来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
新生代参数:
1)-XX:NewSize
和-XX:MaxNewSize
用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
2)
-XX:SurvivorRatio
用于设置Eden和其中一个Survivor的比值,这个值也比较重要。
3)
-XX:+PrintTenuringDistribution
这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。
4).
-XX:InitialTenuringThreshol
和-XX:MaxTenuringThreshold
用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。
2.老年代
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。 另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。
3.永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境或类信息这个区域不存在垃圾回收!关闭VM虚拟就会释放这个区域的内存~
12. OOM以及堆内存调优
OOM,全称“
Out Of Memory
”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError。
看下关于的官方说明: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.
意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。
自定义内存大小:
JVM调优常用参数:
-Xms
:初始化内存分配大小
-Xmx
:设置最大分配内存
-XX:NewSize=n
:设置年轻代大小
-XX:NewRatio=n
:设置年轻代和年老代的比值。
如:为3表示年轻代和年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n
:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。
如3表示Eden: 3 Survivor:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n
:设置持久代大小
-XX:+PrintGCDetails
:打印GC回收的过程日志信息
13. 几种垃圾回收器
新生代收集器:
- Serial
- ParNew
- parallel
老年代收集器
- Serial Old
- CMS
- Parallel Old
新生代和老年代收集器
- G1
14. 通过Dump分析OOM
在大型的应用中,我们经常碰到内存溢出(简称OOM),我们通常通过配置core dump,将内存中信息保存到dump文件,供我们分析,参数如下:
-XX:+HeapDumpOnOutOfMemoryError
当遇到OutOfMemoryError错误时,JVM会将dump heap到文件
-XX:HeapDumpPath=./java_pid.hprof
文件的保存路径,java进程的pid会追加到文件名中
-XX:OnOutOfMemoryError="< cmd args >;< cmd args >"
当OOM发生时,需要执行的命令
15.GC常用算法
引用计数法:
所谓的引用计数法就是给每个对象一个引用计数器,每当有一个地方引用它时,计数器就会加1;当引用失效时,计数器的值就会减1;任何时刻计数器的值为0的对象就是不可能再被使用的。
复制算法:
复制算法就是将内存空间按容量分成两块。当这一块内存用完的时候,就将还存活着的对象复制到另外一块上面,然后把已经使用过的这一块一次清理掉。这样使得每次都是对半块内存进行内存回收。内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。
- 优点:
不会发生碎片化。 - 缺点:
堆的使用效率低下。
递归调用函数。
标记-清除算法
分为标记和清除两个阶段。标记就是把所有活动对象都做上标记的阶段;清除就是将没有做上标记的对象进行回收的阶段
- 优点:
不需要额外空间 - 缺点:
会产生内存碎片
两次扫描浪费时间
标记-压缩算法
标记-压缩算法与标记-清理算法类似,只是后续步骤是让所有存活的对象移动到一端,然后直接清除掉端边界以外的内存。
该算法可以有效的利用堆,但是压缩需要花比较多的时间成本。
总结几种GC算法:
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
16.JMM
1.什么是JMM?
JMM:(Java Memory Model的缩写)
2.它干嘛的?︰
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)。
JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)。
参考:
https://www.cnblogs.com/E-star/p/5556188.html
https://www.cnblogs.com/snowwhite/p/9532311.html