JVM 重点知识归纳

2023-04-25,,

JVM(Java Virtual Machine:译为 Java虚拟机)内核: 通常指通过软件模拟的具有完整硬件系统功能的运行在一个完全隔离环境汇总的完整计算机系统。如下:
  ■  Mware/Visul Box:使用软件模拟物理CPU的指令集;
  ■  JVM:使用软件模拟 Java字节码指令集;
Java语言和 JVM是相对独立的,列如:Groovy、Clojure、Scalar语言都是可以在JVM上运行的,说明复核JVM规范。但是都不符合Java语言规范。JVM主要定义了二进制 Class文件和 JVM指令集等。

一、计算机中整数的表达用补码存储


原码:第一位为符号位(0为正数,1为负数);
反码:符号不动,原码取反;
负数补码:反码加1;
正数补码:和原码相同;
 ​

二、为什么要是用补码


【1】0的表示会统一:

【2】方便计算机计算:

三、JVM 需要对 Java library(Java 库)提供哪些支持


【1】反射(Java.lang.reflect);
【2】ClassLoader;
【3】初始化class和interface;
【4】安全相关(java.security);
【5】多线程;
【6】弱引用;

四、JVM 的启动流程


五、JVM基本结构


【1】PC(Program Counter:程序计数器)寄存器:①、每个线程创建的时候都会拥有一个PC寄存器;②、PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量; ③、每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记;④、这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。⑤、如果执行的是一个Native方法,那这个计数器是空的。
【2】方法区:①、保存装载的类信息(类型的常量池,字段、方法信息,方法字节码); ②、通常和永久区(Perm)关联在一起;
【3】Java堆: ①、和程序开发密切相关;②、应用系统对象都保存在Java堆中;③、所有线程共享Java堆;④、对分代GC来说,堆也是分代的;⑤、GC的主要工作区间;
【4】Java栈:①、线程私有;②、栈由一系列帧组成(因此 Java栈也叫帧栈);③、栈保存一份方法的局部变量(包含传入参数)、操作数栈、常量池指针;④、每一次方法调用创建一个栈,并压栈;

六、Java 没有寄存器,所有参数传递使用操作数栈来完成


七、Java栈之栈上分配


【1】小对象(一般几十个bytes),在没有逃逸(就是在其他线程中也要使用)的情况下,可以直接分配在栈上;
【2】直接分配在栈上,可以自动回收,减轻GC压力;
【3】大对象或者逃逸对象无法在栈上分配;

八、堆、栈、方法区交互


九、内存模型


每一个线程都有一个工作内存和独立内存:【1】工作内存存放主存中变量值的拷备。

【2】当数据从主内存复制到工作内存时,必须出现两个动作:
   ■ 由主内存执行读操作;
   ■ 由工作内存执行响应的 load操作;

【3】当数据从工作内存复制到主内存时,必须出现两个动作:
   ■ 由工作内存执行的存储操作;
   ■ 由主内存执行相应的 write操作;

每一个操作都是原子的,即执行期间不会被中断。对于普通变量,一个线程中更新的值,不会马上反应到其他变量中。如果要在其他线程中立即可见需要使用 volatile关键字。但 volatile 不能代替锁,一般认为 volatile比锁性能好(不绝对)且线程不安全。如果没有volatile -server运行,则不生效。

十、volatile 的属性


【1】可见性:一个线程修改了变量,其他线程可以立即知道;保证可见性的方法:①、volatile ②、synchronized(unlock之前,写变量值会存主内存) ③、final(一旦初始化后完成,其他线程可见);
【2】有序性:①、在本线程内,操作都是有序的 ②、在线程外观察,操作都是无序的。(指令重排或主内存同步延迟);       【3】指令重排:例如:a=1,b=2这种,重排后不影响执行结果的语句就可以重排,a=1;b=a;a=2这种有依赖性的语句就不可以重排。编译器是不考虑多线程之间的语义。

十一、指令重排的基本原则(编译器为了更好的性能)


【1】程序顺序原则:一个线程内保证语义的串行性。
【2】volatile规则:volatile变量的写,先发生于读。
【3】锁规则:解锁(unlock)必然发生在随后的加锁(lock)之前。
【4】传递性:A优于B,B优于C,那么A必然优于C。
【5】线程的 start方法先于它的每一个动作。
【6】线程的所有操作先于线程的终结(thread.join())。
【7】线程终端(interrupt())后的程序不应该在被执行。
【8】对象的构造函数执行结束优先于finally()方法。

十二、解释运行与编译运行


【1】解释运行:①、解释执行以解释方式运行字节码;②、解释执行意思是:读一句,执行语句;
【2】编译运行(JIT)①、字节码编译成机器码;②、直接执行机器码;③、运行时编译;④、编译后性能有数量级的提升。

十三、Trace跟踪参数


-X:non-standard(非标准参数),这些参数不是虚拟机规范规定的。因此,不是所有 VM的实现(如:HotSpot,JRockit,J9等)都支持这些配置参数。例如: -Xmx、-Xms、-Xmn、-Xss;
-XX:not-stable(不稳定参数),这些参数是虚拟机规范中规定的。这些参数指定虚拟机实例在运行时的各种行为,从而对虚拟机的运行时性能有很大影响。 例如:-XX:SurvivorRatio、-XX:+UseParNewGc;
-X和-XX两种参数都可能随着JDK版本的变更而发生变化,有些参数可以能会被废弃掉,有些参数的功能会发生改变,但是JDK官方不会通知开发者这些变化,需要使用者注意。-XX参数被称为不稳定参数,是因为这类参数的设置会引起JVM运行时性能上的差异,配置得当可以提高JVM性能,配置不当则会使JVM出现各种问题,甚至造成JVM崩溃。
【1】-verbose:gc:在控制台打印GC的简要信息;
  ​
【2】-XX:+printGC:在控制台打印详细的GC信息 ;
  ​ 
【3】-XX:+printGCTimeStamps:打印GC的时间戳;
  ​ 
【4】-Xloggc:log/gc.log:指定GC log的位置为log/gc.log,以文件输出。帮助开发人员分析。
【5】-XX:+PrintHeapAtGC:每一次GC后,都打印堆的信息;
 ​
【6】-XX:+TraceClassLoading:监控类的加载;
  ​
【7】-XX:+PrintClassHistogram:按下Ctrl+Break后,打印类的信息(分别显示:序号、实例数量、总大小、类型)。

十四、堆的分配参数


【1】-Xmx -Xms:指定最大堆和最小堆。-Xmx20m -Xms5m 运行代码:Java会尽可能维持在最小堆当中;

【2】-Xmn:设置年轻代大小;
【3】-XX:NewRatio:新生代(eden+2*s)和老年代(不包含永久区)的比值。-4 表示 新生代:老年代=1:4,即年轻代占堆的1/5;
【4】-XX:SurvivorRatio:设置两个Survivor区和 eden区的比。 -8表示两个Survivor:eden=2:8,一个Survivor占年轻代的1/10;
【5】-XX:+HeapDumpOnOutOfMemoryError:OOM时导出到堆文件;
【6】-XX:+HeapDumpPath:导出OOM的路径;
【7】-XX:OnOutOfMemoryError:在OOM时执行一个脚本,可以在OOM时,发送邮件,或者重启服务。

根据实际大小调整新生代和幸存代的大小:官方推荐新生代占3/8,幸存代占新生代的1/10。在OOM时,记得dump出栈,确保可以排查现场问题。

十五、永久区的参数分配


【1】-XX:PermSize与-XX:MaxPermSize:设置永久区的初始空间和最大空间,他们表示一个系统可容纳多少个类型
【2】永久区溢出,一样会抛OOM;

十六、栈大小分配


-Xss:①、通常只有几百K ②、决定函数调用的深度 ③、每个线程都有独立的栈空间 ④、局部变量,参数分配在栈上

十七、Class装载验证流程


【1】加载 (load): ①、取得类的二进制流。②、转为方法区数据结构。③、在Java堆中生成对应的java.lang.Class对象。
【2】链接(link):①、验证:保证 Class流的格式是正确的。②、准备:分配内存,并为类设置初始值(方法区)。③、解析:符号引用替换为直接引用。
【3】初始化(init):①、执行类构造器<clinit>。②、子类的<clinit>调用前保证父类的<clinit>被调用。③<clinit>是线程安全的。

十八、类加载器


【1】什么是 ClassLoader类装载器:
   ■  ClassLoader 类是一个抽象类。
   ■  ClassLoader 实例读入Java字节码,将类装载到JVM中。
   ■  ClassLoader 可以定制,满足不同的字节码流获取方式。
   ■  ClassLoader 负责类装载过程中的加载阶段。
【2】类装载器分种类:每一个ClassLoader 都有一个parent作为父类(BootStrap ClassLoader除外)
   ■  BootStrap ClassLoader(启动ClassLoader);
   ■  Extension ClassLoader(扩展ClassLoader);
   ■  Application ClassLoader(应用ClassLoader);
   ■  Url ClassLoader(自定义ClassLoader);
【3】协同工作:

问题:顶层的 ClassLoader无法调用底层的 ClassLoader。
解决:Thread.setContextClassLoader():①、上下文加载器。②、是一个角色。③、是以解决顶层 ClassLoader无法访问底层 ClassLoader的类的问题。④、基本思想是:在顶层的 ClassLoader传入底层 ClassLoder的实例。

热替换:当一个 class被替换,系统无需启动,替换的类立即生效。

十九、性能监控工具


系统性能监控:确定系统运行的整体状态,基本定位问题所在。Java自带的工具:查看 Java程序运行细节,进一步定位问题。

【1】Linux系统性能检测:uptime or top(系统时间、运行时间、连接数、1,5,15分钟内的系统平均负载)

【2】vmstat:可以统计系统的CPU,内存,swap,io等情况。[vmstat 采样频率 采样次数]。system:表示切换频率。

【2】pidstat:细致观察进程(监控CPU、IO、内存)【安装:sudo apt-get install sysstat 】-p指定进程 -u监控CPU 每秒采样 一共3次;

-t显示线程

检查磁盘IO情况 -d

【3】Windows性能检测工具:perfmon(自带)



【4】pslist:命令工具,可用于自动化数据收集,显示 Java程序的运行情况。

Java 自带工具:bin目录下可执行命令
​​

【1】jps:列出 Java进程,类似于ps命令。
   ● -q :只输出进程ID,不输出类名

​​

-m:输出传递给 Java进程(主函数)的参数

   ●-l:输出主函数的完整路径

   ●-v:显示传递给 JVM的参数

【2】jinfo:可以用来查看正在运行的 Java应用程序的扩展参数,甚至支持在运行时,修改部分参数:
   ●-flag <name>:打印指定 JVM的参数

   ●-flag [+|-]<name>:设定指定JVM参数的布尔值

​​

-flag <name>=<value>:设定指定JVM参数的值

【3】jmap:生成 Java应用程序的堆快照和对象的统计信息:jmap -histo 2972 >c:/s.txt

dump堆:jmap -dump:formate=b,file=c:\heap.hprof 2972
     ​
      ​

【4】jstack:打印线程dump:可用来排查死锁:
   ● -l:打印锁信息;
   ●-m:打印 Java和 native的帧信息;
   ●-F:强制dump,当 jstack没有响应时使用;
例如】:jstack 120 << C:\a.txt

【5】图形化工具:JConsole:可以查看 Java应用程序的运行概况,监控堆信息、永久区使用情况、类加载情况。

 

【6】Visual VM【常用】:是一个功能强大的多合一故障诊断和性能监控可视化工具。基本都会给其安装很多插件,不然就失去了它的魅力。

性能监控:找到占用CPU时间最长的方法:

分析堆Dump:

二十、内存溢出(OOM)的原因


【1】在JVM中,有哪些内存区间:堆,方法区,线程栈,直接内存;
【2】堆溢出:占用大量堆空间,直接溢出;

1 public static void main(String args[]){
2 ArrayList<byte[]> list=new ArrayList<byte[]>();
3 for(int i=0;i<1024;i++){
4 list.add(new byte[1024*1024]);
5 }
6 }
1 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
2 at geym.jvm.ch8.oom.SimpleHeapOOM.main(SimpleHeapOOM.java:14)

解决办法增大堆空间,及时释放内存。
【3】永久区溢出:

 1 /**CglibBean 的使用:可以动态组装一个类
2 HashMap<String, Class> propertyMap = new HashMap<>();
3 propertyMap.put("id", Class.forName("java.lang.Integer"));
4 propertyMap.put("name", Class.forName("java.lang.String"));
5 propertyMap.put("address", Class.forName("java.lang.String")); // 生成动态Bean
6 propertyMap.put("date", Class.forName("java.sql.Date"));
7 CglibBean bean = new CglibBean(propertyMap);
8 // 给Bean设置值
9 bean.setValue("id", 123); //Auto-boxing
10 bean.setValue("name", "454");
11 bean.setValue("address", "789");
12 **/
13 //生成大量的类
14 public static void main(String[] args) {
15 for(int i=0;i<100000;i++){
16 CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
17 }
18 }
Caused by: java.lang.OutOfMemoryError: PermGen space
[Full GC[Tenured: 2523K->2523K(10944K), 0.0125610 secs] 2523K->2523K(15936K),
[Perm : 4095K->4095K(4096K)], 0.0125868 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
def new generation total 4992K, used 89K [0x28280000, 0x287e0000, 0x2d7d0000)
eden space 4480K, 2% used [0x28280000, 0x282966d0, 0x286e0000)
from space 512K, 0% used [0x286e0000, 0x286e0000, 0x28760000)
to space 512K, 0% used [0x28760000, 0x28760000, 0x287e0000)
tenured generation total 10944K, used 2523K [0x2d7d0000, 0x2e280000, 0x38280000)
the space 10944K, 23% used [0x2d7d0000, 0x2da46cf0, 0x2da46e00, 0x2e280000)
compacting perm gen total 4096K, used 4095K [0x38280000, 0x38680000, 0x38680000)
the space 4096K, 99% used [0x38280000, 0x3867fff0, 0x38680000, 0x38680000)
ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)
rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)

解决办法增大Perm区,允许 Class回收;
【4】Java栈溢出:这里栈溢出指的是在创建线程的时候,需要为线程分配栈空间,这个栈空间是向操作系统请求的,如果操作系统无法给足够的空间,就会抛出OOM。例如:-Xmx1g -Xss1m 设置下不断创建线程就会导致栈溢出

 1 public static class SleepThread implements Runnable{
2 public void run(){
3 try {
4 Thread.sleep(10000000);
5 } catch (InterruptedException e) {
6 e.printStackTrace();
7 }
8 }
9 }
10
11 public static void main(String args[]){
12 for(int i=0;i<1000;i++){
13 new Thread(new SleepThread(),"Thread"+i).start();
14 System.out.println("Thread"+i+" created");
15 }
16 }
1 Exception in thread "main" java.lang.OutOfMemoryError:
2 unable to create new native thread

解决办法减少堆内存,减小线程栈大小。
【5】直接内存溢出:-ByteBuffer.allocateDirect()无法从操作系统获得足够的空间。参数如下:-Xmx1g -XX:+PrintGCDetails

1 for(int i=0;i<1024;i++){
2 ByteBuffer.allocateDirect(1024*1024);
3 System.out.println(i);
4 System.gc();
5 }

​​​

JVM 重点知识归纳的相关教程结束。

《JVM 重点知识归纳.doc》

下载本文的Word格式文档,以方便收藏与打印。