1.java虚拟机基本结构:
类加载子系统负责从文件系统或者网络系统中加载Class信息,类加载的信息存放于方法区中,除了类的信息外,方法区还存放运行时常量池信息,包括字符串字面量和数字常量
Java堆在虚拟机启动的时候建立,它是java最主要的内存工作区,几乎所有的java实例对象都存放于java堆中,堆空间是线程共享的
Java的NIO库允许java程序使用直接内存,直接内存是java堆外的,直接向系统申请的内存空间。通常访问直接内存的速度会优于java堆
垃圾回收系统是java虚拟机的重要组成部分。垃圾回收器可以对方法区、java堆和直接内存进行回收。其中java堆是垃圾收集器的工作重点
每一个java虚拟机线程都有一个私有java栈,一个线程的java栈在线程被创建的时候创建。Java栈中保存着帧信息,java栈中保存着局部变量,方法参数,同时和java方法的返回、调用密切相关
本地方法栈和java栈非常类似,最大的不同在于java栈用于java方法的调用,本地栈用于本地方法的调用。作为对java虚拟机的重要扩展,java虚拟机允许直接调用本地方法(主要用c编写)
PC寄存器也是每个线程私有的空间,java虚拟机会为每个线程创建PC寄存器。任意时刻,一个java线程总在执行一个方法,这个正在执行的方法称为当前方法,如果当前方法不是本地方法,PC寄存器就会指向当前正在被执行的指令,如果当前方法是本地方法,PC寄存器的值就是undifined
执行引擎是java虚拟机最核心的组件之一,它负责执行虚拟机的字节码
2.java虚拟机参数的设置
A. -Xxm可设置java虚拟机的堆空间内存
示例:展示java堆、方法区和java栈之间的关系
其中SimpleHeap实例分配在堆空间中,main函数中s1和s2局部变量存放在java栈中,并指向对中的两个实例
B. -Xss来制定线程最大栈空间
函数嵌套调用的层次在很大程度上由栈的大小决定,栈越大,函数可以支持的嵌套调用次数就越多
由于局部变量在栈帧中,因此如果函数的参数和局部变量较多,会使得局部变量表膨胀,从而每一次函数调用就会占用更多的栈空间,最终导致函数的嵌套调用次数减少。亦即在相同的栈容量下,局部变量少的函数可以支持更深的函数调用
栈帧中的局部变量槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后声明的新的局部变量就很有可能复用过期局部变量的槽位
C. 操作数栈
操作数栈也是栈帧中的重要内容之一,它主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的储存空间
操作栈数也是一个先进后出的数据结构,它支持持进栈和入栈两种操作
3.堆的参数配置:
A.java进程启动,虚拟机就会分配一块初始堆空间,可使用参数-Xms指定这块空间的大小。一般来说虚拟机尽可能维持在初始堆空间的范围内运行,但如果初始堆空间耗尽,虚拟机将对堆空间进行扩展,其扩展上限为最大堆空间,最大堆空间可以用参数-Xmx指定
B.实际工作中,也可以将初始堆-Xms和最大堆-Xmx设置相等,这样的好处是可以减少程序运行时垃圾回收的次数,从而提高程序的性能
4.新生代的参数配置
A.参数-Xmn用于设置新生代的大小,设置一个较大的新生代会减小老年代的大小,这个参数对系统性能以及GC有很大的影响。新生代的大小一般设置为整个堆空间的三分之一到四分之一
B.参数-XX:SurvivorRatio用来设置新生代中eden空间和from/to空间的比例关系
5.方法区配置
A.在JDK1.6和JDK1.7中,可使用-XX:PermSize和-XX:MaxPermSize来配置永久区大小,前者表示初始永久区大小,后者表示最大永久区
6.配置栈
栈是每个线程私有的内存空间,在java虚拟机中可以使用-Xss参数指定线程的大小
7.直接内存配置
A.直接最大内存可以使用参数-XX:MaxDirectMemorySize来设置,如不设置,默认为最大堆空间
B.直接内存适合申请次数较少、访问较频繁的场合。如果内存空间本身需要频繁申请,则并不适合使用直接内存
8.java虚拟机垃圾回收机制
A.引用计数法
a)引用计数器的实现:对于一个对象A,只要任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1,。只要A的引用计数器的值为0,则对象A就不可能再被引用
b)引用计数器两个严重的问题:
无法处理循环引用的情况,因此在java的垃圾回收器中,没有这样的算法;
引用计数器要求在每次因引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能会有一定的影响
B.标记清除算法
a)标记清除法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,因此未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段清除所有未被标记的对象。
b)标记清除法可能产生的最大问题是空间碎片
c)标记清除法先通过根节点标记所有可达对象,然后清除所有不可达对象,完成垃圾回收
C.复制算法
a)复制算法的核心思想是:将原有的内存空间分为两块,每次只是用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
b)复制算法比较适用于新生代,因为在新生代垃圾对象通常会多于存活对象,复制算法的效果比较好
D.标记压缩法
a)标记压缩算法首先从根节点开始,对所有可达对象进行标记,之后将所有的存活对象压缩到内存的一端
E.分代算法
a)分代算法将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法,以提高垃圾回收的效率
分代思想被现有的虚拟机广泛使用,几乎所有的垃圾回收器都区分新生代和老年代
9.引用和可触及性的强度
A.java中给了四个级别的引用:强引用、软引用、弱引用和虚引用
B.强引用
a)StringBuffer st=new StringBuffer(“hello world”);st对StringBuffer实例的引用即使强引用,如果此时 StringBuffer str=st,则str对StringBuffer实例的应用也属于强引用
b)强引用具备以下的特点:
强引用可以直接访问目标对象;
强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常,也不会回收强引用所指向的对象
强引用可能导致内存泄露
C.软引用-------可被回收的引用:GC未必会回收软引用的对象,但是当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出
D.弱引用-------发现即回收
10.回收器
A.串行回收器:使用单线程进行垃圾回收的回收器。串行回收器根据作用域不同的堆空间,分为新生代串行回收器和老年代串行回收器
a)新生代串行回收器:主要有两个特点:①它仅仅使用单线程进行回收②它是独占式的垃圾回收
b)老年代串行回收器:老年代回收垃圾通常会比新生代使用更多的时间
11.Linux下的性能监控工具
A.显示系统资源整体使用情况--top命令:top命令的输出可以分为两部分:前半部分是系统统计信息,后半部分是进程信息
B.监控内存和CPU---vmstat命令
Vmstat也是一款功能比较齐全的性能监控工具,它可以统计CPU、内存使用情况、swap使用情况等信息,和sar工具类似,vmstat也可以指定采样周期和采样次数,如 vmstat 1 3,即表示每秒采样一次,共采样3次
C.监控IO使用----iostat,命令
12.内存溢出原因
A.堆溢出,解决办法:使用-Xmx参数设置更大的堆空间
B.直接内存溢出:直接内存的申请速度要比堆内存慢,但访问速度要比堆内存快。但直接内存没有被java虚拟机完全托管,若使用不当,也容易导致直接内存溢出,导致宕机。解决办法:合理执行显示GC,可降低直接内存溢出概率;设置合理的-XX:MaxDirectMemorySize,也可避免意外的内存溢出发生
C.过多线程导致OOM:由于每个线程的开启都要占用系统内存,因此当线程数量太多,也有可能导致OOM。由于线程的栈空间也是在堆外分配的,因此和直接内存非常相似,如果想让系统支持更多的线程,应该使用一个较小的堆空间。
具体解决办法:①尝试减少堆空间,通过设置参数-Xmx②减少每个线程所占用的内存空间,使用-Xss可以指定线程的栈空间,如果减少栈空间,那么栈溢出的风险也会相应提升
D.永久区溢出:永久区是存放类元数据的区域,设置永久区大小参数:-XX:MaxPermSize。如果一个系统不断的产生新的类,而没有回收,最终非常有可能导致永久区溢出。解决办法:①增加MaxPermSize的值②减少系统需要的类的数量③使用ClassLoader合理装载各个类,并定期进行回收
E.GC效率低下引起的OOM
13.应对锁的竞争:锁在应用层的优化
A.减少锁的持有时间
public synchronized void syncMethod(){
otherCode1();
mutexMethod();//只有该方法有同步需求
otherCode2();
}
上例可优化为:
public void syncMethod(){
otherCode1();
synchronized(this){
mutexMethod();//只有该方法有同步需求
}
otherCode2();
}
优化后的代码只对mutexMethod方法,做了同步,锁占用的时间相对较短,因此能有更高的并行度。减少锁的持有时间有助于降低锁冲突的可能性,进而提升系统的并发能力
B.减少锁粒度:缩小锁定对象的范围,从而减少锁冲突的可能性,进而提高系统的并发能力
C.锁分离:锁分离是减少锁粒度的一个特例,它依据应用程序功能的特点,将一个独占锁分成多个锁
D.锁粗化:如果对同一个锁进行不停的请求、同步和释放操作时,其本身也会消耗系统资源,反而不利于性能的优化
Public void demoMethod(){
synchronized(lock){
//执行一些代码
}
//做其他不需要同步的工作,但很快能执行完毕
synchronized(lock){
//do sth
}
}
可做如下优化:
Public void demoMethod(){
//整合成一次锁请求
synchronized(lock){
//do sth
//做其他不需要同步的工作,但很快能执行完毕
}
以下是一个循环内请求锁的例子:
for(int i=0;i<1000;i++){
synchronized(lock){
//do sth
}
}
可做如下优化:
synchronized(lock){
for(int i=0;i<1000;i++){
// do sth
}
}