如何让java程序运行(java程序运行)

  • 时间:
  • 4136人关注

这是一篇关于java相关的编程问答内容,被293位程序员关注,内容涉及到java程序、java程序运行、java程序运行等,由马淑慧 编辑补充,一起来看下大家的回答。

码农之家
精选回答2:java虚拟机运行时数据区分析

13小时5分钟前回答

JVMmemorymodel

这篇文章主要介绍在JVM规范中描述的运行时数据区(RuntimeDataAreas)。这些区域设计用来存储被JVM自身或者在JVM上运行的程序所是用的数据。

我们先总览JVM,然后介绍下字节码,最后介绍不同的数据区域。

总览

JVM作为操作系统的抽象,保证同样的代码在不同的硬件或操作系统上的行为一致。

比如:

对于基本类型int,无论在16位/32位/64位操作系统上,都是一个32位有符号整数。范围从-2^31到2^31-1

无论操作系统或者硬件是大字节序还是小字节序,保证JVM存储和使用的内存中的数据都是大字节序(先读高位字节)

不同的JVM实现可能会有些区别,但大体上是相同的。

java虚拟机运行时数据区分析

上图是一个JVM的总览

JVM解释编译器生成的字节码。虽然JVM是Java虚拟机的缩写,但是只要是能够编译为字节码的语言,都可以基于JVM运行,比如 scala、groovy<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs6qwcux3MPixrW3sbXEtMXFzEkvT6Os19a92sLru+Gxu2NsYXNzbG9hZGVyvNPU2LKiu7q05rW91MvQ0Mqxyv2+3cf41tC1xNK7uPbH+NPyo6zWqrXAvNPU2Mv8tcRjbGFzc2xvYWRlcrG7z/q72bvy1d9KVk3No9a51MvQ0KGjPC9wPg0KPHA+vNPU2LXE19a92sLrzai5/da00NDS/cfmKGV4ZWN1dGlvbiBlbmdpbmUpvfjQ0L3iys26zda00NA8L3A+DQo8cD7WtNDQ0v3H5tDo0qq05rSis8zQ8snPz8LOxKOsscjI57PM0PLWtNDQtb3ExNK70NCjrLvy1d/K/b7dvMbL47XE1tC85L3hufs8L3A+DQo8cD7WtNDQ0v3H5tKyuLrU8LSmwO3T67XXsuOy2df3z7XNs7XEvbu7pTwvcD4NCjxwPioquty24EpWTba8yrXP1sHLvLTKsbHg0uu5psTcKEpJVD1qdXN0IGluIHRpbWUpoaNKSVS+zcrHsNG+rbOj1rTQ0LXEtPrC6yjIyLXjtPrC6ymx4NLrs8mxvrXYtPrC6yhOYXRpdmUgQ29kZSmho7Tmt8VKSVSx4NLryfqzybT6wuu1xMf40/Kzxs6qPC9wPg0KPHA+tPrC67u6tObH+ChDb2RlIENhY2gpoaO8tMqxseDS67y8yvUoSklUKby2tPO1xMzhuN/By0pWTbXE0NTE3CoqPC9wPg0KPGgyIGlkPQ=="基于栈stack的架构">基于栈(stack)的架构

JVM使用基于栈的架构。虽然栈对于开发者是透明的,但是栈对于生成的字节码和JVM都有很重要的作用或者说影响。

我们开发的程序,会转换位低级别的操作,存于字节码中。在JVM中通过操作数(operand)映射到操作指令。按照JVM规范,操作指令需要的参数是从操作数栈获得的(the operand stack)。

java虚拟机运行时数据区分析

举个两个数相加的例子。这这个操作称为 iadd 。下面是在字节码中 3+4 的过程

首先把3和4压入操作数栈

调用 iadd 指令

iadd 指令会从操作数栈顶弹出2个数

3+4的结果压入操作数栈,供后面使用

这种方式被称为基于栈的架构。还有其他的方式可以处理低级别操作,比如基于寄存器的架构(register based architecture)。

字节码

java字节码是java源码转换为一系列低级别操作的结果。每个操作由一个字节长度的操作码(opcode or operation code)和零或多个字节长度的参数(但是大多数操作使用的参数都是通过操作数栈获取的)组成。一个字节可以表示256个数,从0x00到0xff,目前到java8,共使用了204个。

下面列出不同种类的字节码操作码以及其范围和简单的描述

Constants: 将常量池的值或者已知的值压入操作数栈。 0x00 - 0x14

Loads: 将局部变量值压入操作数栈。 0x15 - 0x35

Stores: 从操作数栈加载值赋给局部变量 0x36 - 0x56

Stack: 处理操作数栈 0x57 - 0x5f

Math: 从操作数栈获取值进行基本的数学计算 0x60 - 0x84

Conversions: 进行类型之间的转换 0x85 - 0x 93

Comaprisons: 两个值的比较操作 0x94 - 0xa6

Controls: 执行goto、return、循环等等控制操作 0xa7 - 0xb1

References: 执行分配对象或数组,获取或检查 对象、方法、静态方法的引用。也可以调用静态方法。 0xb2 - oxc3

Extended: Extended: operations from the others categories that were added after. From value 0xc4 to 0xc9

(这句说不好什么意思。。。)

Reserved: JVM实现内部是用的槽子0xca,oxfe,oxff

这204个操作都很简单,举几个例子

ifeq(0x99) 判断两个值是否相等

iadd(0x60) 把两个数相加

i2l (0x85) 把一个int 转换位 long

arraylength (0xbe) 返回数组长度

pop (0x57) 从操作数栈顶弹出一个值

我们需要编译器来创建字节码文件,标准的java编译器就是jdk中的 javac。

public class Test {
 
 public static void main(String[] args) {
  int a =1;
  int b = 15;
  int result = add(a,b);
 }
 
 public static int add(int a, int b){
  int result = a + b;
  return result;
 }
}

通过“javac Test.java” 可以得到 “Test.class”的字节码文件。字节码文件是2进制的,我们可以通过javap,把二进制的字节码文件转换成文本形式

java -verbose Test.class

Classfile /C:/TMP/Test.class
 Last modified 1 avr. 2015; size 367 bytes
 MD5 checksum adb9ff75f12fc6ce1cdde22a9c4c7426
 Compiled from "Test.java"
public class com.codinggeek.jvm.Test
 SourceFile: "Test.java"
 minor version: 0
 major version: 51
 flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
  #1 = Methodref     #4.#15     // java/lang/Object."<init>":()V
  #2 = Methodref     #3.#16     // com/codinggeek/jvm/Test.add:(II)I
  #3 = Class       #17      // com/codinggeek/jvm/Test
  #4 = Class       #18      // java/lang/Object
  #5 = Utf8        <init>
  #6 = Utf8        ()V
  #7 = Utf8        Code
  #8 = Utf8        LineNumberTable
  #9 = Utf8        main
 #10 = Utf8        ([Ljava/lang/String;)V
 #11 = Utf8        add
 #12 = Utf8        (II)I
 #13 = Utf8        SourceFile
 #14 = Utf8        Test.java
 #15 = NameAndType    #5:#6     // "<init>":()V
 #16 = NameAndType    #11:#12    // add:(II)I
 #17 = Utf8        com/codinggeek/jvm/Test
 #18 = Utf8        java/lang/Object
{
 public com.codinggeek.jvm.Test();
  flags: ACC_PUBLIC
  Code:
   stack=1, locals=1, args_size=1
     0: aload_0
     1: invokespecial #1         // Method java/lang/Object."<init>":()V
     4: return
   LineNumberTable:
    line 3: 0
 
 public static void main(java.lang.String[]);
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
   stack=2, locals=4, args_size=1
     0: iconst_1
     1: istore_1
     2: bipush    15
     4: istore_2
     5: iload_1
     6: iload_2
     7: invokestatic #2         // Method add:(II)I
    10: istore_3
    11: return
   LineNumberTable:
    line 6: 0
    line 7: 2
    line 8: 5
    line 9: 11
 
 public static int add(int, int);
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
   stack=2, locals=3, args_size=2
     0: iload_0
     1: iload_1
     2: iadd
     3: istore_2
     4: iload_2
     5: ireturn
   LineNumberTable:
    line 12: 0
    line 13: 4
}

可以看出字节码不只是java代码的简单翻译,它包括:

类的常量池(cosntant pool)描述。常量池是用于存储类元数据的JVM数据区域,比如类内部的方法名,参数列表,等等。当JVM加载一个类的时候,这些元数据就会加载到常量池

通过行号表和或局部变量表来提供函数和天猫的变量在字节码中的具体位置信息

java代码的翻译(包括隐藏的父类构造)

提供更具体的对于操作数栈的操作和更完整的传递和获取参数的方式

下面是一个简单的字节码文件存储信息的描述

ClassFile {
 u4 magic;
 u2 minor_version;
 u2 major_version;
 u2 constant_pool_count;
 cp_info constant_pool[constant_pool_count-1];
 u2 access_flags;
 u2 this_class;
 u2 super_class;
 u2 interfaces_count;
 u2 interfaces[interfaces_count];
 u2 fields_count;
 field_info fields[fields_count];
 u2 methods_count;
 method_info methods[methods_count];
 u2 attributes_count;
 attribute_info attributes[attributes_count];
}

运行时数据区

运行时数据区是存储数据的内存区域设计。这些数据供开发者或JVM内部使用。

java虚拟机运行时数据区分析

堆(Heap)

堆 在JVM启动的时候创建,由所有的JVM线程所共享。所有的类实例、数组都分配到堆(由new所创建的)。

堆必须由一个垃圾收集器来管理,垃圾收集器负责释放被开发者所创建,并且不会再被使用到的对象。

至于垃圾收集的策略由JVM实现决定(比如HotSpot提供了多种算法).

堆内存有一个最大值限制,如果超过这个值 JVM会抛出一个 OutOfMemroy异常

方法区(Method area)

方法区也是被JVM的所有线程所共享。同样的随JVM启动被创建。方法区存储的数据由classloader从字节码中加载,这些数据会在应用运行过程中一致存在,除非加载它们的classloader被销毁或者JVM停止。

方法区存储如下数据:

类信息(属性名、方法名、父类名、借口名、版本、等等)

方法和构造的字节码

加载每个类时创建的运行时常量池

JVM规范并不强迫在堆中实现方法区。在java7以前,HotSpot 使用一个称为永久带(PermGen)的区域实现方法区。永久带与堆相邻(和堆一样进行内存管理),默认位64MB

从java8开始,HptSpot使用分离的本地内存实现方法区,起名元数据区(Metaspace)。元数据区最大可用空间即整个系统的可用内存。

如果方法去申请不到可用内存,JVM也会抛出OutOfMemoryError.

运行时常量池(Runtime constant pool)
运行时常量池是方法区的一部分。因为运行吃常量池对于元数据的重要性,java规范中在方法区之外单独对其进行了描述。运行时常量池会随着加载的类和接口而增长。

常量池有点想传统语言中的语法表。换句话说,当调用一个类、方法或属性时,JVM通过运行时常量池来寻找这些数据在内存中的真实地址。运行时常量池也包含字符串字面值或基本类型的常量

Stirng myString="This is a string litteral"
 
  static final int MY_CONSTANT = 2 ;

pc(程序计数器)寄存器(每个线程) The pc Register (Per Thread)
每个线程有自己的pc(程序计数器)寄存器,与线程创建是一同创建。每个线程在一个时间点上只能执行一个方法,称为该线程的当前方法(current method)。pc寄存器包含JVM当前在执行指令(在方法区)的地址。

如果当前执行的方法是本地方法(native),pc寄存器的值是undefined

虚拟机栈每个线程-java-virtual-machine-stacks-per-thread">虚拟机栈(每个线程) java virtual machine stacks (per thread)
虚拟机栈存储多个帧,因此在描述栈前,我们先来看下帧

帧(frames)

帧是一个数据结构,帧包含表示线程正在执行的当前方法状态的多个数据:

操作数栈(Operand Stack): 之前已经提到过,字节码指令使用操作数栈来传递参数

局部变量数组(Local variable array): 这个数组包含当前执行方法的一个作用域内的所有局部变量。这个数组可以包含基本类型、引用或者返回地址。局部变量数组的大小在编译时就已经确定。jvm在方法调用时使用局部变量传递参数,被调方法的局部变量数组通过调用方法的操作数栈创建。

运行时常量池引用: 引用当前类当前被执行方法的常量池。JVM使用常量池引用传递信号给真正的内存引用。

栈(stack)

每个JVM线程都有一个私有的JVM栈,与线程同时创建。java虚拟机栈存储帧。每次调用一个方法时,都会创建一个帧,并且压入虚拟机栈。当这个方法执行完成时,这个帧也会销毁(无论方法是正常执行完成,还是抛出异常)

在一个线程执行的过程中只有一个帧是可用的。这个帧称为当前帧(current frame)。

对局部变量和操作数栈的操作通常和当前帧的引用一起。

我们再看一个加法的例子

public int add(int a, int b){
 return a + b;
}
 
public void functionA(){
// some code without function call
 int result = add(2,3); //call to function B
// some code without function call
}

java虚拟机运行时数据区分析

在方法A内部,A帧是当前帧,位于虚拟机栈顶。在调用add方法开始时,创建一个新的帧B,并且压入虚拟机栈。帧B成为新的当前帧。

帧B的局部变量数组通过帧A的操作数栈中的数据填充。当add方法结束在,帧B被销毁,帧A重新成为当前帧。add方法的结果压入A帧的操作数栈,这样方法A可以通过帧A的操作数栈获取add 的结果.

总结

以上就是本文关于java虚拟机运行时数据区分析的全部内容,希望对大家有所帮助。

如有不足之处,欢迎留言指出。

展开问题
码农之家
精选回答3:shell脚本运行java程序jar的方法

15小时18分钟前回答

在UBuntu上部署项目的时候,我们往往通过一段shell来启动程序,甚至是通过crontab定时任务来定时的调用java程序,但是很奇怪的一个问题就是,比如我写了一个如下的shell脚本:

#!/bin/sh
export mypath=/root/project/wishnomal

java -Xmx3000m -Xms3000m -server -d64 -Dfile.encoding=UTF-8 -Dfetch.threads=300 -classpath $mypath/:$mypath/wish2-assembly-1.0.0.jar newstandard.CrawlerNewStandard $*

echo "END"

手动命令行运行该脚本的时候,可以正常运行java程序,但是使用crontab定时任务,貌似就不起效果了

分析可能原因:

 1)是否当前用户对此shell脚本没有可执行权限,通过ls -lrt /apps/service/mtk/checking/run.sh查看脚本是有可执行,但是有执行权限呀-rwxr-xr-x

 2)既然单独运行脚本没问题,那会不会是定时的问题呢?于是写了一个简单的输出的shell脚本通过定时也是没问题的。说明还是脚本的问题。

后来上网查了下,想到可能是脚本中环境变量的原因,因为通过crontab运行脚本,是以root用户,而不是当前用户,于是cat /etc/profile查看环境变量,然后修改脚本如下:

分析可能原因:

 1)是否当前用户对此shell脚本没有可执行权限,通过ls -lrt /apps/service/mtk/checking/run.sh查看脚本是有可执行,但是有执行权限呀-rwxr-xr-x

 2)既然单独运行脚本没问题,那会不会是定时的问题呢?于是写了一个简单的输出的shell脚本通过定时也是没问题的。说明还是脚本的问题。

后来上网查了下,想到可能是脚本中环境变量的原因,因为通过crontab运行脚本,是以root用户,而不是当前用户,于是cat /etc/profile查看环境变量,然后修改脚本如下:

#!/bin/sh
export mypath=/root/project/wishnomal
export JAVA_HOME=/root/lib/jdk1.7.0_72
PATH=$PATH:$JAVA_HOME/bin

java -Xmx3000m -Xms3000m -server -d64 -Dfile.encoding=UTF-8 -Dfetch.threads=300 -classpath $mypath/:$mypath/wish2-assembly-1.0.0.jar newstandard.CrawlerNewStandard $*

echo "END"

export显示导出为用户环境变量的环境变量

这样crontab计划任务就正常了。 

修改参考:

#!/bin/sh 
# ----------------------------------------------------------------------------- 
# Start script for the CMGP BOSSCONTROL  
# 
# $Id: run_bosscontrol.sh,v 1.0 2007/11/06 Exp $ 
# ----------------------------------------------------------------------------- 
#指定字符集 
LANG=zh_CN.GBK export LANG 
RUN_HOME=. 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/checking.jar 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/ojdbc14.jar 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/commons-dbutils-1.1.jar 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/log4j-1.2.14.jar 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/dom4j-1.6.jar 
 
export CLASSPATH 
 
java com.**.checking.Checking_Start >> log.out &  

手动命令行运行该脚本的时候,可以正常运行java程序,但是使用crontab定时任务,貌似就不起效果了,很是郁闷哪,查原因哪,分析可能原因:

 1)是否当前用户对此shell脚本没有可执行权限,通过ls -lrt /apps/service/mtk/checking/run.sh查看脚本是有可执行,但是有执行权限呀-rwxr-xr-x

 2)既然单独运行脚本没问题,那会不会是定时的问题呢?于是写了一个简单的输出的shell脚本通过定时也是没问题的。说明还是脚本的问题。

后来上网查了下,想到可能是脚本中环境变量的原因,因为通过crontab运行脚本,是以root用户,而不是当前用户,于是cat /etc/profile查看环境变量,然后修改脚本如下:

#!/bin/sh 
# ----------------------------------------------------------------------------- 
# Start script for the CMGP BOSSCONTROL  
# 
# $Id: run_bosscontrol.sh,v 1.0 2007/11/06 Exp $ 
# ----------------------------------------------------------------------------- 
export PATH=/apps/usr/java/jdk1.5/bin:$PATH 
export JAVA_HOME=/apps/usr/java/jdk1.5 
export JRE_HOME=/apps/usr/java/jdk1.5/jre 
export CLASSPATH=/apps/usr/java/jdk1.5/lib:/apps/usr/java/jdk1.5/jre/lib:$CLASSPATH 
RUN_HOME=/apps/service/checking 
CLASSPATH=$CLASSPATH$RUN_HOME/lib/checking.jar 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/ojdbc14.jar 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/commons-dbutils-1.1.jar 
CLASSPATH=$CLASSPATH:$RUN_HOME/lib/log4j-1.2.14.jar 
 CLASSPATH=$CLASSPATH:$RUN_HOME/lib/dom4j-1.6.jar 
 
export CLASSPATH=$CLASSPATH 
 
java com.**.checking.Checking_Start >> log.out &  

export显示导出为用户环境变量的环境变量

以上这种jar包是通过eclipse工具export导出,不包含MANIFEST.MF文件,如果使用打包工具Ant,我们可以在打包默认的build.xml文件中设置Class-Path

将第三方jar包加入manifest.mf文件中,且指定程序主类

在build.xml中添加如下内容:

<!-- create a property containing all .jar files, prefix lib/, and seperated with a space --> 
<pathconvert property="libs.project" pathsep=" "> 
  <mapper> 
   <chainedmapper> 
    <!-- remove absolute path --> 
    <flattenmapper /> 
    <!-- add lib/ prefix --> 
    <globmapper from="*" to="lib/*" /> 
   </chainedmapper> 
  </mapper> 
   <path> 
   <!-- lib.home contains all jar files, in several subdirectories --> 
   <fileset dir="${lib.dir}"> 
   <include name="**/*.jar" /> 
   </fileset> 
   </path> 
 </pathconvert> 

另外,在create manifest文件时,加上:

<!-- 这样就可以将第三方jar包加入 -->  
<attribute name="Class-Path" value="${libs.project}" /> 
<!-- 程序运行的主类 --> 
<attribute name="Main-Class" value="com.**.checking.Checking_Start " /> 

这样运行ant,打成的jar包中MANIFEST.MF中内容如下:

Manifest-Version: 1.0 
Ant-Version: Apache Ant 1.7.0 
Created-By: 1.5.0_09-b01 (Sun Microsystems Inc.) 
Implementation-Title: fee task 
Implementation-Version: 1.0 
Implementation-Vendor: Aspire 
Main-Class: com.aspire.cmgp.flowcontrol.server.FlowControlServer 
Class-Path: lib/cmgp-util-1.0.1.jar lib/commons-codec-1.3.jar lib/comm 
 ons-collections.jar lib/commons-dbcp-1.2.1.jar lib/commons-httpclient 
 .jar lib/commons-logging.jar lib/commons-pool-1.2.jar lib/dom4j.jar l 
 ib/log4j.jar lib/ojdbc14.jar 

这样在shell脚本中就不需要指定程序所需要的jar包了,也就不存在环境变量设置的恼人问题。比较正规的也是这么操作的。

这样在shell中就直接运行jar包就行了:java -jar 主程序.jar -Xmx1024m -Xms1024m -Xmn512m,

#!/bin/bash后追加

source /etc/profile
source ~/.bash_profile

测试下。。

#! /bin/sh
export JAVA_HOME=/usr/java/jdk1.6.0_18
export CLASSPATH=.:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar
for i in lib/*.jar; 
    do CLASSPATH=$i:${CLASSPATH} 
done
export CLASSPATH=.:${CLASSPATH}

java -cp ${CLASSPATH} main方法所在包名.main方法所在的类名

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持码农之家。

展开问题
码农之家
精选回答4:JavaScript运行原理分析

18小时8分钟前回答

JavaScript是一种基于对象的动态、弱类型脚本语言(以下简称JS),是一种解释型语言,和其他的编程语言不同,如java/C++等编译型语言,这些语言在代码执行前会进行通篇编译,先编译成字节码(机器码)。然后在执行。而JS不是这样做的,JS是不需要编译成中间码,而是可以直接在浏览器中运行,JS运行过程可分为两个阶段,编译和执行。(可参考你不知道的JS这本书),当JS控制器转到一段可执行的代码时(这段可执行代码就是编译阶段生成的),会创建与之对应的执行上下文(Excution Context简称EC)。执行上下文可以理解为执行环境(执行上下文只能由JS解释器创建,也只能由JS解释器使用,用户是不可以操作该‘对象'的)。

JS中的执行环境分为三类:

  • 全局环境:当JS引擎进入一个代码块时,如遇到<script>xxx</script>标签,就是进入一个全局执行环境
  • 函数环境:当一个函数被调用时,在函数内部就形成了一个函数执行环境
  • eval():把字符串单做JS代码执行,不推荐使用

在一段JS代码中可能会产生多个执行上下文,在JS中用栈这种数据结构来管理执行上下文,栈的特点是“先进后出,后进先出”,这种栈称之为函数调用栈。

执行上下文的特点

  • 栈底永远是全局执行上下文,有且仅有一个
  • 全局执行上下文只有在浏览器关闭时,才会弹出栈
  • 其他的执行上下文的数量没有限制
  • 栈顶永远是当前活动执行上下文,其余的都处于等待状态中,一旦执行完毕,立即弹出栈,然后控制权交回下一个执行上下文
  • 函数只有在每次被调用时,才会为其创建执行上下文,函数被声明时是没有的。

执行上下文可以形象的理解为一个普通的JS对象,一个执行上下文的生命周期大概包含两个阶段:

创建阶段

此阶段主要完成三件事件,1、创建变量对象 2、建立作用域链 3、确定this指向

执行阶段

此阶段主要完成变量赋值、函数调用、其他操作

变量对象(VO)的创建过程

  • 1、根据函数参数,创建并初始化arguments对象,给arguments对象添加属性"0","1","2","3"等属性,其初始值为undefined,并设置arguments.length值为实际传入参数的个数。
  • 2、查找function函数声明,在变量对象上添加属性,属性名就是函数名,属性值就是函数的引用值,如果已经存在同名的,则直接覆盖
  • 3、查找var变量声明(查找变量时,会把函数的参数等价于var声明,所以在VO中也会添加和参数名一样的属性,初始值也是undefined),在变量对象添加属性,属性名就是变量名,属性值是undefined,如果已经存在同名的,则不处理

如果存在同名标识符(函数、变量),则函数可以覆盖变量,函数的优先级高于变量

变量对象(OV)和激活对象(AO)是同一个东西,在不同时期的两种叫法。在创建时期叫变量对象,在执行时期叫激活对象

以如下代码为例

var g_name="tom";
var g_age=20;
function g_fn(num){
 var l_name="kity";
 var l_age=18;
 function l_fn(){
  console.log(g_name + '===' + l_name + '===' + num);
 }
}
g_fn(10);

编译阶段

当JS控制器转到这一段代码时,会创建一个执行上下文,G_EC

执行上下文的结构大概如下:

G_EC = {
 VO   : {},
 Scope_chain : [],
 this  : {}
}

/* VO的结构大概 */
VO = {
 g_name : undefined,
 g_age : undefined,
 g_fn : <函数在内存中引用值>
}

/* Scope_chain的大概结构如下 */
Scope_chain = [ G_EC.VO ] // 数组中第一个元素是当前执行上下文的VO,第二个是父执行上下文的VO,最后一个是全局执行上下文的VO,在执行阶段,会沿着这个作用域链一个一个的查找标识符,如果查到则返回,否知一直查找到全局执行上下文的VO

/* this */
this = undefined // 此时this的值是undefined

执行上下文一旦创建完毕,就立马被压入函数调用栈中,此时解释器会悄悄的做一件事情,就是给当前VO中的函数添加一个内部属性[[scope]],该属性指向上面的作用域链。

g_fn.scope = [ global_EC.VO ] // 该scope属性只能被JS解释器所使用,用户无法使用

执行阶段

一行一行执行代码,当遇到一个表达式时,就会去当前作用域链的中查找VO对象,如果找到则返回,如果找不到,则继续查找下一个VO对象,直至全局VO对象终止。

此阶段可以有变量赋值,函数调用等操作,当解释器遇到g_fn()时,就知道这是一个函数调用,然后立即为其创建一个函数执行上下文,fn_EC,该上下文fn_EC同样有两个阶段

分别是创建阶段和执行阶段。

在创建阶段,对于函数执行上下文,在创建变量对象时,会多创建一个arguments对象,然后为arguments对象添加属性:"0","1", "2"其初始值为undefined,

  • 查找function函数声明
  • 查找var变量声明
展开问题
码农之家
精选回答5:JavaScript判断浏览器运行环境的详细方法

16小时34分钟前回答

前言

看到标题,大家就能想起这个需求在很多项目上都能用到。我们部署在Web服务器上的前端应用,既可以用PC浏览器访问,也可以用手机浏览器访问,再加上现在智能设备的推广,我们甚至能在车载系统、穿戴设备和电视平台上访问。

设备的多样化让用户无处不在,有时候我们需要根据不同的浏览器运行环境做出对应的处理。浏览器是JavaScript的承载体,我们可以从浏览器上获取相关的信息,来进一步处理我们的业务逻辑。

然而浏览器品牌众多,有些浏览器使用的标准也不太一样,造就了难以统一的判断。下面我大概罗列一下常用的浏览器品牌和在什么情况下使用浏览器运行环境判断。浏览器相关统计数据可以参考这里。

国际五大浏览器品牌:按照全球使用率降序排列

  • Google Chrome:Windows、OSX、Linux、Android、iOS
  • Apple Safari:OSX、iOS
  • Mozilla Firefox:Windows、OSX、Linux、Android、iOS
  • ASA Opera:Windows、OSX、Linux、Android、iOS
  • Microsoft Internet Explorer或Microsoft Edge:Windows

国产常用浏览器品牌:按照国内使用率降序排列,普遍基于开源项目Chromium进行开发

  • 微信浏览器
  • QQ浏览器
  • UC浏览器
  • 2345浏览器
  • 搜狗浏览器
  • 猎豹浏览器
  • 遨游浏览器
  • 百度浏览器:百度在2019年04月30日宣布停止服务
  • 其他浏览器:很多很多,数不清,我就不列出来了

顺便吐槽一下这个不要脸的红芯浏览器,明明就是基于Chromium进行二次开发再套多一层外壳,还非得说自己开发的浏览器是世界第五大浏览器,偷吃不抹嘴,还是被眼尖的网友发现了。。。。

使用场景

  • 判断用户浏览器是桌面端还是移动端,显示对应的主题样式
  • 判断用户浏览器是Android端还是iOS端,跳转到对应的App下载链接
  • 判断用户浏览器是微信端还是H5端,调用微信分享或当前浏览器分享
  • 获取用户浏览器的内核和载体,用于统计用户设备平台分布区间
  • 获取用户浏览器的载体版本,用于提示更新信息
  • 其实还有很多使用场景,就不一一举例了

原理

针对处理一个这样的使用场景,其实有一个比较专业的名字,叫做浏览器指纹。我们上面谈到的需求也只是浏览器指纹方案里面的一小部分,而我们需要使用到的浏览器指纹就是UserAgent。

这个UserAgent是何方神圣呢,中文翻译过来就是用户代理。引用百度的定义,就是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU类型、浏览器载体及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。而这些信息也足够我们去判断浏览器运行环境了。

准备

目前网上很多解决方法都只是针对系统是否是桌面端还是移动端,Android端还是iOS端,部分浏览器载体的判断和获取等等,没有一个比较完美或者终极的解决方案。

因此我用了很多测试平台整理出一个比较全面的解决方案。这个方案包含浏览器系统及版本、浏览器平台、浏览器内核及版本、浏览器载体及版本、浏览器外壳及版本。

而此方案也是基于navigator.userAgent获取相关浏览器信息(如下),再通过系统、平台、内核、载体、外壳的特有字段进行归类统一,整理出一个完整的浏览器运行环境。

const ua = navigator.userAgent.toLowerCase();

// 输出
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"

浏览器信息:权重按照以下降序排列

  • 浏览器系统:所运行的操作系统,包含Windows、OSX、Linux、Android、iOS
  • 浏览器平台:所运行的设备平台,包含Desktop桌面端、Mobile移动端
  • 浏览器内核:浏览器渲染引擎,包含Webkit、Gecko、Presto、Trident
  • 浏览器载体:五大浏览器品牌,包含Chrome、Safari、Firefox、Opera、Iexplore/Edge
  • 浏览器外壳:基于五大浏览器品牌的内核进行开发,再套一层自研技术的外壳,如国内众多浏览器品牌

获取UserAgent是否包含字段:判断是否包含系统、平台、内核、载体、外壳的特有字段

const testUa = regexp => regexp.test(ua);

获取UserAgent对应字段的版本

const testVs = regexp => (ua.match(regexp) + "").replace(/[^0-9|_.]/ig, "").replace(/_/ig, ".");

方案

上述准备工作完成后,我们就按照权重(系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本)根据系统、平台、内核、载体、外壳的特有字段来归类统一浏览器运行环境。

系统+系统版本

// 系统
let system = "unknown";
if (testUa(/windows|win32|win64|wow32|wow64/ig)) {
 system = "windows"; // window系统
} else if (testUa(/macintosh|macintel/ig)) {
 system = "osx"; // osx系统
} else if (testUa(/x11/ig)) {
 system = "linux"; // linux系统
} else if (testUa(/android|adr/ig)) {
 system = "android"; // android系统
} else if (testUa(/ios|iphone|ipad|ipod|iwatch/ig)) {
 system = "ios"; // ios系统
}

// 系统版本
let systemVs = "unknown";
if (system === "windows") {
 if (testUa(/windows nt 5.0|windows 2000/ig)) {
  systemVs = "2000";
 } else if (testUa(/windows nt 5.1|windows xp/ig)) {
  systemVs = "xp";
 } else if (testUa(/windows nt 5.2|windows 2003/ig)) {
  systemVs = "2003";
 } else if (testUa(/windows nt 6.0|windows vista/ig)) {
  systemVs = "vista";
 } else if (testUa(/windows nt 6.1|windows 7/ig)) {
  systemVs = "7";
 } else if (testUa(/windows nt 6.2|windows 8/ig)) {
  systemVs = "8";
 } else if (testUa(/windows nt 6.3|windows 8.1/ig)) {
  systemVs = "8.1";
 } else if (testUa(/windows nt 10.0|windows 10/ig)) {
  systemVs = "10";
 }
} else if (system === "osx") {
 systemVs = testVs(/os x [\d._]+/ig);
} else if (system === "android") {
 systemVs = testVs(/android [\d._]+/ig);
} else if (system === "ios") {
 systemVs = testVs(/os [\d._]+/ig);
}

平台

let platform = "unknow";
if (system === "windows" || system === "osx" || system === "linux") {
 platform = "desktop"; // 桌面端
} else if (system === "android" || system === "ios" || testUa(/mobile/ig)) {
 platform = "mobile"; // 移动端
}

内核+载体

let engine = "unknow";
let supporter = "unknow";
if (testUa(/applewebkit/ig) && testUa(/safari/ig)) {
 engine = "webkit"; // webkit内核
 if (testUa(/edge/ig)) {
  supporter = "edge"; // edge浏览器
 } else if (testUa(/opr/ig)) {
  supporter = "opera"; // opera浏览器
 } else if (testUa(/chrome/ig)) {
  supporter = "chrome"; // chrome浏览器
 } else {
  supporter = "safari"; // safari浏览器
 }
} else if (testUa(/gecko/ig) && testUa(/firefox/ig)) {
 engine = "gecko"; // gecko内核
 supporter = "firefox"; // firefox浏览器
} else if (testUa(/presto/ig)) {
 engine = "presto"; // presto内核
 supporter = "opera"; // opera浏览器
} else if (testUa(/trident|compatible|msie/ig)) {
 engine = "trident"; // trident内核
 supporter = "iexplore"; // iexplore浏览器
}

内核版本+载体版本

// 内核版本
let engineVs = "unknow";
if (engine === "webkit") {
 engineVs = testVs(/applewebkit\/[\d.]+/ig);
} else if (engine === "gecko") {
 engineVs = testVs(/gecko\/[\d.]+/ig);
} else if (engine === "presto") {
 engineVs = testVs(/presto\/[\d.]+/ig);
} else if (engine === "trident") {
 engineVs = testVs(/trident\/[\d.]+/ig);
}

// 载体版本
let supporterVs = "unknow";
if (supporter === "chrome") {
 supporterVs = testVs(/chrome\/[\d.]+/ig);
} else if (supporter === "safari") {
 supporterVs = testVs(/version\/[\d.]+/ig);
} else if (supporter === "firefox") {
 supporterVs = testVs(/firefox\/[\d.]+/ig);
} else if (supporter === "opera") {
 supporterVs = testVs(/opr\/[\d.]+/ig);
} else if (supporter === "iexplore") {
 supporterVs = testVs(/(msie [\d.]+)|(rv:[\d.]+)/ig);
} else if (supporter === "edge") {
 supporterVs = testVs(/edge\/[\d.]+/ig);
}

外壳+外壳版本

let shell = "none";
let shellVs = "unknow";
if (testUa(/micromessenger/ig)) {
 shell = "wechat"; // 微信浏览器
 shellVs = testVs(/micromessenger\/[\d.]+/ig);
} else if (testUa(/qqbrowser/ig)) {
 shell = "qq"; // QQ浏览器
 shellVs = testVs(/qqbrowser\/[\d.]+/ig);
} else if (testUa(/ubrowser/ig)) {
 shell = "uc"; // UC浏览器
 shellVs = testVs(/ubrowser\/[\d.]+/ig);
} else if (testUa(/2345explorer/ig)) {
 shell = "2345"; // 2345浏览器
 shellVs = testVs(/2345explorer\/[\d.]+/ig);
} else if (testUa(/metasr/ig)) {
 shell = "sougou"; // 搜狗浏览器
} else if (testUa(/lbbrowser/ig)) {
 shell = "liebao"; // 猎豹浏览器
} else if (testUa(/maxthon/ig)) {
 shell = "maxthon"; // 遨游浏览器
 shellVs = testVs(/maxthon\/[\d.]+/ig);
} else if (testUa(/bidubrowser/ig)) {
 shell = "baidu"; // 百度浏览器
 shellVs = testVs(/bidubrowser [\d.]+/ig);
}

终极合体

根据以上的条件判断获得的变量如下,我们可以把它们合并成一个对象输出。这样就可以输出一个清晰的浏览器运行环境,后面想干嘛就干嘛了,多方便。

本文重点探究方案的可行性,没有过多考虑到代码的优化,所以条件判断使用得有些多,如果有什么方法能优化下代码,减少条件判断,可以在下方评论提个建议哟。

  • system:系统
  • systemVs:系统版本
  • platform:平台
  • engine:内核
  • engineVs:内核版本
  • supporter:载体
  • supporterVs:载体版本
  • shell:外壳
  • shellVs:外壳版本
function BrowserType() {
 const ua = navigator.userAgent.toLowerCase();
 const testUa = regexp => regexp.test(ua);
 const testVs = regexp => (ua.match(regexp) + "").replace(/[^0-9|_.]/ig, "").replace(/_/ig, ".");
 // 接上以上if...else条件判断
 // ......
 // 获取到system、systemVs、platform、engine、engineVs、supporter、supporterVs、shell、shellVs
 return Object.assign({
  engine, // webkit gecko presto trident
  engineVs,
  platform, // desktop mobile
  supporter, // chrome safari firefox opera iexplore edge
  supporterVs,
  system, // windows osx linux android ios
  systemVs
 }, shell === "none" ? {} : {
  shell, // wechat qq uc 2345 sougou liebao maxthon baidu
  shellVs
 });
}

在控制台执行BrowserType(),该有的都出来了,哈哈!源码详情请戳这里,喜欢的可以点个赞支持下,谢谢。

JavaScript判断浏览器运行环境的详细方法

结语

写到最后总结得差不多了,后续如果我想起还有哪些判断浏览器运行环境终极方案遗漏的,会继续在这篇文章上补全,同时也希望各位倔友对文章里的要点进行补充或者提出自己的见解。欢迎在下方进行评论或补充喔,喜欢的点个赞或收个藏,保证你在开发时用得上。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对码农之家的支持。

展开问题

参考资料

  • Java程序设计与案例

    Java程序设计与案例

    Java程序设计与案例 作者:刘宝林主编;胡博,谢锋波编 出版时间:2004-11-01 丛编项:普通高等教育十五国家级规划教材

    大小:26.57MBJava

    立即下载
  • 菜鸟成长之路:Java程序员职场全攻略

    菜鸟成长之路:Java程序员职场全攻略

    《菜鸟成长之路:Java程序员职场全攻略》 内容简介:以包罗万象的IT这个大江湖为背景,将Java职场中从入门前的学校菜鸟到成长为技术大牛的过程展现给读者,内容饱满但又不失趣味性。在《

    大小:51.4 MBJava职场

    立即下载
  • Java程序员面试笔试真题库

    Java程序员面试笔试真题库

    《Java程序员面试笔试真题库》 针对当前各大IT企业面试笔试中的特性与侧重点,精心挑选了近3年来18家IT企业的面试笔试真题,这些企业涉及业务包括系统软件、搜索引擎、电子商务、手机A

    大小:291.9 MBJava面试

    立即下载
  • Java程序员面试笔试宝典

    Java程序员面试笔试宝典

    本书是程序员求职面试笔试必备图书,以独特的视角对面试过程中求职者存在的各类问题进行了深度剖析,是一本适合计算机相关专业毕业生阅读的求职指导用书

    大小:73.4 MBJava面试

    立即下载
  • Java程序员修炼之道

    Java程序员修炼之道

    伴随着关键服务平台及其生态体系的与时俱进,Java技术性一直在迅速往前发展趋势。《 Java程序员修炼之道 》包含了Java7的**特性和Java开发设计的核心技术,对当今很多开源系统技术性共存,

    大小:8.7 MBJava

    立即下载

更多回答

29小时53分钟前回答

Java程序运行依赖操作系统吗

在Java平台的结构中, Java虚拟机(JVM) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。它的下方是移植接口,移植接口由两部分组成:适配器和Java操作系统, 其中依赖于平台的部分称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在JVM 的上方是Java的基本类库和扩展类库以及它们的API, 利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java 的平台无关性。 1、java程序是跨平台的,不依赖任何操作系统,只依赖JVM(java虚拟机)而已, 只要操作系统(Windows也得,Linux也得……

5小时6分钟前回答

使用maven运行Java Main的三种方法解析

maven使用exec插件运行java main方法,以下是3种不同的操作方式。 一、从命令行运行 1、运行前先编译代码,exec:java不会自动编译代码,你需要手动执行mvn compile来完成编译。 mvn compile 2、编译完成后,执行exec运行main方法。 不需要传递参数: mvn exec:java -Dexec.mainClass="com.vineetmanohar.module.Main" 需要传递参数: mvn exec:java -Dexec.mainClass="com.vineetmanohar.module.Main" -Dexec.args="arg0 arg1 arg2" 指定对classpath的运行时依赖: mvn exec:java -Dexec.mainClass="com.vineetmanohar.module.Main" -Dexec.classpathScope=runtime 二、在pom.xml中指定某个阶段执行 build plugins plugin groupIdorg.codehaus.mojo/groupId artifactIdexec-maven-plugin/artifactId version1.1.1/version executions exec……

31小时16分钟前回答

javascript按顺序加载运行js方法

首先如果大家对JS动态加载有不理解的地方可以参阅: javascript动态加载实现方法 动态加载JS文件的三种方法 如何你的 script 上没有任何 异步,阻塞 等标注: 浏览器会异步加载 javascript 文件,但是会按照引用文件中的书写顺序从上到下执行解析 javascript Defer属性标记 defer是html4.0中定义的,该属性使得浏览器能延迟脚本的执行,等文档完成解析完成后会按照他们在文档出现顺序再去下载解析。 也就是说defer属性的 script 就类似于将 script 放在body中的加载 效果一致. 但是defer属性在各个浏览器中支持程度有点不同,就是说,有的浏览器不完全支持. Async属性标注 async是HTML5新增的属性, 大部分先进支持该属性的. 该……