Java语言之所以能广受欢迎,其中的原因之一是Java是一门可以跨平台的语言。而跨平台的特性就是通过Java虚拟机(JVM)是实现的;
JVM全称Java Virtual Machine, 即Java 虚拟机;
主要负责把 Java 程序生成的字节码文件,解释成具体系统平台上可读的机器指令,让其在各个平台运行;
我们常说的jvm,就是HotSpot虚拟机;
java源文件--->编译器--->字节码文件(.class文件)--->jvm--->机器码
每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是 Java 为什么能够跨平台的原因;
JRE(Java Runtime Environment) Java运行环境
JDK(Java Development Kit) Java 开发工具包
JVM(Java Virtual Machine ) Java 虚拟机
JDK包含JRE,JRE包含JVM,简单理解:JDK>JRE>JVM
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域:
程序计数器:线程私有的,较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存;
由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任意一个确定的事件,一个处理器只能有一个线程来执行,故为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器;
Java虚拟机栈(Java Virtual Machine Stack):线程私有的,描述的是Java方法执行的线程内存模型,每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程;
本地方法栈(Native Method Stacks):与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地方法服务;
Java堆(Java Heap):是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,此内存区域的唯一目的就是存放对象实例,Java 世界里“几乎”所有的对象实例都在这里分配内存;Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆
Java 堆,由年轻代和年老代组成,分别占据 1/3 和 2/3;
年轻代又分为三部分,Eden、From Survivor、To Survivor,占据比例为 8:1:1,可调;
运行时常量池(Runtime Constant Pool):
直接内存(Direct Memory):并不是虚拟机运行时数据区的一部分,本机直接内存的分配不会受到Java堆大小的限制,但是会受到本机总内存大小限制;
java对象的创建:
即虚拟机遇到⼀条 new 指令时:
类加载检查--->分配内存--->初始化零值--->设置对象头--->执行init方法
对象的内存布局:
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding);
对象的访问定位:
建⽴对象就是为了使⽤对象,对象的访问⽅式由具体虚拟机的实现方式⽽定,⽬前主流的访问⽅式:句柄和指针;
对于垃圾回收,主要考察三个点:
哪些内存需要回收?
什么时候回收?
如何回收?
方式一:引用计数法Reference Counting
java没有使用该方式;
对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的;
方式二:可达性分析算法Reachability Analysis
主流的方式
基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),
如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的;
方式三:引用
Java的四种引用类型:
强引用(Strongly Re-ference):最传统的“引用”的定义,即类似“Object obj=new Object()”这种引用关系,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象;
软引用(Soft Reference):有用但非必须的对象,在系统将发生内存溢出异常前,会把这些对象列进回收范围进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常
弱引用(Weak Reference):描述那些非必须对象
虚引用(Phantom Reference):对象设置虚引用的唯一目的是为了对象被收集器回收时收到一个系统通知;
标记-清除算法Mark-Sweep
首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象;
标记-复制算法:
主要目的是为了解决标记-清除算法面对大量可回收对象时执行效率低的问题;
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉;
标记-整理算法:
首先标记出所有需要回收的对象,在标记完成后,让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存;
弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的
强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡;
在新生代中,每次垃圾收集时都有大批对象死去,每次回收后存活的少量对象,将会逐步晋升到老年代中存放;
基于这种分代,老年代和新生代具备不同的特点,可以采用不同的垃圾收集算法:
在新⽣代中,每次收集都会有⼤量对象死去,所以可以选择标记-复制算法;
⽼年代的对象存活⼏率是⽐较⾼,选择标记-清除或标记-整理;
Serial收集器:最基础、历史最悠久的收集器,是一个"单线程工作的收集器",
"单线程"并不只是说它只会使用一个处理器或一条收集线程去完成垃圾收集工作,
更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束;
ParNew收集器:是Serial收集器的多线程并行版本,同时使用多条线程进行垃圾收集;
Parallel Scavenge收集器:新生代,吞吐量优先收集器;
Serial Old收集器:一个单线程收集器,使用标记-整理算法;
Parallel Old收集器:
CMS(Concurrent Mark Sweep)收集器:
目前很大部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求
Garbage First收集器:G1是一款主要面向服务端应用的垃圾收集器,是目前垃圾回收器的前沿成果;
java虚拟机把字节码文件(.class文件)加载到内存,然后对数据进行校验、解析、初始化,最终形成虚拟机直接使用的机器类型,该过程称为虚拟机的类加载机制;
类加载的过程:
从被加载到虚拟机内存中开始,到卸载出内存为止,整个的生命周期如下:
加载 (Loading)--->验证(Verification)--->准备(Preparation)--->解析(Resolution)--->初始化 (Initialization)--->使用(Using)--->卸载(Unloading);
加载:
通过一个类的全限定名来获取定义此类的二进制字节流;
将这个字节流所代表的静态存储结构转化为堆的运行时数据结构;
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
验证:文件格式、元数据、字节码和符号引用验证;
准备:为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段;
解析:Java虚拟机将常量池内的符号引用替换为直接引用的过程;
Java虚拟机有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader);
类加载器虽然只用于实现类的加载动作,但对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间;
通俗的说就是:
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
JVM 中内置了三个重要的 ClassLoader:
启动类加载器(Bootstrap Class Loader):
扩展类加载器(Extension Class Loader):
应用程序类加载器(Application Class Loader):
双亲委派机制:
类加载器收到了类加载的请求,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,
因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
破坏双亲委派机制:
最常见的就是热部署,
为了实现热插拔,热部署,模块化,即添加一个功能或减去一个功能不用重启,只需要把这模块连同类加载器一起换掉就实现了代码的热替换;
操作系统工具:
top:显示系统整体资源使用情况
vmstat:监控内存和CPU
iostat:监控IO使用
netstat:监控网络使用
JDK性能监控工具:
jps:虚拟机进程状况工具,功能类似于 ps -ef | grep java
jstat:虚拟机统计信息监视工具,用于监视虚拟机各种运行状态信息;
jinfo:Java配置信息工具,实时查看和调整 JVM 的各项参数
jmap:Java内存映像工具
jhat:虚拟机堆转储快照分析工具
jstack:Java堆栈跟踪工具
jcmd:多功能命令
可视化故障处理工具:
参考博文:
https://www.cnblogs.com/three-fighter/p/14636656.html
JConsole:Java监视与管理控制台
可以通过JDK/bin目录下的jconsole.exe启动JCon-sole
也可做直接通过jconsole启动
JHSDB:基于服务性代理的调试工具
使用以下命令进入JHSDB的图形化模式,并使其附加进程11180
jhsdb hsdb --pid 11180
VisualVM:多合-故障处理工具
它在%JAVA_HOME%bin 目录下,VisualVM的精华之处在于它的插件。插件安装可以手动安装或者自动安装;
VisualVM的插件可以手工进行安装,在网站上下载nbm包后,点击“工具->插件->已下载”菜单,然后在弹出对话框中指定nbm包路径便可完成安装
Java Mission Control:可持续在线的监控工具
内存溢出(Out Of Memory) :就是申请内存时,JVM没有足够的内存空间,通俗说法就是蹲坑发现坑位满了。
内存泄露 (Memory Leak):就是申请了内存,但是没有释放,导致内存空间浪费,通俗说法就是占着茅坑不拉屎。
Java堆溢出:HeapDumpOnOutOf-MemoryError
常见堆JVM相关参数:
-XX:PrintFlagsInitial: 查看所有参数的默认初始值-XX:PrintFlagsFinal:查看所有的参数的最终值(可能会存在修改,不再是初始值)
-Xms: 初始堆空间内存(默认为物理内存的1/64)
-Xmx: 最大堆空间内存(默认为物理内存的1/4)
-Xmn: 设置新生代大小(初始值及最大值)
-XX:NewRatio: 配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄(默认15)
-XX:+PrintGCDetails:输出详细的GC处理日志
内存泄漏:
简单说就是应该被垃圾回收的对象没有被垃圾回收。
参考博文:
https://www.cnblogs.com/three-fighter/p/14644152.html