JVM 是 Java语言可以跨平台、保持高发展的根本,没有JVM, Java 语言将失去运行环境。针对 Java 程序的性能优化一定不可能避免针对JVM 的调优,随着JVM 的不断发展,我们的应对措施也在不断地跟随、变化,内存的使用逐渐变得越来越复杂。所有高级语言都需要垃圾回收机制的保护,所以 GC 就是这么重要。
1、Serial GC是第一款 GC,它是一款单线程的GC收集器。ParallelGC与 Serial GC 一样都基于分代概念,差别是它采用了多线程方式加速运行垃圾回收。还有一款垃圾收集器是:Concurrent Mark Sweep(CMS) GC。SerialGC适用于最小化地使用内存和并行开销,ParallelGC适用于最大化应用程序的吞吐量,CMS GC适用于最小化 GC 的中断或停顿时间。后面又出现了Garbage First (G1)垃圾收集器。
注意:数据单位 MB 与 Mb (注意B 字母的大小写)常被误认为是一个意思,其实 MByte 含义是“兆 字节”, Mbit 的含义是“兆比特”。MByte是指宇节数量, Mbit是指比特位数。b但yte中的“Byte”虽然与Mbit中的“bit”翻译一样,都是比特,也都是数据量度单位,但二者是完全不同的。 Byte 是“字节数”, bit是“位数”,在计算机中每八位为一字节,也就是 1Byte=8bit,是 1 : 8 的对应关系 。
-Xms 是设置内存初始化的大小,而-Xmx 是设置最大能够使用内存 的大小(最好不要超过物理内存)。
2、并行计算:服务端程序与一般的用户终端程序相比,服务端程序需要承受很重的用户访问压力。虚拟机除了要执行 main 函数主线程外,还需要做 JIT 编译,需要做垃圾回收。无论是 main函数、 JIT编译还是垃圾回收,在虚拟机内部都实现为单独的一个线程。因为这里的每一个任务都是相对独立的,不应该将没有关联的业务代码拼凑在一起,分离为不同的线程更容易理解和维护。
注意 ,井发和并行是两个非常容易被混淆的概念 。它们都可以表示两个或者多个任务一起 执行,但是偏重点有些不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行 的。而井行是真正意义上的“同时执行。
3、线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作 称为多线程。线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数 据空间,每个线程有自己的执行堆钱和程序计数器为其执行上下文。多线程主要是为了节约 CPU时间。注意,线程在运行中需要使用计算机的内存资源和 CPU。
下图描绘了一个 Java 线程的生命周期,从出生→开发→运行→死亡,中间还存在 一 些临时性状态,如等待。
4、内存泄漏( Memory Leak )
常见的 OutOfMemory 异常是最基本的内存不足引起的异常,很多时候是因为应用程序造成的内存泄漏情况引起了该错误的发生
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未被释放,结果导致 一直占据该内存单元,一直持续到程序结束,即所谓内存泄漏 (就是该内存空间使用完毕之后未回收.)。“内存泄漏”是从操作系统的角度来看的。存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。
大多数情况下,GC 会进行各种年龄段的垃圾回收,实在不行了就放大招,来 一 次独占式的 Full GC 操作,这时候会回收大量的内存,供应用程序继续使用。
5、Soft、 Weak、 Phantom、 Final 和 JNI References
Java 中的引用有点像 C++里面的指针,通过引用可以对堆中的对象进行操作。默认的引用是强引用,当在 Java 语言中使用 New 操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用。eg:StringBuffer sb = new StringBuffer(“Hello World" )。对引用使用“==”操作用于表示两个操作数所指向的堆空间地址是否相同,不表示两个操作数所指向的对象是否相等。
Java 中提供了 4 个级别的引用,即强引用( Strong Reference)、软引用( So位 Reference)、弱引用( Weak Reference)、虚引用( Phantom Reference)这 4 个级别 。强引用类是包内可见的,其他 3 种引用类型均为 Public,可以在应用程序中直接使用,垃圾回收 器会尝试回收只有弱引用的对象。
强引用( Strong Reference }:在一个线程内,无需引用直接可以使用的对象,除非引用不存在了,否则强引用不会被 GC 清理。我们平时声明变量使用的就是强引用,普通 系统 99%以上都是强引用,比如, Strings=”HelloWorld”。软引用( Soft Reference l : NM 抛出 OOM 之前, GC 清理所有的软引用对象 。垃圾回收器在某个时刻决定回收软可达的对象的时候,会清理软引用,并可选地把引用存放 到一个引用队列( Reference Queue)。 类似弱引用,只不过 Java 虚拟机会尽量让软引用的存活时间长一些,迫不得已才清理。弱引用( Weak Reference ): 弱 引用对象与软引用对象的最大不同就在于,当 GC 在进行回收时 ,需要通过算法检查是否回收软引用对象,而对于弱引用对象, GC 总是进 行回收。弱引用对象更容易、更快被 GC 回收。弱引用对象常常用于 Map 结构中,引用数据量较大的对象,一旦该对象的强引用 为null时, GC能够快速地回收该对象空间。复杂关系的弱对象群常常需要好几次 GC 的运行才能完成。 虚引用( Phantom Reference ): 又称为幽灵引用,主要目的是在 一个对象所占的内存 被实际回收之前得到通知, 从而可以进行一些相关的清理工作。首先幽灵引用在创建时必须提供 一个引用队 列作为参数,其次幽灵引用对象 的 get 方法总是返回 null, 因此无法通过幽灵引用来获 取被引用 的对象。6、finalization 机制
Java语言提供了对象终止( finalization) 机制来允许开发人员提供对象被销毁之前的自定义 处理逻辑。如果一个类有特殊的销毁逻 辑,可以覆写 finalize方法。通常在这个方法中进行一些资 源释放和清理的工作,比如关闭文件、套接字和数据库连接等 。由于 finalize方法的存在,虚拟机中的对象一般处于三种可能的状态。
第一种是可达状态,当有引用指向该对象时,该对象处于可达状态。第二种是可复活状态,如果对象的类覆写了 finalize方法,则对 象有可能处于该状态。虽然垃圾回收器是在对象没有引用的情况下才调用其 finalize 方法,但是 在 finalize方法的实现中可能为当前对象添加新的引用。因此在 finalize 方法运行完成之后,垃圾回收器需要重新检查该对象的引用。如果发现新的引用,那么对象会回到可达状态,相当于 该对象被复活,否则对象会变成不可达状态。一个对象的 finalize方法只会被调用一次。第三种是不可达状态,在这个状态下,垃圾回收器可以自由地释放对象所占用的内存空间。7、Serviceability Agent ( SA )
SA 提供了一套可以深入 NM 内部进行探索的机制,对于 JavaWeb 应用、 Java服务端应用的各种问题的诊断具有重要意义。
SA 与目标进程是两个独立的进程。两个进程之间通过进程间通信实现调试,井且 SA 不会影响目标进程的正常运行。 SA 依赖于操作系统提供的调试 API,属于建立 在一系列的 Debug原语上的工具。
8、Interned Strings
常量池就类似一个 Java系统级别提供的缓存。 8 种基本类型的常量池都是系统协调的, String 类型的常量池比较特殊 。 它的主要 使用方法有两种。
直接使用双引号声明出来的 String对象会直接存储在常量池中。如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。 intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。Interned String就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池( String Intern Pool) 。
9、Java 对象头 在 HotSpot虚拟机中,对象在内存中的布局可以分成对象头、实例数据、对齐填充三部分。
对象头:它 主要包括对象自身的运行行元数据, 比如哈希码、 GC 分代年龄、锁状态标志等,同时还包含一个类型指针,指向类元数据,表明该对象所属的类型。实例数据 :它是对象真正存储的有效信息,包括程序代码中定义的各种类型的 字段(包括从父类继承下来的和本身拥有的字段)。对齐填充:它不是必要存在的,仅仅起着占位符的作用。10、JIT ( Just-In-Time )编译器
JIT 编译器2能够将 MSIL 编译成为各种不同的机器代码,以适应对应的系统平台,最终使得程序在目标系统中得到顺利地运行。在 Java 编程语言和环境中,即时编译器( JIT compiler, just-in-time compiler) 是一个把 Java的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序。
在 Java 的发展历史里,一共有两套解释执行器,即古老的字节码解释器、现在普遍 使用的模板解释器和即时编译的技术。即时编译的目的是避免函数被解释执行,而是将整个函数体编译成为机器码,每次函数执行时,只执行编译后的机器码即可,这种方式可以便执行效率大幅度提升。
JVM决定函数是否需要编译执行的依据是判断该函数是否为热点代码。如果函数的被调用频率很高,那么就是热点,热点代码就会被编译执f了。