范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

五万字长篇java虚拟机看这篇文章就够了(上)

  java虚拟机虚拟机的结构
  类加载子系统
  类加载子系统负责从文件系统或者网络中加载Class 信息,加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中可能还会存放运行时常量池信息,包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)。Java堆:
  在虚拟机启动的时候建立,它是Java程序最主要的内存工作区域。几乎所有的Java对象实例都存放于Java堆中。堆空间是所有线程共享的,这是一块与Java应用密切相关的内存区间。直接内存
  Java的NIO库允许Java程序使用直接内存。直接内存是在Java堆外的、直接向系统申请的内存区间。通常,访问直接内存的速度会优于Java堆。因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。由于直接内存在Java堆外,因此它的大小不会直接受限于Xmx指定的最大堆大小,但是系统内存是有限的,Java 堆和直接内存的总和依然受限于操作系统能给出的最大内存。垃圾回收系统
  是Java虛拟机的重要组成部分,垃圾回收器可以对方法区、Java堆和直接内存进行回收。其中,Java 堆是垃圾收集器的工作重点。java栈
  每一个Java虚拟机线程都有一个私有的 Java栈。一个线程的Java栈在线程创建的时候被创建。Java 栈中保存着帧信息,Java 栈中保存着局部变量、方法参数,同时和Java方法的调用、返回密切相关。
  本地方法栈和Java栈非常类似,最大的不同在于Java栈用于Java方法的调用,而本地方
  法栈则用于本地方法调用。作为对Java虛拟机的重要扩展,Java虚拟机允许Java直接调用本地方法(通常使用C编写)。PC ( Program Counter)寄存器
  PC ( Program Counter)寄存器也是每个线程私有的空间,Java 虚拟机会为每一个 Java线程
  创建PC寄存器。在任意时刻,一个Java 线程总是在执行一个方法,这个正在被执行的方法称当前方法。执行引擎
  执行引擎是Java虚拟机的最核心组件之一, 它负责执行虚拟机的字节码。现代虚拟机为了提高执行效率,会使用即时编译技术将方法编译成机器码后再执行。
  Java虚拟机参数设置
  Java虚拟机可以使用JAVA_ HOME/bin/java程序启动(JAVA_ HOME为JDK的安装目录),
  一般来说,Java 进程的命令行使用方法如下:
  java [-options] class [args...]
  其中,
  -options表示Java虚拟机的启动参数,
  class 为带有main()函数的Java类,
  args 表示传递给主函数main(的参数。
  如果需要设定特定的Java 虚拟机参数,在options处指定即可。目前,Hotspot 虚拟机支持大量的虚拟机参数,可以帮助开发人员进行系统调优和故障排查。
  看下面的示例:
  public class  SimpleArgs {
  public static void  main (String[] args) {
  for  ( int  i =  0 ; i < args. length ; i++) {
  System. out .println( "参数"  + (i +  1 ) +  ":"  + args[i]);
  System. out .println( "-Xmx"  + Runtime. getRuntime ().maxMemory() /  1000  /  1000  +  "M" );
  }
  }
  }
  从结果可以看到,第一个参数-Xmx32m传递给Java虚拟机,生效后,使得系统最大可用堆空间为32MB,参数a则传递给主函数main作为应用程序的参数。java堆
  Java堆是和Java应用程序关系最为密切的内存空间,几乎所有的对象都存放在堆中。并且Java堆是完全自动化管理的,通过垃圾回收机制,垃圾对象会被自动清理,而不需要显式地释
  根据垃圾回收机制的不同,Java堆有可能拥有不同的结构。最为常见的一种构成是将整个Java堆分为新生代和老年代。新生代包含Eden+Survivor区,survivor区里面分为from和to区,内存回收时,如果用的是复制算法,从from复制到to(我们也教s0,s1),当经过一次或者多次GC之后,存活下来的对象会被移动到老年区 其中,新生代有可能分为eden区、s0区、s1区,s0 和s1也被称为from和to区域,它们是两块大小相等、可以互换角色的内存空间。
  在绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还存活,则会进入s0或者s1,之后,每经过一次新生代回收, 对象如果存活,它的年龄就会加1。当对象的年龄达到一定条件后,就会被认为是老年对象,从而进入老年代。
  下面通过一个简单的示例,来展示Java堆、方法区和Java栈之间的关系。
  public class  SimpleHeap {
  private int  id ;
  public  SimpleHeap( int  id) {
  this . id  = id;
  }
  public void  show() {
  System. out .println( "My ID is "  +  id );
  }
  public static void  main(String[] args) {
  SimpleHeap s1 =  new  SimpleHeap( 1 );
  SimpleHeap s2 =  new  SimpleHeap( 2 );
  s1.show();
  s2.show();
  }
  }
  上述代码声明了一个SimpleHeap类,并在main(函数中创建了两个SimpleHeap实例。此时,各对象和局部变量的存放如图所示。SimpleHeap实例本身分配在堆中,描述SimpleHeap类的信息存储在方法区,main中的s1和s2的局部变量存储在栈中并且指向两个实例。
  出入java栈
  Java栈是一块线程私有的内存空间。如果说,Java堆和程序数据密切相关,那么Java栈就是和线程执行密切相关的。线程执行的基本行为是函数调用,每次函数调用的数据都是通过Java栈传递的。
  Java栈只支持出栈和入栈两种操作。
  栈帧
  在Java栈中保存的主要内容为栈帧。每一次函数调用, 都会有一个对应的栈帧
  被压入Java栈,每一个函数调用结束,都会有一个栈帧被弹出Java栈。
  如图所示,
  函数1对应栈帧1,函数2对应栈帧2,依此类推。函数1中调用函数2,函数2中调用函数3,函数3中调用函数4。
  当函数I被调用时,栈帧1入栈;当函数2被调用时,栈帧2入栈;当函数3被调用时,栈帧3入栈;当函数4被调用时,栈帧4入栈。当前正在执行的函数所对应的帧就是当前的帧(位于栈顶),它保存着当前函数的局部变量、中间运算结果等数据。
  当函数返回时,栈帧从Java栈中被弹出。Java 方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。在一个栈帧中,至少要包含局部变量表、操作数栈和帧数据区几个部分。
  在一个栈帧中,至少要包含局部变量表、操作数栈和帧数据区几个部分。
  提示:由于每次函数调用都会生成对应的栈帧,从而占用一定的栈空间,因此,如果
  栈空间不足,那么函数调用自然无法继续进行下去。当请求的栈深度大于最大
  可用栈深度时,系统就会抛出StackOverflowError 栈溢出错误。
  Java虚拟机提供了参数-Xss来指定线程的最大栈空间,这个参数也直接决定了函数调用的最大深度。
  下面的代码是一个递归调用,由于递归没有出口,这段代码可能会出现栈溢出
  错误,在抛出错误后,程序打印了最大的调用深度。
  public class  TestStackDeep {
  private static int  count  =  0 ;
  public static void  recursion( long  a,  long  b,  long  c) {
  long  e= 1 , f= 2 ,g= 3 ,h= 4 ,i= 5 ,k= 6 ,q= 7 ,x= 8 ,y= 9 ,z= 10 ;
  count ++;
  recursion (a,b,c) ;
  }
  public static void  recursion() {
  count ++;
  recursion ();
  }
  public static void  main(String args[]) {
  try  {
  recursion ( 1 , 2 , 3 );
  }  catch  (Throwable e) {
  System. out .println( "deep of calling = "  +  count );
  e.printStackTrace();
  }
  }
  }
  -Xss128k 的参数来执行代码
  -Xss256k 的参数来执行代码
  可以看到,在进行大约2700次调用后,发生了栈溢出错误,通过增大-Xss的值,可以获得更深的调用层次,尝试使用参数-Xss256K执行上述代码,可能产生如下输出,很明显,调用层次有明显的增加。局部变量表局部变量的剖析
  局部变量表用于保存函数的参数以及局部变量。局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁。
  由于局部变量表在栈帧之中,因此,如果函数的参数和局部变量较多,会使得局部变量表膨胀,从而每一次函数调用就会占用更多的栈空间,最终导致函数的嵌套调用次数减少。
  [示例]下面的代码演示了这种情况,第1个recursion()函数含有3个参数和10个局
  部变量,因此,其局部变量表含有13个变量。而第2个recursion()函数不含有任何参数和局部变量。当这两个函数被嵌套调用时,第2个recursion()函数可以拥有更深的调用层次。
  public class  TestStackDeep {
  private static int  count  =  0 ;
  public static void  recursion( long  a,  long  b,  long  c) {
  long  e= 1 , f= 2 ,g= 3 ,h= 4 ,i= 5 ,k= 6 ,q= 7 ,x= 8 ,y= 9 ,z= 10 ;
  count ++;
  recursion (a,b,c) ;
  }
  public static void  recursion() {
  count ++;
  recursion ();
  }
  public static void  main(String args[]) {
  try  {
  recursion ( 1 , 2 , 3 );
  }  catch  (Throwable e) {
  System. out .println( "deep of calling = "  +  count );
  e.printStackTrace();
  }
  }
  }
  -Xss128k调用无参数的方法
  -Xss128k调用有参数的方法
  可以看到,在相同的栈容量下,局部变量少的函数可以支持更深的函数调用。
  使用jclasslib工具可以更进一步查看函数的局部变量信息。图2.6显示了第一个recursion()函数的最大局部变量表的大小为26个字。因为该函数包含总共13 个参数和局部变量,且都为long型,long 和double在局部变量表中需要占用2个字,其他如int、short、 byte、 对象引用等占用1个字。
  第一个方法
  第二个方法
  可以看到,在Class文件的局部变量表中,显示了每个局部变量的作用域范围、所在槽位的索引(index 列)、变量名(name 列)和数据类型(J 表示long型)。
  栈帧中的局部变量表中的槽位是可以重用的,如果一个 局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。
  [示例]下面的代码显示了局部变量表槽位的复用。
  在localvar1()函数中,局部变量a和b都作用到了函数末尾,故b无法复用a所在的位置。而在localvar2()函数中,局部变量a在第16行时不再有效,故局部变量b可以复用a的槽位(1个字)。
  我们看到localvar1有三个槽位
  我们看到localvar2中槽位1得到复用,b复用了a的槽位
  局部变量的回收
  局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都是不会被回收的。因此,理解局部变量表对理解垃圾回收也有一定帮助。
  示例:
  public class  LocalVarGCTest {
  public void  localvarGc1() {
  byte [] a =  new byte [ 6  *  1024  *  1024 ];
  System. gc ();
  }
  public void  localvarGc2() {
  byte [] a =  new byte [ 6  *  1024  *  1024 ];
  a =  null ;
  System. gc ();
  }
  public void  localvarGc3() {
  {
  byte [] a =  new byte [ 6  *  1024  *  1024 ];
  }
  System. gc ();
  }
  public void  localvarGc4() {
  {
  byte [] a =  new byte [ 6  *  1024  *  1024 ];
  }
  int  c =  10 ;
  System. gc ();
  }
  public void  localvarGc5() {
  localvarGc1();
  System. gc ();
  }
  public static void  main(String[] args) {
  LocalVarGCTest ins =  new  LocalVarGCTest();
  ins.localvarGc4();
  }
  }
  上述代码中,每一个localvarGc函数都分配了一块6MB的堆空间,并使用局部变量引用
  这块空间。
  在localvarGc1中,在申请空间后,立即进行垃圾回收,很明显,由于byte 数组被变量a引用,因此无法回收这块空间。
  在localvarGc2中,在垃圾回收前,先将变量a置为null,使byte数组失去强引用,故垃
  圾回收可以顺利回收byte数组。
  对于localvarGc3, 在进行垃圾回收前,先使局部变量a失效,虽然变量a已经离开了作用域,但是变量a依然存在于局部变量表中,并且也指向这块byte数组,故byte数组依然无法被回收。
  对于localvarGc4,在垃圾回收之前,不仅使变量a失效,更是申明了变量c,使变量c复用了变量a的字,由于变量a此时被销毁,故垃圾回收器可以顺利回收byte数组。
  对于localvarGc5,它首先调用了localvarGc1很明显,在localvarGc1中 并没有释放
  byte数组,但在localvarGc1返回后,它的栈帧被销毁,自然也包含了栈帧中的所有局部变量,故byte数组失去引用,在localvarGc5的垃圾回收中被回收。
  可以使用参数-XX:+PrintGC执行上述几个函数,在输出的日志中,可以看到垃圾回收前后堆的大小,进而推断byte数组是否被回收。下 面的输出是函数localvarGc4的运行结果:
  从日志中可以看到,堆空间从回收前的10081KB变为回收后的816KB,释放了约很多空间。进而可以推断,byte 数组已被回收释放。操作数栈
  操作数栈也是栈帧中重要的内容之一,它主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
  操作数栈也是一个先进后出的数据结构,只支持入栈和出栈两种操作。许多Java字节码指令都需要通过操作数栈进行参数传递。比如iadd指令,它就会在操作数栈中弹出两个整数并进行加法计算,计算结果会被入栈,如图所示,显示了iadd前后操作数栈的变化。
  看一个简单的案例
  public class  Test1 {
  public static int  add(){
  int  i =  10 ;
  int  j =  20 ;
  int  z = i +j;
  return  z;
  }
  }
  javap -c -l target/classes/cn/tx/test/Test1public class cn.tx.test.Test1 {
  public cn.tx.test.Test1();
  Code:
  0: aload_0
  1: invokespecial #1 // Method java/lang/Object."":()V
  4: return
  LineNumberTable:
  line 3: 0
  LocalVariableTable:
  Start Length Slot Name Signature
  0 5 0 this Lcn/tx/test/Test1;
  public static int add();
  Code:
  0: bipush 10
  2: istore_0
  3: bipush 20
  5: istore_1
  6: iload_0
  7: iload_1
  8: iadd
  9: istore_2
  10: iload_2
  11: ireturn
  LineNumberTable:
  line 6: 0
  line 7: 3
  line 8: 6
  line 9: 10
  LocalVariableTable:
  Start Length Slot Name Signature
  3 9 0 i I
  6 6 1 j I
  10 2 2 z I
  }
  同时我们也可以通过jcclasslib视图查看
  bytecode中的指令可以在下面的指令表中查找到
  public class  TestUser {
  private int  count ;
  public void  test( int  a) {
  count  =  count  + a;
  }
  public  User initUser( int  age, String name) {
  User user =  new  User();
  user.setAge(age);
  user.setName(name);
  return  user;
  }
  public void  changeUser(User user, String newName) {
  user.setName(newName);
  }
  }
  class  User {
  private  String  name ;
  private int  age ;
  public  String getName() {
  return  name ;
  }
  public void  setName(String name) {
  this . name  = name;
  }
  public int  getAge() {
  return  age ;
  }
  public void  setAge( int  age) {
  this . age  = age;
  }
  }
  解析test方法0: aload_0 //取this对应的对应引用值,压入操作数栈
  1: aload_0//取this对应的对应引用值,压入栈,此时栈中有两个值,都是this对象引用
  2: getfield #18 // 引用出栈,通过引用获得对应count的值,并压入栈
  5: iload_1 //从局部变量表中取得a的值,压入栈中
  6: iadd //弹出栈中的count值和a的值,进行加操作,并将结果压入栈
  7: putfield #18 // 经过上一步操作后,栈中有两个值,栈顶为上一步操作结果,栈顶下面是this引用,这一步putfield指令,用于将栈顶的值赋值给引用对象的count字段
  10: return //return void
  解析initUser方法0: new #23 // class com/justest/test/User 创建User对象,并将引用压入栈
  3: dup //复制栈顶值,再次压入栈,栈中有两个User对象的地址引用
  4: invokespecial #25 // Method com/justest/test/User."":()V 调用user对象初始化
  7: astore_3 //从栈中pop出User对象的引用值,并赋值给局部变量表中user变量
  8: aload_3 //从局部变量表中获得user的值,也就是User对象的地址引用,压入栈中
  9: iload_1 //从局部变量表中获得a的值,并压入栈中,注意aload和iload的区别,一个取值是对象引用,一个是取int类型数据
  10: invokevirtual #26 // Method com/justest/test/User.setAge:(I)V 操作数栈pop出两个值,一个是User对象引用,一个是a的值,调用setAge方法,并将a的值传给这个方法,setAge操作的就是堆中对象的字段了
  13: aload_3 //同7,压入栈 14: aload_2 //从局部变量表取出name,压入栈
  15: invokevirtual #29 //MethodUser.setName:(Ljava/lang/String;)V 操作数栈pop出两个值,一个是User对象引用,一个是name的值,调用setName方法,并将a的值传给这个方法,setName操作的就是堆中对象的字段了
  18: aload_3 //从局部变量取出User引用,压入栈
  19: areturn //areturn指令用于返回一个对象的引用,也就是上一步中User的引用,这个返回值将会被压入调用当前方法的那个方法的栈中
  int i=0;
  i = i++; 解析
  0 iconst_0 //把常量0入栈
  1 istore_0 //栈顶元素出栈存储在第0个局部变量上
  2 iload_0 //从第0个局部变量加载到栈顶
  3 iinc 0 by 1 //局部变量值加1并存储
  6 istore_0 //把栈顶元素存储到第0个局部变量(此处覆盖加1的值)
  int i=0;
  i = ++i; 解析
  0 iconst_0 //把常量0入栈顶
  1 istore_0 //栈顶元素出栈存储在第0个局部变量上
  2 iinc 0 by 1 //局部变量值加1并且存储
  5 iload_0 //从第0个局部变量加载到栈中
  6 istore_0 //把栈顶元素存储在第0个局部变量
  常量入栈指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x01
  aconst_null
  null值入栈。
  0x02
  iconst_m1
  -1(int)值入栈。
  0x03
  iconst_0
  0(int)值入栈。
  0x04
  iconst_1
  1(int)值入栈。
  0x05
  iconst_2
  2(int)值入栈。
  0x06
  iconst_3
  3(int)值入栈。
  0x07
  iconst_4
  4(int)值入栈。
  0x08
  iconst_5
  5(int)值入栈。
  0x09
  lconst_0
  0(long)值入栈。
  0x0a
  lconst_1
  1(long)值入栈。
  0x0b
  fconst_0
  0(float)值入栈。
  0x0c
  fconst_1
  1(float)值入栈。
  0x0d
  fconst_2
  2(float)值入栈。
  0x0e
  dconst_0
  0(double)值入栈。
  0x0f
  dconst_1
  1(double)值入栈。
  0x10
  bipush
  valuebyte
  valuebyte值带符号扩展成int值入栈。
  0x11
  sipushvaluebyte1
  valuebyte2
  (valuebyte1 << 8) | valuebyte2 值带符号扩展成int值入栈。
  0x12
  ldc
  indexbyte1
  常量池中的常量值(int, float, string reference, object reference)入栈。
  0x13
  ldc_windexbyte1
  indexbyte2
  常量池中常量(int, float, string reference, object reference)入栈。
  0x14
  ldc2_windexbyte1
  indexbyte2
  常量池中常量(long, double)入栈。
  局部变量值转载到栈中指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x19
  (wide)aload
  indexbyte
  从局部变量indexbyte中装载引用类型值入栈。
  0x2a
  aload_0
  从局部变量0中装载引用类型值入栈。
  0x2b
  aload_1
  从局部变量1中装载引用类型值入栈。
  0x2c
  aload_2
  从局部变量2中装载引用类型值入栈。
  0x2d
  aload_3
  从局部变量3中装载引用类型值入栈。
  0x15
  (wide)iload
  indexbyte
  从局部变量indexbyte中装载int类型值入栈。
  0x1a
  iload_0
  从局部变量0中装载int类型值入栈。
  0x1b
  iload_1
  从局部变量1中装载int类型值入栈。
  0x1c
  iload_2
  从局部变量2中装载int类型值入栈。
  0x1d
  iload_3
  从局部变量3中装载int类型值入栈。
  0x16
  (wide)lload
  indexbyte
  从局部变量indexbyte中装载long类型值入栈。
  0x1e
  lload_0
  从局部变量0中装载int类型值入栈。
  0x1f
  lload_1
  从局部变量1中装载int类型值入栈。
  0x20
  lload_2
  从局部变量2中装载int类型值入栈。
  0x21
  lload_3
  从局部变量3中装载int类型值入栈。
  0x17
  (wide)fload
  indexbyte
  从局部变量indexbyte中装载float类型值入栈。
  0x22
  fload_0
  从局部变量0中装载float类型值入栈。
  0x23
  fload_1
  从局部变量1中装载float类型值入栈。
  0x24
  fload_2
  从局部变量2中装载float类型值入栈。
  0x25
  fload_3
  从局部变量3中装载float类型值入栈。
  0x18
  (wide)dload
  indexbyte
  从局部变量indexbyte中装载double类型值入栈。
  0x26
  dload_0
  从局部变量0中装载double类型值入栈。
  0x27
  dload_1
  从局部变量1中装载double类型值入栈。
  0x28
  dload_2
  从局部变量2中装载double类型值入栈。
  0x29
  dload_3
  从局部变量3中装载double类型值入栈。
  0x32
  aaload
  从引用类型数组中装载指定项的值。
  0x2e
  iaload
  从int类型数组中装载指定项的值。
  0x2f
  laload
  从long类型数组中装载指定项的值。
  0x30
  faload
  从float类型数组中装载指定项的值。
  0x31
  daload
  从double类型数组中装载指定项的值。
  0x33
  baload
  从boolean类型数组或byte类型数组中装载指定项的值(先转换为int类型值,后压栈)。
  0x34
  caload
  从char类型数组中装载指定项的值(先转换为int类型值,后压栈)。
  0x35
  saload
  从short类型数组中装载指定项的值(先转换为int类型值,后压栈)。
  将栈顶值保存到局部变量中指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x3a
  (wide)astore
  indexbyte
  将栈顶引用类型值保存到局部变量indexbyte中。
  0x4b
  astroe_0
  将栈顶引用类型值保存到局部变量0中。
  0x4c
  astore_1
  将栈顶引用类型值保存到局部变量1中。
  0x4d
  astore_2
  将栈顶引用类型值保存到局部变量2中。
  0x4e
  astore_3
  将栈顶引用类型值保存到局部变量3中。
  0x36
  (wide)istore
  indexbyte
  将栈顶int类型值保存到局部变量indexbyte中。
  0x3b
  istore_0
  将栈顶int类型值保存到局部变量0中。
  0x3c
  istore_1
  将栈顶int类型值保存到局部变量1中。
  0x3d
  istore_2
  将栈顶int类型值保存到局部变量2中。
  0x3e
  istore_3
  将栈顶int类型值保存到局部变量3中。
  0x37
  (wide)lstore
  indexbyte
  将栈顶long类型值保存到局部变量indexbyte中。
  0x3f
  lstore_0
  将栈顶long类型值保存到局部变量0中。
  0x40
  lstore_1
  将栈顶long类型值保存到局部变量1中。
  0x41
  lstore_2
  将栈顶long类型值保存到局部变量2中。
  0x42
  lstroe_3
  将栈顶long类型值保存到局部变量3中。
  0x38
  (wide)fstore
  indexbyte
  将栈顶float类型值保存到局部变量indexbyte中。
  0x43
  fstore_0
  将栈顶float类型值保存到局部变量0中。
  0x44
  fstore_1
  将栈顶float类型值保存到局部变量1中。
  0x45
  fstore_2
  将栈顶float类型值保存到局部变量2中。
  0x46
  fstore_3
  将栈顶float类型值保存到局部变量3中。
  0x39
  (wide)dstore
  indexbyte
  将栈顶double类型值保存到局部变量indexbyte中。
  0x47
  dstore_0
  将栈顶double类型值保存到局部变量0中。
  0x48
  dstore_1
  将栈顶double类型值保存到局部变量1中。
  0x49
  dstore_2
  将栈顶double类型值保存到局部变量2中。
  0x4a
  dstore_3
  将栈顶double类型值保存到局部变量3中。
  0x53
  aastore
  将栈顶引用类型值保存到指定引用类型数组的指定项。
  0x4f
  iastore
  将栈顶int类型值保存到指定int类型数组的指定项。
  0x50
  lastore
  将栈顶long类型值保存到指定long类型数组的指定项。
  0x51
  fastore
  将栈顶float类型值保存到指定float类型数组的指定项。
  0x52
  dastore
  将栈顶double类型值保存到指定double类型数组的指定项。
  0x54
  bastroe
  将栈顶boolean类型值或byte类型值保存到指定boolean类型数组或byte类型数组的指定项。
  0x55
  castore
  将栈顶char类型值保存到指定char类型数组的指定项。
  0x56
  sastore
  将栈顶short类型值保存到指定short类型数组的指定项。
  wide指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0xc4
  wide
  使用附加字节扩展局部变量索引(iinc指令特殊)。
  通用(无类型)栈操作指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x00
  nop
  空操作。
  0x57
  pop
  从栈顶弹出一个字长的数据。
  0x58
  pop2
  从栈顶弹出两个字长的数据。
  0x59
  dup
  复制栈顶一个字长的数据,将复制后的数据压栈。
  0x5a
  dup_x1
  复制栈顶一个字长的数据,弹出栈顶两个字长数据,先将复制后的数据压栈,再将弹出的两个字长数据压栈。
  0x5b
  dup_x2
  复制栈顶一个字长的数据,弹出栈顶三个字长的数据,将复制后的数据压栈,再将弹出的三个字长的数据压栈。
  0x5c
  dup2
  复制栈顶两个字长的数据,将复制后的两个字长的数据压栈。
  0x5d
  dup2_x1
  复制栈顶两个字长的数据,弹出栈顶三个字长的数据,将复制后的两个字长的数据压栈,再将弹出的三个字长的数据压栈。
  0x5e
  dup2_x2
  复制栈顶两个字长的数据,弹出栈顶四个字长的数据,将复制后的两个字长的数据压栈,再将弹出的四个字长的数据压栈。
  0x5f
  swap
  交换栈顶两个字长的数据的位置。Java指令中没有提供以两个字长为单位的交换指令。
  类型转换指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x86
  i2f
  将栈顶int类型值转换为float类型值。
  0x85
  i2l
  将栈顶int类型值转换为long类型值。
  0x87
  i2d
  将栈顶int类型值转换为double类型值。
  0x8b
  f2i
  将栈顶float类型值转换为int类型值。
  0x8c
  f2l
  将栈顶float类型值转换为long类型值。
  0x8d
  f2d
  将栈顶float类型值转换为double类型值。
  0x88
  l2i
  将栈顶long类型值转换为int类型值。
  0x89
  l2f
  将栈顶long类型值转换为float类型值。
  0x8a
  l2d
  将栈顶long类型值转换double类型值。
  0x8e
  d2i
  将栈顶double类型值转换为int类型值。
  0x90
  d2f
  将栈顶double类型值转换为float类型值。
  0x8f
  d2l
  将栈顶double类型值转换为long类型值。
  0x91
  i2b
  将栈顶int类型值截断成byte类型,后带符号扩展成int类型值入栈。
  0x92
  i2c
  将栈顶int类型值截断成char类型值,后带符号扩展成int类型值入栈。
  0x93
  i2s
  将栈顶int类型值截断成short类型值,后带符号扩展成int类型值入栈。
  整数运算
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x60
  iadd
  将栈顶两int类型数出栈相加,结果入栈。
  0x64
  isub
  将栈顶两int类型数相减,结果入栈。
  0x68
  imul
  将栈顶两int类型数相乘,结果入栈。
  0x6c
  ip
  将栈顶两int类型数相除,结果入栈。
  0x70
  irem
  将栈顶两int类型数取模,结果入栈。
  0x74
  ineg
  将栈顶int类型值取负,结果入栈。
  0x61
  ladd
  将栈顶两long类型数相加,结果入栈。
  0x65
  lsub
  将栈顶两long类型数相减,结果入栈。
  0x69
  lmul
  将栈顶两long类型数相乘,结果入栈。
  0x6d
  lp
  将栈顶两long类型数相除,结果入栈。
  0x71
  lrem
  将栈顶两long类型数取模,结果入栈。
  0x75
  lneg
  将栈顶long类型值取负,结果入栈。
  0x84
  (wide)iincindexbyte
  constbyte
  在局部变量上将整数值constbyte加到indexbyte指定的int类型的局部变量中。
  浮点运算
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x62
  fadd
  将栈顶两float类型数相加,结果入栈。
  0x66
  fsub
  将栈顶两float类型数相减,结果入栈。
  0x6a
  fmul
  将栈顶两float类型数相乘,结果入栈。
  0x6e
  fp
  将栈顶两float类型数相除,结果入栈。
  0x72
  frem
  将栈顶两float类型数取模,结果入栈。
  0x76
  fneg
  将栈顶float类型值取反,结果入栈。
  0x63
  dadd
  将栈顶两double类型数相加,结果入栈。
  0x67
  dsub
  将栈顶两double类型数相减,结果入栈。
  0x6b
  dmul
  将栈顶两double类型数相乘,结果入栈。
  0x6f
  dp
  将栈顶两double类型数相除,结果入栈。
  0x73
  drem
  将栈顶两double类型数取模,结果入栈。
  0x77
  dneg
  将栈顶double类型值取负,结果入栈。
  逻辑运算——移位运算
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x78
  ishl
  左移int类型值。
  0x79
  lshl
  左移long类型值。
  0x7a
  ishr
  算术右移int类型值。
  0x7b
  lshr
  算术右移long类型值。
  0x7c
  iushr
  逻辑右移int类型值。
  0x7d
  lushr
  逻辑右移long类型值。
  逻辑运算——按位布尔运算
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x73
  iand
  对int类型按位与运算。
  0x7f
  land
  对long类型的按位与运算。
  0x80
  ior
  对int类型的按位或运算。
  0x81
  lor
  对long类型的按位或运算。
  0x82
  ixor
  对int类型的按位异或运算。
  0x83
  lxor
  对long类型的按位异或运算。
  控制流指令——条件跳转指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x99
  ifeqbranchbyte1
  branchbyte2
  若栈顶int类型值为0则跳转。
  0x9a
  ifnebranchbyte1
  branchbyte2
  若栈顶int类型值不为0则跳转。
  0x9b
  ifltbranchbyte1
  branchbyte2
  若栈顶int类型值小于0则跳转。
  0x9e
  iflebranchbyte1
  branchbyte2
  若栈顶int类型值小于等于0则跳转。
  0x9d
  ifgtbranchbyte1
  branchbyte2
  若栈顶int类型值大于0则跳转。
  0x9c
  ifgebranchbyte1
  branchbyte2
  若栈顶int类型值大于等于0则跳转。
  0x9f
  if_icmpeqbranchbyte1
  branchbyte2
  若栈顶两int类型值相等则跳转。
  0xa0
  if_icmpnebranchbyte1
  branchbyte2
  若栈顶两int类型值不相等则跳转。
  0xa1
  if_icmpltbranchbyte1
  branchbyte2
  若栈顶两int类型值前小于后则跳转。
  0xa4
  if_icmplebranchbyte1
  branchbyte2
  若栈顶两int类型值前小于等于后则跳转。
  0xa3
  if_icmpgtbranchbyte1
  branchbyte2
  若栈顶两int类型值前大于后则跳转。
  0xa2
  if_icmpgebranchbyte1
  branchbyte2
  若栈顶两int类型值前大于等于后则跳转。
  0xc6
  ifnullbranchbyte1
  branchbyte2
  若栈顶引用值为null则跳转。
  0xc7
  ifnonnullbranchbyte1
  branchbyte2
  若栈顶引用值不为null则跳转。
  0xa5
  if_acmpeqbranchbyte1
  branchbyte2
  若栈顶两引用类型值相等则跳转。
  0xa6
  if_acmpnebranchbyte1
  branchbyte2
  若栈顶两引用类型值不相等则跳转。
  控制流指令——比较指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0x94
  lcmp
  比较栈顶两long类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈。
  0x95
  fcmpl
  比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
  0x96
  fcmpg
  比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
  0x97
  dcmpl
  比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
  0x98
  dcmpg
  比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。
  控制流指令——无条件跳转指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0xa7
  gotobranchbyte1
  branchbyte2
  无条件跳转到指定位置。
  0xc8
  goto_wbranchbyte1
  branchbyte2
  branchbyte3
  branchbyte4
  无条件跳转到指定位置(宽索引)。
  控制流指令——表跳转指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0xaa
  tableswitchdefaultbyte1
  defaultbyte2
  defaultbyte3
  defaultbyte4
  lowbyte1
  lowbyte2
  lowbyte3
  lowbyte4
  highbyte1
  highbyte2
  highbyte3
  highbyte4
  jump offsets...
  通过索引访问跳转表,并跳转。
  0xab
  lookupswitchdefaultbyte1
  defaultbyte2
  defaultbyte3
  defaultbyte4
  npairs1
  npairs2
  npairs3
  npairs4
  match offsets
  通过键值访问跳转表,并跳转。
  控制流指令——异常和finally
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0xbf
  athrow
  抛出异常。
  0xa8
  jsrbranchbyte1
  branchbyte2
  跳转到子例程序。
  0xc9
  jsr_wbranchbyte1
  branchbyte2
  branchbyte3
  branchbyte4
  跳转到子例程序(宽索引)。
  0xa9
  (wide)ret
  indexbyte
  返回子例程序。
  对象操作指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0xbb
  newindexbyte1
  indexbyte2
  创建新的对象实例。
  0xc0
  checkcastindexbyte1
  indexbyte
  类型强转。
  0xc1
  instanceofindexbyte1
  indexbyte2
  判断类型。
  0xb4
  getfieldindexbyte1
  indexbyte2获取对象字段的值。
  引用出栈,通过引用获得对应属性的值,并压入栈
  0xb5
  putfieldindexbyte1
  indexbyte2
  给对象字段赋值。
  0xb2
  getstaticindexbyte1
  indexbyte2
  获取静态字段的值。
  0xb3
  putstaticindexbyte1
  indexbyte2
  给静态字段赋值。
  数组操作指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0xbc
  newarray
  atype
  创建type类型的数组。
  0xbd
  anewarrayindexbyte1
  indexbyte2
  创建引用类型的数组。
  0xbe
  arraylength
  获取一维数组的长度。
  0xc5
  multianewarrayindexbyte1
  indexbyte2
  dimension
  创建dimension维度的数组。
  方法调用指令
  指令码
  操作码(助记符)
  操作数
  描述(栈指操作数栈)
  0xb7
  invokespecialindexbyte1
  indexbyte2
  编译时方法绑定调用方法。
  0xb6
  invokevirtualindexbyte1
  indexbyte2
  运行时方法绑定调用方法。
  0xb8
  invokestaticindexbyte1
  indexbyte2
  调用静态方法。
  0xb9
  invokeinterfaceindexbyte1
  indexbyte2
  count
  0
  调用接口方法。   方法返回指令   指令码   操作码(助记符)   操作数   描述(栈指操作数栈)   0xac   ireturn   返回int类型值。   0xad   lreturn   返回long类型值。   0xae   freturn   返回float类型值。   0xaf   dreturn   返回double类型值。   0xb0   areturn   返回引用类型值。   0xb1   return   void函数返回。   线程同步指令   指令码   操作码(助记符)   操作数   描述(栈指操作数栈)   0xc2   monitorenter   进入并获得对象监视器。   0xc3   monitorexit   释放并退出对象监视器。   帧数据区   Java栈帧需要一些数据来支持常量池解析、正常方法返回和异常处理等。大部分Java字节码指令需要进行常量池访问,在帧数据区中保存着访问常量池的指针,方便程序访问常量池。栈上分配   栈上分配是Java虚拟机提供的一项优化技术,它的基本思想是,对于那些线程私有的对象(这里指不可能被其他线程访问的对象),可以将它们打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统的性能。   栈_上分配的一个技术基础是进行逃逸分析。逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。如下代码显示了一"个逃逸的对象:   private static User u ;   public static void alloc() {   u = new User ();   u . id = 5 ;   u . name = "geym" ;   }   对象User u是类的成员变量,该字段有可能被任何线程访问,因此属于逃逸对象。而以下代码片段显示了一个非逃逸的对象:   public static void alloc1() {   User u= new User ();   u. id = 5 ;   u. name = "geym" ;   }   在上述代码中,对象User以局部变量的形式存在,并且该对象并没有被alloc()函数返回,或者出现了任何形式的公开,因此,它并未发生逃逸,所以对于这种情况,虚拟机就有可能将User分配在栈上,而不在堆上。   下面这个简单的示例显示了对非逃逸对象的栈上分配。   public class OnStackTest {   public static class User {   public int id = 0 ;   public String name = "" ;   }   public static void alloc() {   User u= new User() ;   u. id = 5 ;   u. name = "geym" ;   }   public static void main(String[] args) throws InterruptedException {   long b = System. currentTimeMillis ();   for ( int i = 0 ; i < 100000000 ; i++) {   alloc ();   long e = System. currentTimeMillis ();   System. out .println(e - b);   }   }   }   上述代码在主函数中进行了1亿次alloc(调用进行对象创建,由于User对象实例需要占据约16字节的空间,因此累计分配空间达到将近1.5GB.如果堆空间小于这个值,就必然会发生GC。使用如下参数运行上述代码:   -server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations   这里使用参数-server执行程序,因为在Server模式下,才可以启用逃逸分析。   参数-XX:+DoEscapeAnalysis启用逃逸分析,   -Xmx 10m指定了堆空间最大为10MB,显然,如果对象在堆上分配,必然会引起大量的GC。如果GC真的发生了。   参数-XX:+PrintGC 将打印GC日志。   参数-XX:+EliminateAllocations开启了标量替换(默认打开),允许将对象打散分配在栈上。   比如对象拥有id和name两个字段,那么这两个字段将会被视为两个独立的局部变量进行分配。   参数-XX:-UseTLAB关闭了TLAB。   可以看到,没有任何形式的GC输出,程序就执行完毕了。说明在执行过程中,User 对象的分配过程被优化。如果关闭逃逸分析或者标量替换中任何一个,再次执行程序,就会看到大量的GC日志,说明栈上分配依赖逃逸分析和标量替换的实现。   总结:   对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效避免垃圾回收带来的负面影响,但由于和堆空间相比,栈空间较小,因此对于大对象无法也不适合在栈上分配。类都去哪了?识别方法区   和Java堆一样,方法区是一块所有线程共享的内存区域。它用于保存系统的类信息,比如类的字段、方法、常量池等。方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误。   public class PermTest {   public static void main(String[] args) {   int i = 0 ;   try {   for (i = 0 ; i < 1000000 ; i++) {   CglibBean bean = new CglibBean( "cn.tx.Perm" +i , new HashMap());   System. out .println(bean);   }   } catch (Exception e){   e.printStackTrace();   }   }   }   class CglibBean {   /**   * 实体Object   */   public Object object = null ;   /**   * 属性map   */   public BeanMap beanMap = null ;   public CglibBean() {   super ();   }   @SuppressWarnings ( "unchecked" )   public CglibBean(Map propertyMap) {   this . object = generateBean(propertyMap);   this . beanMap = BeanMap. create ( this . object );   }   public CglibBean(String msg, Map propertyMap) throws ClassNotFoundException {   propertyMap.put(msg, msg.getClass());   this . object = generateBean(propertyMap);   this . beanMap = BeanMap. create ( this . object );   }   /**   * 给bean属性赋值   * @param property 属性名   * @param value 值   */   public void setValue(String property, Object value) {   beanMap .put(property, value);   }   /**   * 通过属性名得到属性值   * @param property 属性名   * @return 值   */   public Object getValue(String property) {   return beanMap .get(property);   }   /**   * 得到该实体bean对象   * @return   */   public Object getObject() {   return this . object ;   }   private Object generateBean(Map propertyMap) {   BeanGenerator generator = new BeanGenerator();   Set keySet = propertyMap.keySet();   for (Iterator i = keySet.iterator(); i.hasNext();) {   String key = (String) i.next();   generator.addProperty(key, (Class) propertyMap.get(key));   }   return generator.create();   }   }   class CglibTest {   @SuppressWarnings ( "unchecked" )   public static void main(String[] args) throws ClassNotFoundException {   // 设置类成员属性   HashMap propertyMap = new HashMap();   propertyMap.put( "id" , Class. forName ( "java.lang.Integer" ));   propertyMap.put( "name" , Class. forName ( "java.lang.String" ));   propertyMap.put( "address" , Class. forName ( "java.lang.String" ));   // 生成动态 Bean   CglibBean bean = new CglibBean(propertyMap);   // 给 Bean 设置值   bean.setValue( "id" , new Integer( 123 ));   bean.setValue( "name" , "454" );   bean.setValue( "address" , "789" );   // 从 Bean 中获取值,当然了获得值的类型是 Object   System. out .println( " >> id = " + bean.getValue( "id" ));   System. out .println( " >> name = " + bean.getValue( "name" ));   System. out .println( " >> address = " + bean.getValue( "address" ));   // 获得bean的实体   Object object = bean.getObject();   // 通过反射查看所有方法名   Class clazz = object.getClass();   Method[] methods = clazz.getDeclaredMethods();   for ( int i = 0 ; i < methods. length ; i++) {   System. out .println(methods[i].getName());   }   }   }   在JDK 1.6、JDK 1.7中,方法区可以理解为永久区(Perm)。永久区可以使用参数   -XX:PermSize和-XX:MaxPermSize指定,默认情况下,-XX:MaxPermSize 为64MB。一个大的永久区可以保存更多的类信息。如果系统使用了一些动态代理,那么有可能会在运行时生成大量的类,如果这样,就需要设置一个合理的永久区大小,确保不发生永久区内存溢出。   -XX:+PrintGCDetails -XX:PermSize=5M -XX:MaxPermSize= 5m   这里指定了初始永久区5MB,最大永久区5MB,即当5MB空间耗尽时,系统将抛出内存溢出。   在JDK 1.8中,永久区已经被彻底移除。取而代之的是元数据区,元数据区大小可以使   参数-XX:MaxMetaspaceSize指定(一个大的元数据区可以使系统支持更多的类),这是一块外的直接内存。与永久区不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系内存   运行参数: -XX:MetaspaceSize=5m -XX:MaxMetaspaceSize=40m   通过Visual VM也可以查看到移除情况。   常用的java虚拟机参数   要诊断虚拟机,我们就要学习如何对Java虚拟机进行最基本的配置和跟踪。本章将主要介绍一些常用的Java虛拟机参数,它们可以对系统进行跟踪和配置,对系统故障诊断、性能优化有着重要的作用。跟踪调试参数   Java的一大特色就是 支持自动的垃圾回收(GC) ,但是有时候,如果垃圾回收频繁出现,   或者占用了太长的CPU时间,就不得不引起重视。此时,就需要一些跟踪 参数来进一步甄别垃圾回收器的效率和效果。   最简单的一个GC参数是-XX:+PrintGC,使用这个参数启动Java虚拟机后,只要遇到GC,   就会打印日志,如下所示:[GC 4793K->377K (15872K),0. 0006926 secs]   [GC 4857K->377K(15936K), 0. 0003595 secs]   [GC 4857K->377K(15936K), 0.0001755 secs]   [GC 4857K->377K (15936K),0. 0001957 secs]   该日志显示,共进行了4次GC,每次GC占用一行,在GC前,   堆空间使用量(已占用)约为4MB,   GC后,堆空间使用量(已占用)为377KB,   当前可用的堆空间总和(空闲)约为16MB ( 15936KB)。最后,显示的是本次GC所花费的时间。   如果需要更加详细的信息,则可以使用-XX:+PrintGCDetails参数。它的输出可能如下:[GC (System.gc()) [PSYoungGen: 4957K->856K(76288K)] 4957K->864K(251392K), 0.0028907 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   [Full GC (System.gc()) [PSYoungGen: 856K->0K(76288K)] [ParOldGen: 8K->629K(175104K)] 864K->629K(251392K), [Metaspace: 3391K->3391K(1056768K)], 0.0057303 secs] [Times: user=0.16 sys=0.00, real=0.01 secs]   [GC (System.gc()) [PSYoungGen: 2334K->0K(76288K)] 2964K->629K(251392K), 0.0010263 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   [Full GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] [ParOldGen: 629K->629K(175104K)] 629K->629K(251392K), [Metaspace: 3391K->3391K(1056768K)], 0.0024450 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   [GC (System.gc()) [PSYoungGen: 3645K->96K(76288K)] 4275K->725K(251392K), 0.0009841 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   [Full GC (System.gc()) [PSYoungGen: 96K->0K(76288K)] [ParOldGen: 629K->624K(175104K)] 725K->624K(251392K), [Metaspace: 3391K->3391K(1056768K)], 0.0060740 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]   [GC (System.gc()) [PSYoungGen: 3645K->96K(76288K)] 4270K->720K(251392K), 0.0007587 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   [Full GC (System.gc()) [PSYoungGen: 96K->0K(76288K)] [ParOldGen: 624K->624K(175104K)] 720K->624K(251392K), [Metaspace: 3391K->3391K(1056768K)], 0.0028285 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   [GC (System.gc()) [PSYoungGen: 4956K->64K(76288K)] 5580K->688K(251392K), 0.0006692 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   [Full GC (System.gc()) [PSYoungGen: 64K->0K(76288K)] [ParOldGen: 624K->624K(175104K)] 688K->624K(251392K), [Metaspace: 3405K->3405K(1056768K)], 0.0062451 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]   Heap   PSYoungGen total 76288K, used 1966K [0x000000076b380000, 0x0000000770880000, 0x00000007c0000000)   eden space 65536K, 3% used [0x000000076b380000,0x000000076b56b9e0,0x000000076f380000)   from space 10752K, 0% used [0x000000076f380000,0x000000076f380000,0x000000076fe00000)   to space 10752K, 0% used [0x000000076fe00000,0x000000076fe00000,0x0000000770880000)   ParOldGen total 175104K, used 624K [0x00000006c1a00000, 0x00000006cc500000, 0x000000076b380000)   object space 175104K, 0% used [0x00000006c1a00000,0x00000006c1a9c2d8,0x00000006cc500000)   Metaspace used 3419K, capacity 4496K, committed 4864K, reserved 1056768K   class space used 371K, capacity 388K, committed 512K, reserved 1048576K   [GC (System.gc()) [PSYoungGen: 4957K->856K(76288K)] 4957K->864K(251392K), 0.0028907 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   2. PSYoungGen 是指GC发生的区域,还有一个ParOldGen   3. 4957K->856K(76288K) ,这三个数字分别对应GC之前占用年轻代的大小,GC之后年轻代占用,以及整个年轻代的大小。   4. 4957K->864K(251392K) ,这三个数字分别对应GC之前占用堆内存的大小,GC之后堆内存占用,以及整个堆内存的大小。   5. 0028907 是该时间点GC占用耗费时间。   -Xms --jvm堆的最小值   -Xmx --jvm堆的最大值   -XX:MaxNewSize --新生代最大值   -XX:MaxPermSize=1028m --永久代最大值   -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)   -XX:+PrintGCDateStamps 输出GC的时间戳   -XX:+PrintGCDetails --打印出GC的详细信息   -verbose:gc --开启gc日志   -Xloggc:d:/gc.log -- gc日志的存放位置   -Xmn -- 新生代内存区域的大小   -XX:SurvivorRatio=8 --新生代内存区域中Eden和Survivor的比例系统参数查看   参数-XX:+PrintVMOptions可以在程序运行时,打印虚拟机接受到的命令行显式参数。其输   -XX:+PrintVMOptions   参数-XX:+PrintCommandLineFlags可以打印传递给虚拟机的显式和隐式参数,隐式参数未必是通过命令行直接给出的,它可能是由虚拟机启动时自行设置的,使用-XX:+PrintCommandL ineFlags   -XX:InitialHeapSize=266658240 -XX:MaxHeapSize=4266531840 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesInpidualAllocation -XX:+UseParallelGC堆的参数配置最大堆和初始堆的设置   1.8后永久代变成matespace在jvm之外。主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域,不受MaxPermSize控制   当Java进程启动时,虚拟机就会分配一块初始堆空间,可以使用参数-Xms指定这块空间的大小。一般来说,虚拟机会尽可能维持在初始堆空间的范围内运行。但是如果初始堆空间耗尽,虚拟机将会对堆空间进行扩展,其扩展上限为最大堆空间,最大堆空间可以使用参数-Xmx指定。   -Xms:初始堆大小   -Xmx:最大堆大小   案例   public class HeapAlloc {   public static void main(String[] args) {   System. out .print( "maxMemory=" );   System. out .println(Runtime. getRuntime ().maxMemory() + " bytes" );   System. out .print( "free mem=" );   System. out .println(Runtime. getRuntime ().freeMemory() + " bytes" );   System. out .print( "total mem=" );   System. out .println(Runtime. getRuntime ().totalMemory() + " bytes" );   byte [] b = new byte [ 1 * 1024 * 1024 ];   System. out .println( "分配了1M空间给数组" );   System. out .print( "maxMemory=" );   System. out .println(Runtime. getRuntime ().maxMemory() + " bytes" );   System. out .print( "free mem=" );   System. out .println(Runtime. getRuntime ().freeMemory() + "bytes" );   System. out .print( "total mem=" );   System. out .println(Runtime. getRuntime ().totalMemory() + " bytes" );   b = new byte [ 4 * 1024 * 1024 ];   System. out .println( "分配了4M空间给数组" );   System. out .print( "maxMemory=" );   System. out .println(Runtime. getRuntime ().maxMemory() + " bytes" );   System. out .print( "free mem=" );   System. out .println(Runtime. getRuntime ().freeMemory() + " bytes" );   System. out .print( "total mem=" );   System. out .println(Runtime. getRuntime ().totalMemory() + " bytes" );   }   }   参数:   -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -Xmx20m   上述代码首先在第4~9行打印了基本的系统信息,包括最大可用内存、当前空闲内存和当前总内存。   接着,在第10行申请了1MB内存空间,显然,这块空间将在堆上分配。   在第12~18行,同样打印了最大可用内存、当前空闲内存和当前总内存。   接着,在第20行再次申请了4MB空间。最后,同样打印了这3个参数。   根据前 文的介绍很容易让人想到,这里的最大可用内存就是指-Xmx的取值,当前总内存应该不小于-Xms的设定,因为当前总内存总是在-Xms和-Xmx之间,从-Xms开始根据需要向上增长。而当前空闲内存应该是当前总内存减去当前已经使用的空间。但实际也会很快就能发现中间的偏差。   可以看到,当前的最大内存由-XX:MaxHeapSize 20971520指定,它正好是20* 1024* 1024=20971520字节。而打印的最大可用内存仅仅为20316160字节,比设定值略少。这是因为分配给堆的内存空间和实际可用的内存空间并非一个 概念。由于垃圾回收的需要,虚拟机会对堆空间进行分区管理,不同的区域采用不同的回收算法,一些算法会使用空间换时间的策略工作。   提示:在实际工作中,也可以直接将初始堆-Xms与最大堆-Xmx设置相等。这样的好处是可以减少程序运行时进行的垃圾回收次数,从而提高程序的性能。新生代的配置-XX: SurvivorRatio   参数-Xmn可以用于设置新生代的大小。设置一个较大的新生代会减小老年代的大小,这个参数对系统性能以及GC行为有很大的影响。   新生代的大小一般般设置为整个堆空间的1/3到1/4左右。   SurvivorRatio定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10   参数-XX:SurvivorRatio用来设置新生代中eden空间和from/to 空间的比例关系,它的含义   如下:   -XX: SurvivorRatio=eden/ from=eden/to   考察以下这段简单的Java程序,它连续向系统请求10MB空间(每次申请1MB)。   public class NewSizeDemo {   public static void main(String[] args) {   byte [] b = null ;   for ( int i = 0 ; i < 10 ; i++) {   b = new byte [ 1 * 1024 * 1024 ];   }   }   }   使用不同的堆分配参数执行这段程序,虚拟机的行为表现受到堆空间分配的影响   使用-Xmx20m -Xms20m -Xmn2m -XX:SurvivorRatio=2 -XX:+PrintGCDetails运行   这里eden与from的比值为2比1,故eden区为1024KB。总可用的新生代为1024KB+512KB=1536KB,而新生代总大小为1024KB+512KB+512KB =2048KB=2MB。   由于eden区无法容纳任何一个程序中分配的1MB数组,故触发了一次新生代GC,对eden区进行了部分回收,同时,这个偏小的新生代无法为1MB数组预留空间,故所有的数组都分配在老年代,老年代最终占用18432KB空间。   使用-Xmx20m -Xms20m -Xmn8m -XX:SurvivorRatio=2 -XX:+PrintGCDetails运行   在这个参数下,由于eden区有足够的空间,因此所有的数组都首先分配在eden区。但eden区并不足以预留全部10MB的空间,故在程序运行期间,出现了3次新生代GC。由于程序中每申请一次空间,也同时废弃了,上一次申请的内存(上次申请的内存失去了引用),故在新生代GC中,有效回收了这些失效的内存。最终结果是:所有的内存分配都在新生代进行,通过GC保证了新生代有足够的空间,而老年代没有为这些数组预留任何空间,只是在GC过程中,部分新生代对象晋升到老年代。   使用-Xmx30m -Xms30m -Xmn20m -XX:SurvivorRatio=8 -XX:+PrintGCDetails运行   在这次执行中,由于新生代使用20MB空间,其中eden区占用了16384KB,完全满足10MB   数组的分配,因此所有的分配行为都在eden直接进行,且没有触发任何GC行为。因此from/to和老年代tenured的使用率都为0。   由此可见,不同的堆分布情况,对系统执行会产生一定影响。在实际工作中,应该根据系统的特点做合理的设置,基本策略是:尽可能将对象预留在新生代,减少老年代GC的次数(在本例中的第一种情况, 对象都分配在老年代,显然为后续的老年代GC埋下了伏笔)。-XX:NewRatio=老年代/新生代   除了可以使用参数-Xmn指定新生代的绝对大小外,还可以使用参数-XX:NewRatio来设置新生代和老年代的比例   使用-Xmx20m -Xms20m -XX:NewRatio=2 -XX:+PrintGCDetails运行   此时,因为堆大小为20MB。新生代和老年代的比为1比2。故新生代大小为20MB*1/3= 6MB左右,老年代为13MB左右。   由于在新生代GC时,from/to 空间不足以容纳任何一个1MB数组,影响了新生代的正常回收,故在新生代回收时需要老年代进行空间担保。因此,导致两个1MB数组进入老年代(在新生代GC时,尚有1MB数组幸存,理应进入from/to, 而from/to只有640KB,不足以容纳)。   堆的分配参数示意图。   堆溢出参数   在Java程序的运行过程中,如果堆空间不足,则有可能抛出内存溢出错误(OutOfMemory),   简称为OOM。如下文字显示了典型的堆内存溢出:   一旦发生这类问题,系统就会被迫退出。如果发生在生产环境,可能会引起严重的业务中断。为了能够不断改善系统,避免或减少这类错误的发生,需要在发生错误时,获得尽可能多的现场信息,以帮助研发人员排查现场问题。   Java 虚拟机提供了参数-XX:+HeapDumpOnOutOfMemoryError,使用   该参数,可以在内存溢出时导出整个堆信息。和它配合使用的还有-XX:HeapDumpPath,可以指定导出堆的存放路径。   在很多java所写的服务器脚本中我们可以看到,如nacos   演示案例:   public class DumpOOM {   public static void main(String[] args) {   Vector v= new Vector () ;   for ( int i= 0 ; i< 25 ; i++)   v.add( new byte [ 1 * 1024 * 1024 ]) ;   }   }   -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump   可以看到,虚拟机将当前的堆导出,并保存到D:/a.dump文件下。使用MAT等工具打开该文件进行分析,如图所示,可以很容易地找到这些byte数组和保存它们的Vector 对象实例。   非堆内存的参数设置方法区配置   在JDK 1.8中,永久区被彻底移除,使用了新的元数据区存放类的元数据。默认情况下,   元数据区只受系统可用内存的限制,但依然可以使用参数-XX:MaxMetaspaceSize指定永久区的值大小   16g栈配置   在Java虚拟机中可以使用-Xss参数指定线程的栈最大大小直接内存配置   直接内存也是Java程序中非常重要的组成部分,特别是在NIO被广泛使用后,直接内存的使用也变得非常普遍。直接内存跳过了Java堆,使Java程序可以直接访问原生堆空间,因此,从一定程度上加快了内存空间的访问速度。但是,武断地认为使用直接内存一定可以提高内存访问速度也是不正确的。   最大可用直接内存可以使用参数-XX:MaxDirectMemorySize设置,如不设置,默认值为最大堆空间,即-Xmx。当直接内存使用量达到-XX:MaxDirectMemorySize时,就会触发垃圾回收,   如果垃圾回收不能有效释放足够空间,直接内存溢出依然会引起系统的OOM。   [示例]一般来说,直接内存的访问速度(读或者写)会快于堆内存。下面的代码统计   了对直接内存和堆内存的读写速度。   public class AccessDirectBuffer {   public void directAccess() {   long starttime = System. currentTimeMillis ();   ByteBuffer b = ByteBuffer. allocateDirect ( 500 );   for ( int i = 0 ; i < 100000 ; i++) {   for ( int j = 0 ; j < 99 ; j++)   b.putInt(j);   b.flip();   for ( int j = 0 ; j < 99 ; j++)   b.getInt();   b.clear();   }   long endtime = System. currentTimeMillis ();   System. out .println( "testDi rectWrite:" + (endtime - starttime));   }   public void bufferAccess() {   long starttime = System. currentTimeMillis ();   ByteBuffer b = ByteBuffer. allocate ( 500 );   for ( int i = 0 ; i < 100000 ; i++) {   for ( int j = 0 ; j < 99 ; j++)   b.putInt(j);   b.flip();   for ( int j = 0 ; j < 99 ; j++)   b.getInt();   b.clear();   }   long endtime = System. currentTimeMillis ();   System. out .println( "testBufferwrite:" + (endtime - starttime));   }   public static void main(String[] args) {   AccessDirectBuffer alloc = new AccessDirectBuffer();   alloc.bufferAccess();   alloc.directAccess();   alloc.bufferAccess();   alloc.directAccess();   }   }   程序中,对bufferAccess()和directACcess()方法分别进行了两次调用,第一次视为热身代码,这里忽略其输出,只关注第2次调用的输出结果。从结果可以看出,直接内存的访问比堆内存快很多。   虽然在访问读写上直接内存有较大的优势,但是在内存空间申请时,直接内存毫无优势   示例代码:   public class AllocDirectBuffer {   public void directAllocate() {   long starttime = System. currentTimeMillis ();   for ( int i = 0 ; i < 200000 ; i++) {   ByteBuffer b = ByteBuffer. allocateDirect ( 1000 );   }   long endtime = System. currentTimeMillis ();   System. out .println( "directAllocate:" + (endtime - starttime));   }   public void bufferAllocate() {   long starttime = System. currentTimeMillis ();   for ( int i = 0 ; i < 200000 ; i++) {   ByteBuffer b = ByteBuffer. allocate ( 1000 );   }   long endtime = System. currentTimeMillis ();   System. out .println( "bufferAllocate:" + (endtime - starttime));   }   public static void main(String[] args) {   AllocDirectBuffer alloc = new AllocDirectBuffer();   alloc.bufferAllocate();   alloc.directAllocate();   }   }   由此,可以得出结论:直接内存适合申请次数较少、访问较频繁的场合。如果内存空间本身需要频繁申请,则并不适合使用直接内存。虚拟机的工作模式Server和Client   目前的Java虚拟机支持Client和Server两种运行模式。使用参数-client可以指定使用Client模式,使用参数-server可以指定使用Server 模式。默认情况下,虚拟机会根据当前计算机系统环境自动选择运行模式。使用-version 参数可以查看当前的模式,如下所示:   与Client模式相比,Server模式的启动比较慢,因为Server模式会尝试收集更多的系统性能信息,使用更复杂的优化算法对程序进行优化。因此,当系统完全启动并进入运行稳定期后,Server模式的执行速度会远远快于Client 模式。   所以,对于后台长期运行的系统,使用-server参数启动对系统的整体性能可以有不小的帮助。但对于用户界面程序,运行时间不长,又追求启动速度,Client 模式也是不错的选择。   从发展趋势上看,未来64位系统必然会逐步取代32位系统,而在64位系统中虚拟机更倾向于使用Server模式运行。   五万字长篇-java虚拟机看这篇文章就够了(中)   五万字长篇-java虚拟机看这篇文章就够了(下)   Git版本控制学习总结

原谅原谅那个你最不想原谅的人,原谅他是放过自己。伤口是光进入你内心的地方。所有大彻大悟之人都曾经历过无药可救,心不死则道不生。你们有没有发现,你在什么时候才会成长?是在逆境的时候,不是一颗心只想陪伴你到老大千世界,茫茫人海相遇就是缘,不是每个人都有机会相遇的,好好珍惜今生的缘,珍惜身边每个人,才不会给你留下人生遗憾。要珍惜彼此的缘份,相识到相知,一段缘一段情,人生真如一本念不完的书如何成功把小康过成一地鸡毛?有同款吗?这社风人心总与我们内心的设想背道而驰。提醒心智似孩童者以我为鉴。昨晚听到有人因我而感慨看看她憨态可掬的样子,要是没有巨额外债该是多么幸福啊!我心里顿时潮了,眼泪一下子落了下来,久久女排联赛中的希望之星二传篇二传是球队的大脑,好的二传可以通过出色的传球技术弥补一传和防守的不足。中国女排现役的优秀二传有丁霞,姚迪和刁琳宇三人。她们的球技都已进入成熟期,巴黎奥运会的主力二传之争基本发生在她CBA重磅消息!北控做新决策,朱芳雨邀请威姆斯,郭昊文有望复出北控男篮近日宣布主教练张劲松已经辞职。在马布里离开后,张劲松担任主教练8场比赛,战绩仅有2胜6负,排名倒数第5。这让人们不禁回想起马布里执教北控男篮的成绩。要知道,马布里带队第一年棋界浴火之年,王天一豪创伟业,孟繁睿天才出道,国粹涅槃重生时光的齿轮,捎带年华,转动不停,讲述着人世间的悲欢离合与万物传承。两年前的那条台阶,我踏不上去。去年的那条台阶,勉力可以蹬踩。今年这条台阶,终于能试着一走。虎去兔至,岁月光阴,已不HappyChineseNewYear,那不勒斯官方INS引韩国球迷不满。直播吧1月22日讯那不勒斯今日20客胜萨勒尼塔纳,继续领跑意甲。那不勒斯官方INS账号发的一则中国新年快乐的拜年内容引发韩国球迷不满。那不勒斯INS7小时前发布了stories,2023年新能源车的11个趋势丨南财号联播mpId1662022年中国经济数据出炉GDP达121。02万亿元,同比增长31月17日,国家统计局发布的2022年经济数据。初步核算,全年国内生产总值1210207亿元,按不变价广东队三消息马尚暂时离队,徐杰有望替补赵睿,租借小将被退货马尚暂时离队北京时间1月23日,CBA第二阶段休赛期,广东男篮在结束和辽宁男篮的收官大战后,就宣布就地结束各自回家过春节,第三阶段将会在3月1号打响,在此期间球员是有一个多月的休息全是火枪手!阿森纳队内有6名球员,本赛季直接参与10粒以上进球直播吧1月23日讯阿森纳本赛季队内虽然没有进球大户,但算上刚刚加盟的特罗萨德,目前队内已经有6名球员在本赛季直接参与了10粒以上的进球。阿森纳队内直接参与10粒以上进球的球员萨卡8千万豪宅一屋豪车过年,林丹下厨宴请友人,和谢杏芳合作当老板头条创作挑战赛正值春节长假期间,大家都各自忙碌回家团圆,很多运动员和演艺明星们同样享受着各自的家庭的团聚欢乐时光,林丹也不例外。在社交媒体上,林丹分享了全家团年饭的美食,这次林丹也
智己L7,一辆车长5米1的小钢炮在电力的带动下,百公里加速成绩达到8秒6秒甚至是4秒这些传统燃油车渴望的极限成为了家常便饭。显然,随着汽车电气化的推进,马力值的提升几乎成了产品性能中最容易解决的难题。电驱动带来最00,皆大欢喜!欧冠默契球诞生,欧联6冠王被做掉,小组悬念结束北京时间10月26日凌晨,欧冠小组赛G组第5轮结束一场焦点战,曼城客场00战平多特蒙德,上演皆大欢喜的结局。曼城提前一轮锁定头名,多特蒙德提前一轮出线,提前2个多小时踢完比赛的塞维围绕李彦宏委员关于车与路的建议,工信部回应了智能网联汽车是全球汽车产业发展的未来方向,也是我国汽车产业转型升级发展的战略选择,今后将联合公安部交通运输部住房城乡建设部等部门,在推动更多地方先行先试推进智能网联汽车产品准入试点web3。0比特币区块链元宇宙,以及那些待收割的韭菜们前几天看到周星驰在社交账号上招聘web3。0的人才,感觉有必要说说web3。0,当然不是基于技术层面,而是从另一个维度说说web3。0以及其它相关的概念,从而做到如何反欺诈,如何避枪球联动智能监控技术解析来了枪球联动解决方案特性分析传统探测技术智能监控技术适用范围多数为周界,用于安全防区探测难度大绝大多数周界与安全防区漏报率一般,多种探测技术的结合会降低误报几率,但同时也会提高漏报率智苹果将发布搭载48核M2处理器的MacPro据彭博社的消息称,苹果公司正在内部加紧测试其首款搭载AppleSilicon芯片的MacPro根据消息称,苹果将发布一款全新的MacPro,并且已经在测试一台拥有M2处理器的计算机终于,苹果还是对折叠屏下手了早在去年,就有不少传闻称苹果将要在2023年左右推出折叠屏iPhone。彼时,市面上的折叠屏被炒作得如火如荼,似乎上万的售价也阻挡不了各位数码爱好者购买的决心。不过面对外界厂商接连大屏党注意!买苹果14Plus还是苹果13ProMax对于iPhone14Plus和iPhone13ProMax这两款产品来说,机圈的买新不买旧真的适用吗?友友用数据来告诉你,如何正确的选择(不考虑14ProMax的情况下)!一iPhA股明天上演绝地反击今天的大盘再次让大家失望了一把,我们原本以为昨天的大跌能够让今天有像样的反弹,但是上午临近中午收盘确实有比较大的反弹,一度三大指数全面翻红,这不像是普通机构能够做到的,但是下午大盘港股为何大跌港股连续大跌,已经破了15000点,昨天开始出现恐慌性情绪。很多人不仅要问为什么,也喜欢从已然发生的事情上寻找原因。我们可以观察,伴随昨天港股大跌,人民币黄金期货与美元计价期货走势行空时评离岸人民币汇率一度跌破7。3,美元升值割了谁的韭菜?行空时评,空哥带你看颜色不一样的烟火。昨天离岸人民币汇率跌破7。3,最低跌至7。33,市场一片惊呼,截至10月24日,在岸人民币兑美元汇率报收7。26,创十几年来新低,不少媒体都已