欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

HotspotVM的类型选择

程序员文章站 2022-07-15 14:52:50
...
前言

一般情况下,JDK中提供了Client和Server两种类型的JVM,那JDK是在运行时是如何选择的呢。本文主要探讨默认情况下Windows和Solaris(Linux)是如何选择JVM种类的。

JVM.cfg文件

在JRE_HOME/lib/<arch>(i386)/下存在这个一个jvm.cfg文件,用于配置JVM的种类。
/OpenJDK7/hotspot/src/share/tools/launcher/java.c 下的ReadKnownVMs函数注释详细的说明了该文件的主要格式:
    /*
     *     jvmcfg         :=  { vmLine }
1820 *     vmLine         :=  knownLine
1821 *                    |   aliasLine
1822 *                    |   warnLine
1823 *                    |   ignoreLine
1824 *                    |   errorLine
1825 *                    |   predicateLine
1826 *                    |   commentLine
1827 *     knownLine      :=  flag  "KNOWN"                  EOL
1828 *     warnLine       :=  flag  "WARN"                   EOL
1829 *     ignoreLine     :=  flag  "IGNORE"                 EOL
1830 *     errorLine      :=  flag  "ERROR"                  EOL
1831 *     aliasLine      :=  flag  "ALIASED_TO"       flag  EOL
1832 *     predicateLine  :=  flag  "IF_SERVER_CLASS"  flag  EOL
1833 *     commentLine    :=  "#" text                       EOL
1834 *     flag           :=  "-" identifier
     */



1.假如flag出现在knowLine行,那么identifier将作为存放JVM 库文件的路径名,去加载相应的VM;
2.假如flag出现在aliasLine行的第一个位置上,那么改行的第二个flag的identifier将作为JVM的名称;
3.假如flag出现在warnLine行的位置,那么该flag的identifier作为JVM的名称,但是会产生一条警告信息;
C:\Users\xingjl.fnst>D:\JDK\Oracle\jdk1.6.0_45\bin\java  -classic -version
Warning: classic VM not supported; client VM will be used
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) Client VM (build 20.45-b01, mixed mode)

4.假如flag出现在ignoreLine行的位置,那么该identifier将被忽略使用默认的VM。
5.假如flag出现在errorLine行的位置,那么错误消息将会生成。
C:\Users\xingjl.fnst>D:\JDK\Oracle\jdk1.6.0_45\bin\java  -error -version
Unrecognized option: -error
Could not create the Java virtual machine.

6.假如flag出现在predicateLine行,并且通过了机器上的判断(是否为服务器),那么predicateLine行的第二个flag的identifier将作为vm的名称;否则使用第一个。
7.如果在命令行中没有flag指定,那么jvm.cfg的文件的第一行将作为VM的名称。
8.PredicateLines这一行只可以作为jvm,cfg文件的第一行使用。
下面一个Windows JDK6u45的jvm.cfg文件的内容
-client KNOWN
-server KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR

Lanuger启动流程

首先java.exe的CreateExecutionEnvironment函数调用GetJREPath去分析JRE的的安装路径,然后调用ReadKnownVMs函数去加载jvm.cfg文件,将文件中内容加载到如下的结构体中:
/* Values for vmdesc.flag */
#define VM_UNKNOWN              -1
#define VM_KNOWN                 0
#define VM_ALIASED_TO            1
#define VM_WARN                  2
#define VM_ERROR                 3
#define VM_IF_SERVER_CLASS       4
#define VM_IGNORE                5
struct vmdesc {
    char *name;
    int flag;
    char *alias;
    char *server_class;
};

其中flag为上面宏定义中定义的内容,name为jvm.cfg 文件中每行的第一个flag,当一行是aliasLine行时,aliasLine 则记录第二个falg的值;当一行为predicateLine 行时,server_class则记录第二flag的值。

加载完成后,调用CheckJvmType函数来确定当前所使用的VM种类;
命令行指定VM类型

1.命令行中,使用的是VM_KNOWN类型的(-client或-server),那么JVM将按照相应的路径加载jvm.dll
-client ${ JAVA_HOME } \jre\bin\client\jvm.dll
-server ${ JAVA_HOME } \jre\bin\server\jvm.dll

2.在命令行中,使用的是VM_ALIASED_TO(别名)类型的第一个flag时(-hotspot),那么使用VM_ALIASED_TO行的第二个flag作为vm的种类
while (knownVMs[jvmidx].flag == VM_ALIASED_TO) {
      int nextIdx = KnownVMIndex(knownVMs[jvmidx].alias);
–中略-
      jvmidx = nextIdx;
      jvmtype = knownVMs[jvmidx].name+1;
      loopCount++;
}

3.在命令行中,使用的是VM_WARN行内容,则先输出一条警告消息,然后使用jvm.cfg文件中的第一行falg作为VM类型
4.在命令行中,使用的是VM_IGNORE行内容,使用jvm.cfg文件中的第一行falg作为VM类型
5.在命令行中,使用的是VM_ERROR行内容,则会打印Error消息。
以下是3-5条的代码:
switch (knownVMs[jvmidx].flag) {
    case VM_WARN:
        if (!speculative) {
            fprintf(stderr, "Warning: %s VM not supported; %s VM will be used\n",
                    jvmtype, knownVMs[0].name + 1);
        }
        /* fall through */
    case VM_IGNORE:
        jvmtype = knownVMs[jvmidx=0].name + 1;
        /* fall through */
    case VM_KNOWN:
        break;
    case VM_ERROR:
        if (!speculative) {
            ReportErrorMessage2("Error: %s VM not supported", jvmtype, JNI_TRUE);
            exit(1);
        } else {
            return "ERROR";
        }
}

命令行未指定VM类型

在CheckJvmType函数中有如下代码:
if (jvmtype == NULL) {
      char* result = knownVMs[0].name+1;
      /* Use a different VM type if we are on a server class machine? */
      if ((knownVMs[0].flag == VM_IF_SERVER_CLASS) &&
          (ServerClassMachine() == JNI_TRUE)) {
        result = knownVMs[0].server_class+1;
      }
      if (_launcher_debug) {
        printf("Default VM: %s\n", result);
      }
      return result;
}

由此可知,VM类型默认使用jvm.cfg第一行的flag。最终由该行的类型(是否为VM_IF_SERVER_CLASS)以及是否为服务器决定VM的类型。
Windows下:

JDK6u45的jvm.cfg文件参照上面提供的。
第一行非VM_IF_SERVER_CLASS类型,如果人为的修改为VM_IF_SERVER_CLASS类型,进行第二步的判断:是否为服务器级别的机器,Windows环境下的ServerClassMachine函数如下:
824jboolean
825ServerClassMachine() {
826  jboolean result = JNI_FALSE;
827#if   defined(NEVER_ACT_AS_SERVER_CLASS_MACHINE)
828  result = JNI_FALSE;
829#elif defined(ALWAYS_ACT_AS_SERVER_CLASS_MACHINE)
830  result = JNI_TRUE;
831#endif
832  return result;
833}

一般情况下总是返回false,所以,即使修改了cfg文件,VM种类也不会根据机器的配置进行自动搭配。
Solaris(Linux)

JDK6u45的jvm.cfg文件如下:
-client IF_SERVER_CLASS -server
-server KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR

ServerClassMachine函数如下:
jboolean
1457ServerClassMachine(void) {
1458  jboolean result = JNI_FALSE;
1459#if   defined(NEVER_ACT_AS_SERVER_CLASS_MACHINE)
1460  result = JNI_FALSE;
1461#elif defined(ALWAYS_ACT_AS_SERVER_CLASS_MACHINE)
1462  result = JNI_TRUE;
1463#elif defined(__sun) && defined(__sparc)
1464  result = solaris_sparc_ServerClassMachine();
1465#elif defined(__sun) && defined(i586)
1466  result = solaris_i586_ServerClassMachine();
1467#elif defined(__linux__) && defined(i586)
1468  result = linux_i586_ServerClassMachine();
1469#else
1470  if (_launcher_debug) {
1471    printf("ServerClassMachine: returns default value of %s\n",
1472           (result == JNI_TRUE ? "true" : "false"));
1473  }
1474#endif
1475  return result;
1476}

我们以sun sparc平台的JDK为例,solaris_sparc_ServerClassMachine函数代码如下:
1102jboolean
1103solaris_sparc_ServerClassMachine(void) {
1104  jboolean            result            = JNI_FALSE;
1105  /* How big is a server class machine? */
1106  const unsigned long server_processors = 2UL;
1107  const uint64_t      server_memory     = 2UL * GB;
1108  const uint64_t      actual_memory     = physical_memory();
1109
1110  /* Is this a server class machine? */
1111  if (actual_memory >= server_memory) {
1112    const unsigned long actual_processors = physical_processors();
1113    if (actual_processors >= server_processors) {
1114      result = JNI_TRUE;
1115    }
1116  }
1117  if (_launcher_debug) {
1118    printf("solaris_" LIBARCHNAME "_ServerClassMachine: %s\n",
1119           (result == JNI_TRUE ? "JNI_TRUE" : "JNI_FALSE"));
1120  }
1121  return result;
1122}

如果机器的内存大于等于2GB,并且机器的CPU为2核或以上时,即判定为服务器级别的机器。
综合Widows和Solaris的分析,默认的VM种类选在如下:

1. 32位的JDK在Windows平台上,总是使用-client模式(即jvm.cfg文件中的第一个flag)
   默认第一个flag
  
   -client KNOWN
   

2. 32位的JDK在Solaris(Linux)机器上,根据机器是否为服务器级别的选择相应的VM;服务器级别的使用-sever,否则使用-client
   jvm.cfg默认的第一个falg
  
   -client IF_SERVER_CLASS -server 
   

3. 64位的JDK在Windows,Solaris平台上只有-server一种VM类型,可以选择-client但是      会被忽略。
  
   -client IGNORE
   

4. 64位的JDK在Linux平台上只有-server一种VM类型,选择-client会报错
  
   -client ERROR
   



-以上-
相关标签: JVM VM