共计 9500 个字符,预计需要花费 24 分钟才能阅读完成。
源码解读:包装类型
因为 java 是面向对象的,很多时候咱们须要用到的是对象而非根本数据类型。因而咱们就在每个根本数据类型上都建了一个包装类型,他们具备对象的性质,并增加有属性和办法。这里咱们间接开始对包装类源码中的一些独特之处做出解析。
在解读包装类的源码之前咱们无妨先谈一谈 java 中与包装类型相干的语法糖[语法糖就是对现有语法的一个封装, 通过编译器实现,可通过 javap/jad 等反编译工具察看到]
反编译:执行 java 之前,咱们首先须要将 java 文件编译为 class 字节码文件 (javac xx.java),此时编译生成的字节码文件其实就是一个二进制文件,咱们用文本编辑器将它关上能够察看到,外面寄存了许多重要数据信息,具体能够参考相干文档理解这些二进制信息代表的含意。但问题是咱们要想浏览这些二进制信息无疑是艰难且繁琐的,这时候就能够 通过反编译工具将字节码文件转为咱们能够了解的信息。其中 JDK 有给咱们自带的反编译工具
javap
,但它生成的信息更靠近字节码,能够让咱们更业余的理解到 class 文件外部的具体细节,jad 则是间接生成咱们熟知的 java 高级语法,在后文能够看到。
拆装箱
拆装箱就是一个简略常见的语法糖。咱们在程序中间接对包装类以及根底数据类型操作时,在程序编译过程中编译器就会主动进行拆装箱操作。咱们这里能够先通过 jad 工具进行反编译以更加简略直观的形式察看的拆装箱后果。
/**
* valueOf(): 装箱
* intValue: 拆箱
*/
Integer x1 = new Integer(101);
int x2 = 101;
Integer x3 = 101;
// 反编译后果:// Method java/lang/Integer.intValue:()I;包装类主动拆箱
System.out.println(x1+x2);
// Method java/lang/Integer.intValue:()I
System.out.println(x1==x2);
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 根本类型主动装箱
System.out.println(x1.equals(x2));
System.out.println(x1==x3);
jad 反编译后果:
Integer x1 = new Integer(101);
int x2 = 101;
Integer x3 = Integer.valueOf(101);
System.out.println(x1.intValue() + x2);
System.out.println(x1.intValue() == x2);
System.out.println(x1.equals(Integer.valueOf(x2)));
System.out.println(x1 == x3);
察看发现 java 编译过程的确为咱们的代码增加了不少的 ” 额定信息 ”。这里其实就是所谓的主动拆装箱了。
字段剖析
在看源码中具体的办法实现之前,咱们先看一下外面蕴含的一些重要字段,先从简略的 Integer 开始
每个保障类中都存有一个根本数据类型值,它是实现拆箱操作的要害。
在其余包装类中都能发现相似的字段:最大最小值,类对象,对应根本数据类型值,位大小等。
/**
* A constant holding the minimum value an {@code int} can
* have, -2<sup>31</sup>.
*/
@Native public static final int MIN_VALUE = 0x80000000;
/**
* A constant holding the maximum value an {@code int} can
* have, 2<sup>31</sup>-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
/**
* The {@code Class} instance representing the primitive type
* {@code int}.
*
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
/**
* All possible chars for representing a number as a String
*/
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
/**
* The number of bits used to represent an {@code int} value in two's
* complement binary form.
*
* @since 1.5
*/
@Native public static final int SIZE = 32;
再比方 Short 类:
包装类的缓存机制
咱们先来看一下这个办法:
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
这个办法置信咱们大家都不生疏,在装箱过程中应用的就是该办法。但在理论装箱之前还做了一个判断,其中的要害类 IntegerCache 的命名其实就曾经为咱们解释了它的作用。这里咱们先点进去看一下它外部的实现。
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch(NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
代码并不难懂,后面的动态代码块做了个范畴的调节性能,重点察看一下代码段
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high")
它的意思咱们认真想想就能明确无非就是从 "java.lang.Integer.IntegerCache.high"
这个“中央”获取到要用的参数。它与咱们相熟的 System.getProperties()
办法相似,但要说出它们的具体区别还是得从源码角度深入分析。
首先咱们要晓得 JVM 的执行逻辑,程序启动时 JVM 会主动调用 initializeSystemClass
办法来初始化 System 类
在 java.lang.System
中咱们能够看到它的执行逻辑:
/**
* Initialize the system class. Called after thread initialization.
*/
private static void initializeSystemClass() {
// VM might invoke JNU_NewStringPlatform() to set those encoding
// sensitive properties (user.home, user.name, boot.class.path, etc.)
// during "props" initialization, in which it may need access, via
// System.getProperty(), to the related system encoding property that
// have been initialized (put into "props") at early stage of the
// initialization. So make sure the "props" is available at the
// very beginning of the initialization and all system properties to
// be put into it directly.
props = new Properties();
initProperties(props); // initialized by the VM
// There are certain system configurations that may be controlled by
// VM options such as the maximum amount of direct memory and
// Integer cache size used to support the object identity semantics
// of autoboxing. Typically, the library will obtain these values
// from the properties set by the VM. If the properties are for
// internal implementation use only, these properties should be
// removed from the system properties.
//
// See java.lang.Integer.IntegerCache and the
// sun.misc.VM.saveAndRemoveProperties method for example.
//
// Save a private copy of the system properties object that
// can only be accessed by the internal implementation. Remove
// certain system properties that are not intended for public access.
sun.misc.VM.saveAndRemoveProperties(props);
lineSeparator = props.getProperty("line.separator");
sun.misc.Version.init();
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
// Load the zip library now in order to keep java.util.zip.ZipFile
// from trying to use itself to load this library later.
loadLibrary("zip");
// Setup Java signal handlers for HUP, TERM, and INT (where available).
Terminator.setup();
// Initialize any miscellenous operating system settings that need to be
// set for the class libraries. Currently this is no-op everywhere except
// for Windows where the process-wide error mode is set before the java.io
// classes are used.
sun.misc.VM.initializeOSEnvironment();
// The main thread is not added to its thread group in the same
// way as other threads; we must do it ourselves here.
Thread current = Thread.currentThread();
current.getThreadGroup().add(current);
// register shared secrets
setJavaLangAccess();
// Subsystems that are invoked during initialization can invoke
// sun.misc.VM.isBooted() in order to avoid doing things that should
// wait until the application class loader has been set up.
// IMPORTANT: Ensure that this remains the last initialization action!
sun.misc.VM.booted();}
具体实现细节开发者曾经在代码中给出了具体的解释,这里在对细节作简要阐明:
initProperties(props);
初始化参数,咱们点进该办法能够看到它是个本地办法,本地办法实现从操作系统中获取全局属性
native 关键字作用?
首先咱们晓得 java 是跨平台的,作为跨平台的代价就是 java 对于底层操作系统的管制远不如 c、c++ 这些。这个时候 java 必须凋谢一个“通道”实现 java 与操作系统之间的“沟通”,native 便是该入口。当 JVM 要调用一个 native 办法时,JVM 会从本地库中调用该办法,同时该办法通过 c 语言实现,c 语言实现时调用 JNI(java native interface)
private static native Properties initProperties(Properties props);
接下来是sun.misc.VM.saveAndRemoveProperties(props);
同样点进去看一下源码
public static void saveAndRemoveProperties(Properties var0) {if (booted) {throw new IllegalStateException("System initialization has completed");
} else {savedProps.putAll(var0);
String var1 = (String)var0.remove("sun.nio.MaxDirectMemorySize");
if (var1 != null) {if (var1.equals("-1")) {directMemory = Runtime.getRuntime().maxMemory();} else {long var2 = Long.parseLong(var1);
if (var2 > -1L) {directMemory = var2;}
}
}
var1 = (String)var0.remove("sun.nio.PageAlignDirectMemory");
if ("true".equals(var1)) {pageAlignDirectMemory = true;}
var1 = var0.getProperty("sun.lang.ClassLoader.allowArraySyntax");
allowArraySyntax = var1 == null ? defaultAllowArraySyntax : Boolean.parseBoolean(var1);
var0.remove("java.lang.Integer.IntegerCache.high");
var0.remove("sun.zip.disableMemoryMapping");
var0.remove("sun.java.launcher.diag");
var0.remove("sun.cds.enableSharedLookupCache");
}
}
认真看一下这串代码,它的理论实现的是不是这样一个过程:
savedProps.putAll(var0);
:零碎参数备份到 VM 类中var0.remove()
: 将零碎类中某些指定参数移除directMemory|pageAlignDirectMemory|allowArraySyntax
:对 VM 类中的特定属性赋值
咱们想一想这样做的目标是什么?
开发者通知咱们的是:保留只能由外部实现拜访的零碎属性对象的公有正本,删除某些不用于公共拜访的零碎属性
// Save a private copy of the system properties object that
// can only be accessed by the internal implementation. Remove
// certain system properties that are not intended for public access.
sun.misc.VM.saveAndRemoveProperties(props);
它这样设计次要还是为了平安思考和隔离角度思考,防止 JVM 的外部行为受到运行时用户代码对 System.properties
的批改所烦扰。
所以咱们在给 JVM 设置参数的时候往往写在运行时的 VM options 上。例如这里的java.lang.Integer.IntegerCache.high
咱们能够在 VM options 上写-Djava.lang.Integer.IntegerCache.high=xxx
这里能够本人试一试,看一下有没有成果:
Integer x1 = 222;
Integer x2 = 222;
System.out.println(VM.getSavedProperty("java.lang.Integer.IntegerCache.high"));
System.out.println(System.getProperty("java.lang.Integer.IntegerCache.high"));
System.out.println(x1 == x2);
// 返回后果:// 300
// null
// true
initializeSystemClass
办法看到这里其实曾经能够解释咱们一开始的遇到的问题了,即 sun.misc.VM.getSavedProperty()
办法与 System.getProperties()
办法的区别。
至于前面代码块的内容与本篇内容无关,在这里不做过多解释。
咱们持续回到 IntegerCache
类,前面的内容果然和咱们一开始想的统一,它的实现就是相似于“缓存”的机制。该类在加载之初就曾经主动实例化了默认范畴 [-128,127] 的 Integer 对象。别离存储在 cache[]数组中。
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
这样一来咱们在该范畴内所获取的同一个指定值,是不是都是同一个对象了。
可是这么做又有什么益处呢?
它的作用其实就是相似于缓存的作用。咱们在创立大量 Integer 对象时能够缩小内存的调配(大量 Integer 对象就在指定范畴内创立)。而内存调配少了,JVM gc 次数是不是就少了,gc 次数少了,程序执行效率天然也就进步了。
回过头来咱们再看一下 Short、Long、Byte、Character 类,他们同样都有缓存机制,然而并不能手动调节缓存的范畴
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
public static Character valueOf(char c) {if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}