基于 jdk8 https://docs.oracle.com/javase/8/docs/api/index.html
异常簇
概览
检查异常(Checked Exception)
检查异常是指必须要被手动处理的异常,手动处理的方式有两种,一种是使用try...catch
捕获,第二种是使用throw
继续抛出。
抑制异常(Suppressed Exception)
try...catch...finally
场景中,可能会在三个语句块中分别抛出3个不同的异常,那么只有一个异常会被抛出,异常的优先级为finally>catch>try。也就是说如果try、catch、finally语句块中分别抛出异常A、B、C,那么只有C会被抛出,A和B就被抑制。如果try、catch分别抛出异常A、B,则只有B会被抛出。此时A被抑制。
在JDK7中伴随着try-with-resources
语法,Throwable开始允许包含被抑制异常信息。可以通过Throwable.addSuppressed(t)
向Throwable中添加被抑制的异常,通过Throwable.getSupressed()
获取该Throwable抑制的其他异常。
这和JDK1.4中引入的异常链不同,异常链中的异常是因果关系,表达的是异常的连锁反应;抑制异常中的异常没有任何关系,表达的是多个异常的返回结果。
Throwable
是Java语言中所有异常的父类。仅Throwable或其子类能被JVM或Java抛出,同样只有Throwable或其子类能有被用在catch语句中。
Throwable的所有子类中,除了RuntimeException和Error的子类不是检查异常,其他异常都是检查异常。
Throwable包含的信息有:
- new 异常时当前线程的堆栈快照
- 一个简要的异常信息
- 该异常抑制(suppress)的其他异常
- 异常链
如果要建立异常链,可以通过构造方法,或者initCause(t)
方法创建异常间的联系。
public String getMessage()
简要描述异常的详情,用于方便定位异常的具体原因
private String detailMessage;
public synchronized Throwable getCause()
保存诱发此结果异常的原因异常。递归getCause()
即可形成异常链。
默认cause=this,所以如果cause==this,表示没有初始化过原因异常。
initCause()
方法中会判断cause是否不等于this来保证只能被初始化一次。
private Throwable cause = this;
public void printStackTrace()
打印这个异常,和这个异常抑制的所有异常,和这个异常的所有异常链中的堆栈信息,格式一般如下(注意缩进规则,抑制异常也可以有异常链):
Exception in thread "main" java.lang.Exception: Main block
at Foo3.main(Foo3.java:7)
Suppressed: Resource$CloseFailException: Resource ID = 2
at Foo3.main(Foo3.java:5)
Caused by: java.lang.Exception: Rats, you caught me
at Resource2$CloseFailException.<init>(Resource2.java:45)
... 2 more
Caused by: java.lang.Exception: I did it
at Foo3.main(Foo3.java:8)
/*
* 表示空堆栈,在所有空堆栈的异常中共享此字段
*/
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
/*
* 调用本地方法来填充堆栈
*/
private native Throwable fillInStackTrace(int dummy);
public void setStackTrace(StackTraceElement[] stackTrace)
可以手动设置异常的堆栈信息,一般用于给RPC框架等高级系统使用,来填充client端的异常堆栈。
public final synchronized void addSuppressed(Throwable exception)
添加被此异常抑制的异常,此方法是线程安全的,通常由 try-with-resources 语句调用(自动和隐式)。
可以通过构造函数禁用抑制行为。
Exception
Exception类的任何非RuntimeException子类表示需要应用程序检查的异常。
- ReflectiveOperationException:由反射操作抛出的所有异常的父类
- NoSuchMethodException:当反射找不到特定方法时抛出
- ClassNotFoundException:当应用尝试通过类的字符串名称加载类,但反射找不到指定名称的类定义时抛出:
- 使用Class类的forName方法
- 使用ClassLoader类的findSystemClass方法
- 使用ClassLoader类的loadClass方法
- IllegalAccessExcepiton:当通过反射创建实例、设置或获取字段、调用实例方法,但没有访问权限时抛出。可以通过
setAccessible(true)
来获得权限 - InstantiationException:当应用调用Class类的newInstance方法创建实例,但实例化失败时抛出,实例化失败的原因包括但不限于:
- Class是抽象类、接口、数组、原始类型、或void的类型
- Class表示的类没有无参构造函数
- NoSuchFieldException:当反射时该类没有指定名称的field时抛出
- InterruptedException:当线程处于等待、睡眠或其他方式被占用,并且被其他线程中断时自动抛出。运行中的线程可以通过
Thread.interrupted()
检测当前线程来判断是否已经被中断 - CloneNotSupportedException:调用clone方法,但对象未实现Cloneable接口时抛出。重写方法也可以抛出此异常表示对象不能被克隆
- RuntimeException:未检查异常的父类。此类异常一般由代码不健壮引起,可以通过完善编码来避免。
- ArrayStoreException:向数组插入非此数组类型的对象时抛出。
Object x[]=new String[2];x[0]=new Integer(0);
- NegativeArraySizeException:当尝试创建数组时指定的数组大小为负数时抛出
- ArithmeticException:执行错误的算术时抛出,比如除0
- IllegalStateException:表示在不适当的时候调用了方法。比如在对象还未初始化完方法就被调用时可以抛出
- TypeNotPresentException:该异常与ClassNotFoundException的不同是该异常非检查异常
- EnumConstantNotPresentException:通过名称访问不存在的枚举值是抛出,该异常可能在反射时被抛出
- IndexOutOfBoundsException:索引超出范围时抛出
- StringIndexOutOfBoundsException:访问字符串索引位置超过字符串范围时抛出
- ArrayIndexOutOfBoundsException:访问数组索引位置超过索引范围时抛出
- NullPointerException:当使用对象,但对象为null时抛出
- UnsupportedOperationException:不支持请求的操作,一般表示不同的实现类不支持某个父类方法,此异常是Java Collections Framework的成员
- IllegalMonitorStateException:在未获得的监视器上调用wait或notify方法时抛出
- ClassCastException:强制转换对象类型不一致时抛出,比如
(String)new Integer(0)
- SecurityException:由安全管理器抛出,表示有安全违规行为,比如用户自己定义了一个与jdk内置同名的包并使用包下的类时。比如如果定义了一个
java.lang
包,并在这个包下定义了一个类,加载和使用这个类就会抛出SecurityException - IllegalArgumentException:参数非法时抛出
- NumberFormatException:解析非数字格式的字符串为数字时抛出
- IllegalThreadStateException:当线程未处于请求的适当状态抛出,例如参见Thread类的suspend和resume方法
- ArrayStoreException:向数组插入非此数组类型的对象时抛出。
Error
Error 表示不应该由应用程序捕获的严重错误,及时发生也无法恢复,通常由JVM运行错误引起。ThreadDeath 错误虽然是一种正常的情况,但也不应该手动捕获。Error及其子类被视为未检查异常。
- AssertionError:使用assert关键字断言失败时抛出
- ThreadDeath:当调用
Thread.stop
(deprecated)方法时,会在被停止的线程抛出ThreadDeath错误。仅当在需要的时候才应该捕获此错误,捕获后一定要将其重新抛出,保证让线程确实死亡。未捕获的ThreadDeath不会在顶级异常处理程序中打印出消息。虽然线程死亡是正常发生的情况,但因为许多程序都会直接捕获Exception然后丢弃,所以此异常的父类是Error。 - VirtualMachineError:当JVM出现错误,或者系统资源耗尽的时候抛出
- InternalError:表示JVM发生了一些意外的错误
- StackOverflowError:当递归深度超过栈大小时抛出,可以通过不停止的递归来触发
- OutOfMemoryError:当JVM由于内存不足无法创建对象,并且GC无法提供更多内存时抛出
- UnknownError:当JVM发生未知且严重的异常时抛出
- LinkageError:此错误表示,当A类对B类有某种依赖,但是在A类已经编译完成后,B类发生了不兼容A类的变化
- ClassCircularityError:当JVM检测到循环依赖时抛出。JVM通过队列来检测循环依赖,当队列中的出现重复时认为出现循环
- NoClassDefFoundError:如果JVM或ClassLoader尝试加载类(正常的方法调用,或new新实例)但找不到类定义时抛出。这种情况的发生由于编译时能够搜索到类定义存在,但运行时无法找到类定义
- BootstrapMethodError:当invokedynamic指令未找到其引导的方法,或其引导的方法无法为目标方法类型提供call site
- VerifyError:当JVM的验证器检测到java文件虽然格式正确,但是包含内部不一致或安全问题时抛出
- UnsatisfiedLinkError:JVM找不到native本地方法的定义时抛出
- ExceptionInInitializerError:表示JVM在初始化静态变量时发生了异常
- ClassFormatError:JVM读取一个格式错误的java文件时抛出
- UnsupportedClassVersionError:当java文件中的主要和次要版本号不受当前JVM支持时抛出
- IncompatibleClassChangeError:当前执行的方法所依赖的类的定义被改变时抛出
- AbstractMethodError:调用抽象方法时抛出。正常情况下这个错误可以被编译器捕获;但是当编译后的类定义发生了更改时可能在运行期间发生
- InstantiationError:当尝试创建抽象类或接口的实例时抛出。正常情况下这个错误可以被编译器捕获;但是当编译后的类定义发生了更改时可能在运行期间发生
- NoSuchFieldError:当尝试使用不存在的字段时抛出。正常情况下这个错误可以被编译器捕获;但是当编译后的类定义发生了更改时可能在运行期间发生
- NoSuchMethodError:当尝试使用不存在的方法时抛出。正常情况下这个错误可以被编译器捕获;但是当编译后的类定义发生了更改时可能在运行期间发生
- IllegalAccessError:当尝试访问没有访问权限的属性时抛出。正常情况下这个错误可以被编译器捕获;但是当编译后的类定义发生了更改时可能在运行期间发生
StackTraceElement
表示由Throwable.getStackTrace()
返回的堆栈中的一个栈帧。除了堆栈顶部的帧外,所有帧都表示一个方法调用。堆栈顶部的帧表示生成堆栈跟踪的执行点。StackTraceElement一般由虚拟机创建和初始化。
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;
字符及字符串
CharSequence
CharSequence 用来表示字符序列。此接口提供对不同类型字符序列的统一只读访问。一个字符值表示Basic Multilingual Plane (BMP) 字符集中定义的一个字符或UTF编码方式中的一个代理位surrogate。
此接口没有约定equals和hashCode方法的具体实现,不能保证每个此接口实现的正确性,因此不建议在Set或Map的键中使用。
String
String类表示字符串,Java程序中的字符串字面量也是String实例。创建后无法修改,只能被回收。由于其不可变性,String会存储hash码用于提高效率。由于默认的UTF-16编码在使用英语的国家会浪费一半的内存空间,因此在JDK9之后其内部存储由char数组改为byte数组,动态适应英文编码和UTF16编码。其内部定义的两个关键属性:
private final char value[];
private int hash; // Default to 0
public int length() {
return value.length;
}
/** JDK9+ **/
private final byte value[];
public int length() {
return value.length >> coder(); //当使用UTF16编码方式时,2字节一个代码单元,因此长度需要除2
}
byte coder() {
return COMPACT_STRINGS ? coder : UTF16;
}
@Native static final byte LATIN1 = 0;
@Native static final byte UTF16 = 1;
/** JDK9+ end **/
Stirng类包括用于检索序列中的单个字符、比较字符串、搜索字符串、提取子串以及转换大小写副本的方法。Java语言为字符串拼接运算符(+)以及其他对象转换为字符串提供特殊支持。字符串连接是通过StringBuilder(JDK1.5之前是StringBuffer)类的append方法实现。字符串转换通过toString方法实现,因为toString方法在Object类中定义。
String内字符串默认UTF-16编码格式,其中补充字符由Unicode代理对表示。索引值指的是字符的代码单元,因此补充字符占字符串中的两个位置。String类中除了有用于处理Unicode代码单元的方法外,还提供了用于处理Unicode代码点的方法。
在内存中,字符串分为字面量和变量。字面量在字符串常量池中存储;变量(new出来的对象)和其他对象一样在堆中存储。字符串常量池在JDK6及之前版本在永久代空间,JDK7之后字符串常量池放到了堆中。JDK8之前Stirng使用char数组存储数据,JDK9开始改为使用byte数组存储数据。
String类中的native Stirng intern()
方法会在常量池查找,若不存在就会将当前字符串放入常量池中,并返回当地字符串地址引用。如果存在就返回字符串常量池那个字符串地址。在JDK1.6时,如果串池中有返回已有的串池中的对象的地址,如果没有,会把此对象复制一份,放入字符串常量池,并返回池中的对象地址;JDK1.7开始,如果没有,则会把堆中对象的引用地址复制一份,放入字符串常量池。
G1回收器中,会对堆内存活的String对象进行去重操作,以减少内存使用。
关于String更多知识,相关阅读:字符串常量池
Java中数组的最大长度
Java中可以创建数组的最大长度并不是一个固定值,大致是用Java中int的最大值减去Java对象header占内存的字数量。由于会进行内存字节对齐,所以jdk中通常使用字为单位表示对象的内存大小,以方便进行其他计算。计算过程在arrayOop.hpp
中。
//hotspot/src/share/vm/oops/arrayOop.hpp
/* 返回JDK中定义的原始类型数组的最大长度。 这个长度可以确保调用
* typeArrayOop::object_size(scale, length, header_size) 时不会发生溢出。
* 并且也需要确保在32位平台上数组长度转换到以Byte为单位表示内存大小时不会溢出size_t类型。
*/
static int32_t max_array_length(BasicType type) {
const size_t max_element_words_per_size_t =
align_size_down((SIZE_MAX/HeapWordSize - header_size(type)), MinObjAlignment);
const size_t max_elements_per_size_t =
HeapWordSize * max_element_words_per_size_t / type2aelembytes(type);
//SIZE_MAX等于uint64最大值,是一个巨大的数字,所以通常会进入到这个if分支
if ((size_t)max_jint < max_elements_per_size_t) {
/* 这里之所以不能返回max_jint,是因为有一些代码(例如CollectedHeap, Klass::oop_oop_iterate())
* 使用int类型来传递以字为单位表示的对象内存大小。所以需要避免加上header内存大小时int类型溢出。
* 详见 CRs 4718400 and 7110613.
*/
return align_size_down(max_jint - header_size(type), MinObjAlignment);
}
return (int32_t)max_elements_per_size_t;
}
/* 这个方法只能被当做其他方法的入参调用,不然不会被进行常量折叠优化。
* 返回给定类型的数组对象的header大小,大小以字为单位表示。
*/
static int header_size(BasicType type) {
size_t typesize_in_bytes = header_size_in_bytes();
return (int)(Universe::element_type_should_be_aligned(type)
? align_object_offset(typesize_in_bytes/HeapWordSize)
: typesize_in_bytes/HeapWordSize);
}
HeapWordSize、字节对齐工具方法、和BasicType的定义在globalDefinitions.hpp
中
//hotspot/src/share/vm/utilities/globalDefinitions.hpp
/* 表示一字长的结构。因此`HeapWord *`是一个堆内的通用指针。
* 在堆内的对象大小以字为单位计算。所以可以用以下方法遍历堆内对象:
* HeapWord * hw;
* hw+=oop(hw)->word_size();
*/
class HeapWord {
private:
char* i;
/* 省略其他部分 */
};
/*
* 64位系统中一个字长为8字节,所以HeapWorkSize=8。32位系统中等于4
*/
const int HeapWordSize = sizeof(HeapWord);
/*
* 字节对齐的工具类
*/
#define align_size_up_(size, alignment) (((size) + ((alignment) - 1)) & ~((alignment) - 1))
inline intptr_t align_size_up(intptr_t size, intptr_t alignment) {
return align_size_up_(size, alignment);
}
#define align_size_down_(size, alignment) ((size) & ~((alignment) - 1))
inline intptr_t align_size_down(intptr_t size, intptr_t alignment) {
return align_size_down_(size, alignment);
}
// NOTE: replicated in SA in vm/agent/sun/jvm/hotspot/runtime/BasicType.java
enum BasicType {
/* 省略其他部分 */
T_OBJECT = 12,
T_ARRAY = 13,
/* 省略其他部分 */
};
对象对齐相关属性*Alignment
,都是以HeapWord为单位,也就是以字为单位。MinObjAlignment的初始化在arguments.cpp
中:
//hotspot/src/share/vm/runtime/arguments.cpp
void set_object_alignment() {
MinObjAlignmentInBytes = ObjectAlignmentInBytes;
MinObjAlignment = MinObjAlignmentInBytes / HeapWordSize;
/* 省略其他部分 */
}
而这个ObjectAlignmentInBytes则是VM.java
中的代码:
//hotspot/agent/src/share/classes/sun/jvm/hotspot/runtime/VM.java
public int getObjectAlignmentInBytes() {
if (objectAlignmentInBytes == 0) {
Flag flag = getCommandLineFlag("ObjectAlignmentInBytes");
objectAlignmentInBytes = (flag == null) ? 8 : (int)flag.getIntx();
}
return objectAlignmentInBytes;
}
当然一般JVM在初始化的时候会默认给ObjectAlignmentInBytes进行赋值,在globals.hpp
中:
//hotspot/src/share/vm/runtime/globals.hpp
lp64_product(intx, ObjectAlignmentInBytes, 8, "Default object alignment in bytes, 8 is minimum")
现在知道了Java中可以申请数组的最大长度是如何计算来的了,在实践中64位关闭指针压缩时为Integer.MAX_VALUE-3
,开启后为Integer.MAX_VALUE-2
。
AbstractStringBuilder
可修改的字符串,StringBuffer和StringBuilder的父类,StringBuffer和StringBuilder的绝大部分方法都是在此抽象类中具体实现。由于其可变性,在扩容时会预留出额外的空间,所以value.length并不等于实际长度,因此内部存储了count变量缓存字符长度。同String一样在JDK9以后使用byte数组存储数据。内部定义了两个关键属性:
char[] value;
int count;
/** JDK9+ **/
byte[] value;
byte coder;
/** JDK9+ end **/
扩容时,会优先尝试扩容(2*value.length)+2
,如果优先扩容失败,说明超过了最大数组容量Integer.MAX_LENGTH-8
或发生了整型溢出,此时如果所需的最小容量小于最大数组容量,会一步到位扩容到最大数组容量,否则冒着OOM的风险直接申请所需最小容量。
StringBuffer相比StringBuilder只是在每个方法上都加了synchronized关键字。
CharacterData
CharacterData抽象类定义了处理单个字符的基本方法,在其不同子类中实现了Unicode字符集不同Plane段的处理。一般只有在系统类Character中被使用,自己使用可以通过CharacterData.of(codePoint)
方法获取当前代码点所处Plane的实现类。
static final CharacterData of(int ch) {
if (ch >>> 8 == 0) { // fast-path
return CharacterDataLatin1.instance;
} else {
switch(ch >>> 16) { //plane 00-16
case(0):
return CharacterData00.instance;
case(1):
return CharacterData01.instance;
case(2):
return CharacterData02.instance;
case(14):
return CharacterData0E.instance;
case(15): // Private Use
case(16): // Private Use
return CharacterDataPrivateUse.instance;
default:
return CharacterDataUndefined.instance;
}
}
}
字符工具类
- ConditionalSpecialCasing
- String.toLower/UpperCase() 的工具类。处理带有条件的特殊大小写。无条件的大小写映射在Character.toLower/UpperCase()中处理
- CharacterName
- 目前不知道是做什么的
- StringCoding
- 字符串编码解码工具
基本数据类型和数学
Number类型
是代表所有原始数据类型byte、double、float、int、long、short的包装类型的超类。其定义了包装类的原始数据类型转换方法。数据类型转换可能丢失精度和位宽。数据类型对象的valueOf方法会优先返回缓存中的对象,而parseXX方法都会创建一个新对象。每个包装类型各包含一个private final
修饰的原始类型。
4个整型数据类型:
数据类型 | 字节 | 范围 | 缓存范围 |
---|---|---|---|
Byte | 1 | [-2\(^7\),2\(^7\)-1] | [-128,127] |
Short | 2 | [-2\(^{15}\),2\(^{15}\)-1] | [-128,127] |
Integer | 4 | [-2\(^{31}\),2\(^{31}\)-1] | [-128,127] |
Long | 8 | [-2\(^{63}\),2\(^{63}\)-1] | [-128,127] |
两个浮点类型,浮点类型包含几个特殊值正无穷、负无穷、NaN:
数据类型 | 字节 | 指数位数 | 尾数位数 | 指数偏移 |
---|---|---|---|---|
Float | 4 | 8 | 23 | 127 |
Double | 8 | 11 | 52 | 1023 |
字符、布尔、枚举类型
一个字符类型Character,该类提供了大量的静态方法用于确定字符的类别(小写字母、数字等)以及字符大小写转换。Character类的字段和方法是根据来自Unicode标准的字符信息定义的,特别是作为Unicode字符数据库一部分的UnicodeData文件。该文件可从Unicode Consortium获取。
char数据类型(以及Character对象封装的值)基于原始Unicode规范,该规范将字符定义为固定宽度的16位。 Unicode标准已经更改为允许表示需要超过16位的字符。合法代码点的范围现在是U+0000到U+10FFFF,称为Unicode标量值。
从U+0000到U+FFFF的字符集,也称为基本多语言平面(BMP)。码位大于U+FFFF的字符称为增补字符。 Java平台在char数组以及String和StringBuffer类中使用UTF-16编码方式。在这种表示中,增补字符表示为一对字符值,第一个来自高代理范围(\uD800-\uDBFF),第二个来自低代理范围(\uDC00-\uDFFF)。因此,char值表示BMP代码点,包括代理代码点或UTF-16编码的代码单元。int值表示所有Unicode代码点,包括补充代码点。int的低21位用于表示Unicode代码点,高11位必须为零。除非另有说明,关于增补字符和代理字符值的行为如下:
- 仅接受char值的方法不能支持增补字符。他们将代理范围中的char值视为未定义的字符。例如
Character.isLetter('\uD840')
返回false,即使此特定值后跟字符串中的任何低代理值将表示一个字母。 - 接受int值的方法支持所有Unicode字符,包括增补字符。例如
Character.isLetter(0x2F81A)
返回true,因为代码点值表示一个字母(CJK 表意文字)。
在Java SE API文档中,Unicode代码点用于U+0000和U+10FFFF之间范围内的字符值,Unicode代码单元用于作为UTF-16编码的代码单元的16位字符值。有关Unicode 术语的更多信息,请参阅Unicode词汇表。
数据类型 | 字节 | 范围 | 缓存范围 |
---|---|---|---|
Character | 2 | [\u0000, \uFFFF] | [0,127] |
一个布尔类型Boolean,该类提供了布尔值和字符串互转方法,以及在处理布尔值时有用的其他常量和方法。内部预先创建了TRUE和FALSE两个常量。
一个枚举类型Enum,该类是抽象类型,是所有Java语言枚举类型的公共基类。有关枚举的更多信息可以在The Java™ Language Specification第8.9节中找到。当使用Set存储枚举以及使用Map把枚举当做键时,可以使用专门且高效的EnumSet和EnumMap实现。内部定义了两个字段:
字段 | 类型 | 描述 |
---|---|---|
name | String | 枚举常量的名称,在枚举构造函数中初始化。枚举的toString方法返回的就是这个字段 |
ordinal | int | 该枚举常数的序数(从0开始按声明顺序递增)。一般在基于枚举的数据结构中被使用,例如EnumSet和EnumMap |
Math 和 StrictMath
两个类中都定义了一批相同的基本数学运算方法,如指数、对数、平方根、三角函数等。
两个类的不同点是,Math类不限制不同平台的实现必须返回完全按位相同的结果。默认情况下,许多Math下的方法只是调用StrictMath中的等效方法。鼓励不同平台的实现使用特定于平台的指令来提升Math类中方法的性能。
而StrictMath类为了确保Java程序的可移植性,限制这个类中的一些方法在不同平台的实现中必须返回按位完全相同的结果。其由C语言的fdlibm库来实现。
两者在JDK中都使用原始类型的有符号二进制补码整数算法。开发人员应该选择合适的基本数据类型来避免数据溢出。在需要检查溢出错误的情况下,方法adddexact、subtractExact、multiplyExact和toIntExact在结果溢出时抛出算术异常。对于其他算术运算,如除法、绝对值、自增、自减和反运算,只有在特定的最小或最大值时才会发生溢出,应该根据适当的最小或最大值进行检查。
进程
ProcessBuilder
用于创建系统进程。
每个ProcessBuilder实例管理一组进程属性。start()
方法使用这些属性创建一个新的Process实例。可以重复调用start()
方法以创建具有相同或相关属性的新子进程。其管理的属性包括:
//一个字符串列表,包含调用子进程应用及其参数
List<String> command;
//一组环境变量的Map,默认继承当前进程,可以通过`System.getenv()`获取。
File directory;
//工作目录,默认工作目录是当前进程的工作目录
Map<String,String> environment;
/**
长度为3,使用数组位区分是输入流、输出流和异常流,全部默认为管道流:
1. `redirects[0]`表示输入流,可以通过`Process.getOutputStream()`获取此流,但是如果通过redirectInput重定向到其他源后`Process.getOutputStream()`会返回一个空的输出流:
+ write方法抛出IOException
+ close方法无效
2. `redirects[1]`表示输出源,可以通过`Process.getInputStream()`获取此流,但是如果通过redirectOutput重定向到其他源后`Process.getInputStream()`会返回一个空的输入流:
+ read方法总是返回-1
+ available方法总是返回0
+ close方法无效
3. `redirects[2]`表示异常源,可以通过`Process.getErrorStream()`获取此流,但是如果通过redirectError重定向到其他源后`Process.getErrorStream()`会返回一个空的输入流:
+ read方法总是返回-1
+ available方法总是返回0
+ close方法无效
**/
Redirect[] redirects;
/**
默认为false,表示子进程的标准输出和标准异常是两个单独的流。如果设置为true后:
+ 标准输出和标准异常将使用同一个流
+ 标准输出和标准异常可以使用redirectOutput重定向
+ 创建子进程时,将忽略由redirectError方法设置的任何重定向
+ 从`Process.getErrorStream()`返回的流将始终是空输入流
**/
boolean redirectErrorStream;
修改ProcessBuilder的属性只影响之后由start方法创建的进程,不会影响已经被创建的进程和父进程。
大多数的异常检查都是在stat方法中进行,比如除非调用了start方法,否则将command属性设置为空不会抛出异常。
ProcessBuilder不是线程安全的,如果多个线程并发访问同一个实例进行属性修改和创建新进程,需要在外部进行同步。
ProcessBuilder.Redirect
表示子进程的输出输出流的源。每个重定向实例是以下之一:
- 特殊值
Redirect.PIPE
,表示管道 - 特殊值
Redirect.INHERIT
,表示使用父进程的源 - 从文件读取的重定向,由调用
Redirect.from(File)
创建 - 写入文件的重定向,由调用
Redirect.to(File)
创建 - 附加到文件的重定向,由调用
Redirect.appendTo(File)
创建
ProcessEnvironment
用来管理进程的环境变量,构造方法是private修饰,无法被手动实例化。
Process
ProcessBuilder.start()
和Runtime.exec
方法创建一个本机进程并返回一个Process
子类的实例,该子类可用于控制进程并获取有关它的信息。Process类提供了获取进程输入、获取进程输出、等待进程完成、检查进程退出状态以及销毁(杀死)进程的方法。
创建进程的方法可能不适用于某些本地平台上的特殊进程,例如本地窗口进程、守护进程、Microsoft Windows 上的 Win16/DOS 进程或 shell 脚本。
默认情况下,创建的子进程没有自己的终端或控制台。它的所有标准 I/O(即 stdin、stdout、stderr)操作将被重定向到父进程,在那里可以通过使用getOutputStream()、getInputStream() 和 getErrorStream() 方法获得的流访问它们。父进程使用这些流向子进程提供输入和从子进程获取输出。由于一些原生平台只为标准输入输出流提供有限的缓冲区大小,如果不能及时写入输入流或读取子进程的输出流,可能会导致子进程阻塞,甚至死锁。
如果需要,还可以使用ProcessBuilder类的方法重定向子进程 I/O。
当不再有对Process对象的引用时,子进程不会被终止,而是子进程继续异步执行。
不要求由Process对象表示的进程相对于拥有Process对象的Java进程异步或并发执行。
ProcessBuilder.start()是创建进程的首选方式。
UNIXProcess
继承Process,UNIX平台下的进程实现。里面的创建线程的关键方法是forkAndExec方法,由native实现。
ProcessImpl
进程包装类,ProcessBuilder的start方法会调用ProcessImpl静态strat方法,最终会new一个UNIXProcess。JDK9以后与UNIXProcess类合并到了一起。
线程
Runnable
定义了一个旨在线程中执行代码的接口,其实现类必须定义一个无参无返回值的run方法。Thread类只是一个线程,也是实现了此接口才拥有了执行用户代码的能力。此外,Runnable接口还提供了一种可以不用继承Thread类就能在线程中执行代码的可能。只需要实现此接口并传入给Thread类,就不必覆盖Thread的其他方法起到开箱即用的效果。毕竟在没有必要的情况下,在线程中执行代码都没有必要继承Thread类。
Thread
是程序中执行的线程。Java虚拟机允许应用程序同时运行多个线程。
每个线程都有一个优先级。具有较高优先级的线程优先于具有较低优先级的线程执行。优先级并不会在所有平台都有效。如果没有主动设置优先级会继承父线程的优先级。
线程可以设置成守护线程,这样可以伴随着所有用户线程的结束而自动结束。由守护线程创建的线程默认是守护线程。
每个线程都有一个用于识别的名称。多个线程可能具有相同的名称。如果在创建线程时未指定名称,则会为其生成一个新名称。
Thread类中的一些属性
//如果Thread类没有被继承,Thread.run没有被重写,那么会Thread.run会调用target.run来执行用户代码。
private Runnable target;
//创建线程时请求的栈大小,可以被虚拟机重写和忽略。
private long stackSize;
Thread类中的一些方法
//返回执行当前代码的线程
public static native Thread currentThread();
/**
通知调度程序当前线程原意放弃CPU占用。调度程序可以随意忽略此通知。Yield是一种启发式编程的尝试,很少适合使用这种方法。
它对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件引起的错误。
在设计并发控制结构(例如`java.util.concurrent.locks`包中的结构)时,它也很有用
**/
public static native void yield();
//使当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度程序的精度和准确性。该线程不会失去对任何监视器的占有。
public static native void sleep(long millis) throws InterruptedException;
//所有的Thread构造方法都会调用init方法进行初始化。
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);
//启动这个线程,start方法不可以被多次调用。最终会调用native方法`start0()`;
public synchronized void start();
//调用用户代码。如果继承了Thread类,应该重写此方法。否则应该传入Runnable对象。
public void run();
//由虚拟机调用做线程结束前的清理操作
private void exit();
/**
此方法不安全,已经被弃用。强制停止一个线程。如果停止了一个未启动的线程,线程启动后会立刻停止。
如果一个线程被强制停止,会解锁它锁定的所有监视器,如果这些监视器保护的对象处于一个不一致的状态,
释放后不一致状态会对其他线程可见,会引发未知错误。正确的停止线程方式应该是让线程自己退出run方法。
这个方法最终会调用native的`stop0()`方法。
**/
public final void stop();
/**
中断线程,如果线程被wait、join、sleep方法阻塞,则调用interrupt会抛出InterruptedException。
此方法只是设置中断标识,并不会立刻中断线程。运行中的线程会在某些节点检查中断状态来进行中断。
也就是说调用了interrupt后,线程会在未知的位置中断。对一个非活跃线程调用interrupt不会有任何影响,因为线程不活跃就无法检查中断标识。
此方法最终会调用native的`interrupt0()`。
**/
public void interrupt();
/**
这是一个静态方法,测试当前线程是否中断。此方法调用后会清除线程的中断标识。也就是说第一次调用返回true,第二次调用将返回false。
此方法最终会调用native的`isInterrupted`,并传入true(清除状态)。
**/
public static boolean interrupted();
//测试线程是否被中断,不会清除状态位。此方法最终会调用native的`isInterrupted`,并传入false(不清除状态)。
public boolean isInterrupted();
//此方法没有被实现且已经被弃用。旨在提供一个不释放任何资源和锁的情况下销毁一个线程的方法。
public void destroy();
//测试线程是否存活
public final native boolean isAlive();
//已经被弃用,因为会导致死锁。暂停一个线程,如果线程是活动的,它将被挂起,直到调用`resume()`。
public final void suspend();
//已经被弃用,此方法仅用于恢复被挂起的线程,suspend会导致死锁。
public final void resume();
//改变线程的优先级,但是不能超过线程组中允许的最大优先级,超过取最小。
public final void setPriority;
/**
在A线程中调用B线程的join方法,A线程中调用之后的代码等待B线程结束后执行。
其实现方法是使用了`this.isAlive`为条件的`this.wait`循环,当线程B终止时会调用`this.notifyAll`。
建议用户不要手动调用线程实例的wait、notify或notifyAll方法,这些方法用于在锁对象上调用。
**/
public final synchronized void join(long millis);
//判断线程是否持有目标锁对象。
public static native boolean holdsLock(Object obj);
线程状态、生命周期
//线程状态枚举
public enum State {
//线程创建了还没有调用start()
NEW,
//在Java中线程的Redy和Running统称为Runnable。指线程调用了start。此状态下线程可能已经在运行,也可能在等待系统调度。
RUNNABLE,
//线程等待synchronized锁后进入此状态
BLOCKED,
//线程由于调用wait、join、park方法进入等待状态
WAITING,
//线程由于调用带有超时时间的wait、join、park方法进入超时等待状态
TIMED_WAITING,
//线程已经执行完成进入终止状态
TERMINATED;
}
ThreadGroup
public class ThreadGroup implements Thread.UncaughtExceptionHandler{
private final ThreadGroup parent;
String name;
int maxPriority;
boolean destroyed;
boolean daemon;
boolean vmAllowSuspension;
int nUnstartedThreads = 0;
int nthreads;
Thread threads[];
int ngroups;
ThreadGroup groups[];
}
一个线程组代表一组线程。此外,一个线程组还可以包含其他线程组。线程组形成一棵树,其中除了初始线程组之外的每个线程组都有一个父线程组。
一个线程可以访问关于它自己的线程组的信息,但不能访问关于它的线程组的父线程组或任何其他线程组的信息。
通过线程的构造函数和线程组的构造函数建立联系。线程组的无参构造函数式private修饰,只能由JVM调用创建一个名为”system“的系统线程组。里面管理系统线程,例如对象的销毁等。system线程组的直接子线程组是main线程组,这个线程组至少包含一个main线程,用于执行main方法。
ThreadGroup中的一些方法锁定策略是尽可能只锁定树的一层,否则从下往上锁定。也就是说,从子线程组到父线程组。这样做的好处是可以限制需要持有的锁的数量,特别是可以避免必须获取根线程组的锁(或全局锁),这在具有许多线程组的多处理器系统中可能会成为争用的源。一些方法例如activeCount通常会获取线程组状态的快照,然后使用该快照,而不是在处理子线程时锁定线程组。
ThreadLocal
ThreadLocal.ThreadLocalMap.Entry继承WeakReference原因分析
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
private static AtomicInteger nextHashCode = new AtomicInteger();
public T get() {
getMap(Thread.currentThread()).getEntry(this);
}
public void set(T value) {
getMap(Thread.currentThread()).set(this, value);
}
public void remove() {
getMap(Thread.currentThread()).remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
private Entry[] table;
private Entry getEntry(ThreadLocal<?> key) {
...
expungeStaleEntry()
}
private void set(ThreadLocal<?> key, Object value) {
...
index=key.threadLocalHashCode & (table.len-1)
expungeStaleEntry()
}
private void remove(ThreadLocal<?> key) {
...
expungeStaleEntry()
}
private int expungeStaleEntry(int staleSlot) {
//清理table中key==null的项
}
}
}
ThreadLocal作为一个标识符,与Thread互相引用,在ThreadLocal.ThreadLocalMap中实现操作,相结合提供了线程局部变量存储的能力。
Thread中只是存储了一个ThreadLocal.ThreadLocalMap属性,而ThreadLocal只是作为一个存、取、删对象操作的入口,及ThreadLocalMap.Entry的标识符。ThreadLocal中的操作入口会获取Thread中的threadLocals
属性,并调用threadLocals
的方法。ThreadLocalMap在一个Entry的数组中存储了真正的线程局部变量,并提供了存、取、删Entry的方法。并且还提供了线程局部变量清理能力。Entry继承了弱引用WeakReference<ThreadLocal<?>>
,并有一个value对象,这个value对象是最后实际的线程局部变量。ThreadLocal中还有一个静态的AtomicInteger属性nextHashCode
,最后要存储的线程局部变量在Entry数组中的位置是由key.threadLocalHashCode & (table.length - 1)
确定。
ThreadLocal作为一个由JDK提供的线程局部变量存储能力,而不是一个集合框架,是需要帮助使用者管理被存储的线程局部变量。也就是说如果作为集合框架,集合内的元素的生命周期由使用者管理。而ThreadLocal内存储的对象,并不要求使用者一定去移除,如果使用者不进行移除操作,而Entry还保留了value的强引用,就会导致Entry和value都不会被回收,最终内存泄漏。
正常来讲线程局部变量存储在Thread类中,伴随着线程的销毁而被销毁。但是由于线程池的存在,线程使用完并不一定会被销毁,而会被复用。因此为了避免使用者一直不做移除操作而导致内存泄漏,ThreadLocal的设计中使用了WeakReference来持有键,也就是ThreadLocal对象。这样当这个ThreadLocal对象在使用完,没有了强引用后,会被JVM进行回收。而ThreadLocalMap中的所有操作方法都会调用一个expungeStaleEntry方法来对键已经被回收的Entry进行清理。在这里,如果长时间没有调用存、取、删等操作方法,就不会触发Entry的清理操作,最终也是会造成内存泄漏。
而我们在使用ThreadLocal时,一般都是作为全局静态变量来使用。在这种场景下,ThreadLocal提供的线程局部变量清理能力便失去了作用,因为ThreadLocal永远都保留一个强引用,WeakReference内的ThreadLocal便不会被JVM回收。
当在线程池中使用ThreadLocal时,如果不对存储的数据做清理,线程被复用的时候还会保持着上一个工作环境的线程局部变量,就会引发未知的业务异常。
因此,我们在使用ThreadLocal时,需要严格规范线程局部变量存和删,使用完的数据一定要及时做remove操作。这样才能避免内存泄漏和业务异常。
Entry中的value不设置成WeakReference,是因为不清楚这个value除了Entry的引用还是否还存在其他引用,如果不存在其他引用,并且我们的业务代码还没有结束,当GC的时候就会直接将这个value清除,而此时我们的ThreadLocal还处于使用期间,value的清理早于业务的结束,就会造成value为null的错误,所以将其设置为强引用。
因为是作为线程局部变量,数据的生命周期在线程内,如果数据放在线程外存储则会带来数据声明周期管理上的麻烦,无法及时的跟随线程的销毁而回收。既然是JDK提供的,有充足的条件可以放到Thread类中,所以最好的方式是放到线程中。
InheritableThreadLocal
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null){
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}
}
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
}
InheritableThreadLocal可以在子线程中继承父线程内存储的线程局部变量。Thread内有一个inheritableThreadLocals属性,InheritableThreadLocal重写了ThreadLocal的getMap方法。同时Thread在调用init初始化的时候会把父线程中的inheritableThreadLocals复制一份。
通过覆盖InheritableThreadLocal中的childValue方法,可以修改继承父线程局部变量时的行为,比如使用深拷贝创建副本而不只是继承引用来避免互相影响。
基础类
FunctionalInterface
类型注解,用于标注一个函数式接口。被此注解修饰的函数式接口中,只能声明一个抽象方法,重写Object对象的抽象方法不算在内。同时可以定义其他默认方法。如果修饰一个不符合条件的类时,编译将报错。
SuppressWarnings
告诉编译器忽略指定的警告,不在编译完成后出现警告信息
关键字 | 用途 |
---|---|
all | 抑制所有警告 |
boxing | t抑制装箱、拆箱操作时候的警告 |
cast | 抑制映射相关的警告 |
dep-ann | 抑制启用注释的警告 |
deprecation | 抑制过期方法警告 |
fallthrough | 抑制确在switch中缺失breaks的警告 |
finally | 抑制finally模块没有返回的警告 |
hiding | 抑制相对于隐藏变量的局部变量的警告 |
incomplete-switch | 忽略没有完整的switch语句 |
nls | 忽略非nls格式的字符 |
null | 忽略对null的操作 |
rawtypes | 使用generics时忽略没有指定相应的类型 |
restriction | 抑制禁止使用劝阻或禁止引用的警告 |
serial | 忽略在serializable类中没有声明serialVersionUID变量 |
static-access | 抑制不正确的静态访问方式警告 |
synthetic-access | 抑制子类没有按最优方法访问内部类的警告 |
unchecked | 抑制没有进行类型检查操作的警告 |
unqualified-field-access | 抑制没有权限访问的域的警告 |
unused | 抑制没被使用过的代码的警告 |
SafeVarargs
抑制泛型变长参数方法的编译告警。只能修饰static或final的方法。其他位置可以使用@SuppressWarnings("unchecked")
代替使用。
Deprecated
标识一个元素已经被弃用
ClassValue
在一些特殊场景,例如动态语言、IDE、框架中需要缓存Class和Class关联对象的映射,同时也需要缓存的键值能关联的被垃圾回收,也就是成对的被回收。ClassValue就是在这中需求中产生的。其实现类似于ThreadLoad和Thread。是在Class类中耦合了一个ClassValue.ClassValueMap对象。同时ClassValueMap对象继承了WeakHashMap。
AutoCloseable
改接口配合try-with-resources语法使用,实现自动关闭资源。
Cloneable
如果不实现此接口,那么调用Object.clone()
时会抛出CloneNotSupportException。
Override
标识覆盖了父类中的方法的声明,编译器会帮助校验方法是否重写正确。
Readable
提供了可以往CharBuffer读取数据的接口。
Iterable
提供了创建迭代器,及forEach操作的接口。
Package
定义了包对象,用于在运行时获取包信息。只能使用不能创建。
Void
Void类是一个不可实例化的占位符类,用于保存对表示Java关键字Void的class对象的引用。一般有两个用途,一个是在反射中用于匹配void返回类型;还有可以在泛型中使用,比如Future中定义返回空。
ClassLoader
类加载器的种类、委派模型、引导类加载器的实现在JDK9版本有所改变,以下仅限JDK8及以下版本
类加载器负责加载类。ClassLoader类是一个抽象类。给定类全路径名称,类加载器尝试定位或者生成构成类定义的数据。其典型的实现时将名称转换为文件名,然后从文件系统中读取类文件。
每个Class对象都包含对定义它的ClassLoader的引用。数组的类对象不是由类加载器创建的,而是根据Java运行时的需要自动创建的。Class.getClassLoader()
返回的数组的类加载器与其元素类型的类加载器相同;如果元素类型是原始类型,则数组类没有类加载器。类加载器创建的对象的方法和构造函数可能会引用其他类。为了确定所引用的类,Java虚拟机调用最初创建类的类加载器的loadClass方法。
ClassLoader类使用委托模型来搜索类和资源。ClassLoader的每个实例都有一个关联的父类加载器。当请求查找类或资源时,ClassLoade实例会首先将类或资源的搜索委托给其父类加载器,如果父加载器不可以加载(无法加载或不负责此类),然后再尝试自己查找类或资源。这样可以避免重复加载,及防止核心类被篡改。虚拟机的内置类加载器,称为“引导类加载器(bootstrap class loader)”,它没有父级,但可以作为ClassLoader实例的父级。
但双亲委派模型并不是一直适用。如JNDI、JDBC等场景需要引导类加载器去加载用户类;而有一些框架提供者会在框架中提供接口,如果需要用户实现类和框架类在同一个类加载器中加载,就需要使用Java提供的SPI机制来实现。SPI的实现方式是JDK提供的上下文类加载器,这是一种概念加载器,其是硬编码了ClassLoader的调用。
还有一种概念类加载器是线程类加载器,其在Thread类中存储了创建线程的类的类加载器。
支持并发加载类的类加载器被称为具有并行能力的类加载器,并且需要在类初始化时通过调用ClassLoader.registerAsParallelCapable
方法来注册自己。请注意,ClassLoader类默认注册为具有并行能力。但是,如果它们具有并行能力,它的子类仍然需要注册自己。在委托模型不是严格分层的环境中,类加载器需要具有并行能力,否则类加载会导致死锁,因为加载器锁在类加载过程中一直保持着(参见 loadClass方法)。
通常,Java虚拟机以平台相关的方式从本地文件系统加载类。例如,在UNIX系统上,虚拟机从CLASSPATH环境变量定义的目录中加载类。但是,有些类可能不是来自文件;它们可能来自其他来源,例如网络,或者它们可以由应用程序构建。方法defineClass将字节数组转换为类Class的实例。可以使用Class.newInstance创建这个新定义的类的实例。
由命名空间确定虚拟机中类的唯一性,命名空间是由该类加载器及其父类加载器构成的。在该命名空间中,父类加载器加载的类对子类是可见的,但是子类加载器加载的类对父类加载器不可见,而且在同一命名空间中一定不会出现多个相同的Class对象。用户自定义类加载器可以实现应用程序的插件机制,同时也能实现应用隔离。
引导类加载器由c++实现,JVM提供,所以不继承ClassLoader,出于安全考虑,Bootstrap引导类加载器只加载包名为java、javax、sun等开头的类。其他所有继承ClassLoader的包括扩展类加载器和应用程序类加载器都称为自定义类加载器。ExtClassLoader和AppClassLoader都在sun.misc.Launcher
的子类中实现。且都不是ClassLoader的直接实现类,两者都继承了java.net.URLClassLoader
,同时URLClassLoader继承了java.security.SecureClassLoader
,最后SecureClassLoader才是ClassLoader的直接实现类。
ClassLoader的主要方法属性
/**
父加载器,维持委派模型
Note: VM hardcoded the offset of this field, thus all new fields must be added *after* it.
**/
private final ClassLoader parent;
/**
并行类加载时synchronized块对加载的不同类分开持有锁对象。
JVM使用这个字段来判断类加载器是否具有并行能力,以及选择类加载的合适锁对象
**/
private final ConcurrentHashMap<String, Object> parallelLockMap;
//自定义类加载器应该重写findClass方法而不是重写此方法。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) { //使用parallelLockMap
Class c = findLoadedClass(name);
if(c==null){
if(parent!=null){
c=parent.loadCalss(name,false);
}else{
c=findBootstrapClassOrNull(name); //没父加载器就要调用bootstrap class loader
}
。。。
if(c==null){
c=findClass(name); //最后才自己尝试装载类
}
}
if(resolve){
resolveClass(c); //如果需要,装载后马上进行解析
}
}
}
//默认实现抛出ClassNotFoundException,用于重写实现自定义加载器查找类
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
//使用原始字节数组创建Class对象。其实调用native方法,由c++实现。
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{...} //+2重载
private native Class<?> defineClass0(String name, byte[] b, int off, int len, ProtectionDomain pd); //+2重载
//校验自定义加载的类不能是`java.*`为开头的包。
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd){...}
//类能被使用需要经过加载和链接,此方法链接加载的类,最终调用native方法,由c++实现
protected final void resolveClass(Class<?> c){...}
private native void resolveClass0(Class<?> c);
//查找已经被引导类加载器加载的类,最终调用native方法,由c++实现
private Class<?> findBootstrapClassOrNull(String name){...}
//查找已经被加载过的类,最终调用native方法,由c++实现
protected final Class<?> findLoadedClass(String name){...}
private native final Class<?> findLoadedClass0(String name);
JDK9开始由于模块化改变,ClassLoader也发生了变更。扩展类加载器被替换为平台类加载器。引导类加载器不在只由c++开发而是由java和c++共同协作,但是引导类加载器依然作为null存在。引导类加载器、平台类加载器、应用类加载器现直接继承于BuildinClassLoader,不再继承URLClassLoader。类加载器可以设置名称,便于调试使用。最关键的一点是委派模型发生了改变,应用类加载器和平台类加载器会优先委托当前加载的类的模块去加载。
Class
在Java中,类也是一个对象。类Class的实例表示正在运行的Java应用程序中的类和接口。枚举是一种类,注解是一种接口。每个数组也属于一个类,该类反映为一个Class对象,该对象由具有相同元素类型和维数的所有数组共享。原始Java类型和关键字void也表示为Class对象。int的Class为int
;int数组的Class为int[]
;void的Class为void
。类没有公共构造函数。取而代之的是,Java虚拟机在加载类时并通过调用类加载器中的defineClass方法自动构造类对象。
有三种方式可以得到Class对象:
- Class.forName方法,这种方法类会进行类加载并初始化
- 实例对象上的getClass方法,在创建实例对象时类已经被加载到JVM中了,所以getClass不会再次加载
- 类的class常量属性(Integer.class),这种只会加载Class,而不会进行初始化。此时能访问类的
static final
常量成员,在访问类的变量成员时会触发初始化。
Class包含了类的定义及获取类定义的一些方法,比如获取构造方法、获取成员方法、获取属性等。获取的构造方法一定是本类的,不会是父类中的构造方法。Class中带有Declared
的方法都是获取本类所有可见性的属性或方法。不带Declared
的方法可以获取本类及父类中的所有public的属性和方法。
/**
加载类或接口,initialize表示是否加载后进行初始化。loader指定加载器进行加载;
此方法不能用于原始类型或void的。如果是数组类,则数组类的组件类型被加载但不被初始化。
**/
public static Class<?> forName(String className) throws ClassNotFoundException{...}
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException{...}
private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller)
/**
使用无参构造器创建一个此Class对象的新实例。如果尚未初始化该类,则将其初始化。
**/
public T newInstance() throws InstantiationException, IllegalAccessException {...}
/**
返回此Class的全名。如果Class表示原始类型或void,则返回对应Java语言关键字(如int、void)。
如果是数组,则前面加"["符号,符号数量表示数组深度。
**/
public String getName(){...}
private native String getName0();
/**
返回类名。如果是匿名类,则返回一个空字符串。
数组的简单名称是附加了“[]”的组件类型的简单名称。特别是组件类型为匿名的数组的简单名称是“[]”
<https://stackoverflow.com/questions/15202997/what-is-the-difference-between-canonical-name-simple-name-and-class-name-in-jav/15203417#15203417>
**/
public String getSimpleName(){...}
public String getCanonicalName(){...}
public String getTypeName(){...}
/**
获得父类,此方法可以用于获取带泛型的父类。Type中包含泛型信息。
如果表示Object类、接口、原始类型或 void,则返回 null。如
果此对象表示数组类,则返回Object类。
**/
public Type getGenericSuperclass(){...}
/**
返回所有实现的接口,包括接口中继承的接口,按照implements和extends顺序返回。
如果此对象表示数组类型,则接口Cloneable和java.io.Serializable将按该顺序返回
**/
public Class<?>[] getInterfaces(){...}
private native Class<?>[] getInterfaces0();
/**
返回值int的每一位表示一个修饰符。修饰符由public、protected、private、final、static、abstract和interface常量组成,见[java.lang.reflect.Modifier]
如果底层类是一个数组类,那么它的public、private 和 protected 修饰符与其组件类型的修饰符相同。
如果此类表示原始类型或void,则其public修饰符始终为true,其protected和private修饰符始终为false。
如果此对象表示数组类、原始类型或void,则其final修饰符始终为true,其接口修饰符始终为false。
**/
public native int getModifiers();
/**
如果此Class是在另一个类中声明的,则返回表示在其中声明它的类的Class对象。
如果此类或接口不是任何其他类的成员,则此方法返回null。
如果此Class表示数组类、原始类型或void,则此方法返回null。
**/
public Class<?> getDeclaringClass(){...}
/**
返回定义此匿名类的方法、构造方法、或类 <https://blog.csdn.net/liuxiao723846/article/details/54668820>
**/
public Method getEnclosingMethod(){...}
public Constructor<?> getEnclosingConstructor(){...}
public Class<?> getEnclosingClass() {...}
/**
返回在此类中定义的内部类
或者如果此Class表示原始类型、数组类或void,则此方法返回长度为0的数组。
**/
public Class<?>[] getDeclaredClasses(){...}
public Class<?>[] getClasses(){...}
/**
如果此Class是在非static类、方法、块中声明的,则parameterTypes的第一个参数是声明了此Class的实例对象。
**/
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes){...}
public Constructor<T> getConstructor(Class<?>... parameterTypes) {...}
/**
返回的数组中的元素没有排序,也没有任何特定的顺序。
如果该类具有默认构造函数,则它包含在返回的数组中。
如果此Class对象表示接口、原始类型、数组类型或void,则此方法返回长度为0的数组。
**/
public Constructor<?>[] getDeclaredConstructors()
/**
如果类没有公共构造函数,或者类是数组类,或者原始类型或void,则返回长度为0的数组。
请注意,虽然此方法返回一个Constructor<T> 对象数组(即此类的构造函数数组),但此方法的返回类型可能是Constructor<?>[]而不是Constructor<T>[]是预期的。
这种信息量较少的返回类型是必要的,可以同时聚合保存不同类的Constructor对象。
**/
public Constructor<?>[] getConstructors() {...}
/**
此方法**无法找到**数组类型中的length属性。
**/
public Field getDeclaredField(String name){...}
public Field getField(String name){...}
/**
如果此Class表示数组类型、原始类型或 void,则此方法返回长度为0的数组。
返回数组中的元素没有排序,也没有任何特定的顺序
**/
public Field[] getDeclaredFields() {...}
public Field[] getFields(){...}
/**
如果名称是“<init>”或“<clinit>”,则会引发NoSuchMethodException。
如果在一个类中声明了多个具有相同参数类型的方法(Java不允许,但JVM允许),并且其中一个方法的返回类型比其他任何方法都更具体,则返回该方法;否则任意选择其中一种方法。
如果此Class代表一个数组类型,那么这个方法找不到clone()方法。
不包含**静态方法**
**/
public Method getDeclaredMethod(String name, Class<?>... parameterTypes){...}
public Method getMethod(String name, Class<?>... parameterTypes){...}
/**
返回的方法中不包含“<clinit>”方法
如果此Class表示数组类型、原始类型或void,则返回的数组的长度为0。
返回数组中的元素没有排序,也没有任何特定的顺序。
**/
public Method[] getDeclaredMethods(){...}
/**
如果此Class表示一个数组类型,则返回从Object继承的公共方法。但它不包含clone。
如果此Class表示一个接口,则返回的数组不包含任何来自Object的隐式声明的方法。因此,如果在此接口或其任何超接口中没有显式声明任何方法,则返回的数组的长度为0。(请注意,表示类的Class始终具有从Object继承的公共方法。)
**/
public Method[] getMethods() {...}
/**
获取注解,从父类继承来的注解,以及注解继承的父注解.
详见[注解](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AnnotatedElement.html)
**/
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass){...}
public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass){...}
public Annotation[] getDeclaredAnnotations(){...}
public <A extends Annotation> A getAnnotation(Class<A> annotationClass){...}
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass){...}
public Annotation[] getAnnotations(){...}
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass){...}
/**
使用该类的加载器获取指定资源,如果资源以"/"开头,则资源相对于CLASSPATH的绝对路径。否则是基于该类所在包的相对路径。
**/
public InputStream getResourceAsStream(String name){...}
public java.net.URL getResource(String name){...}
/**
与instanceof等效。失败抛出ClassCastException。
如果此Class表示原始类型,则此方法永远返回false(int.class.isInstance(2)==false)。
**/
public native boolean isInstance(Object obj);
public native boolean isAssignableFrom(Class<?> cls); //判断参数是否与此Class相同,或是子类
public native boolean isInterface(); //判断是否是接口类
public native boolean isArray(); //是否是数组类
public native boolean isPrimitive(); //否是原始数据类型
public boolean isEnum(){...} //是否是枚举类型
public boolean isAnnotation() {...} //判断是否是注解类型,如果此方法返回true,则isInterface也返回true。
public boolean isSynthetic() {...} //判断此类是否是[合成类](https://juejin.cn/post/6850418111481544717)
public boolean isAnonymousClass() {...} //判断是否是匿名类
public boolean isLocalClass(){...} //判断是否是本地类
public boolean isMemberClass() {...} //判断是否是成员类
public Package getPackage(){...} //返回此类所属的包对象
public ClassLoader getClassLoader() {...} //返回加载此Class的ClassLoader。如果由引导类加载器加载则返回null。
public TypeVariable<Class<T>>[] getTypeParameters(){...} //如果是模板类,获取泛型类型参数,详解后文
public native Class<? super T> getSuperclass(); //返回此Class的超类。如果此Class表示Object类、接口、原始类型或void,则返回null。如果此对象表示数组类,则返回Object类。
static native Class<?> getPrimitiveClass(String name); //返回指定原始类型的Class
public T[] getEnumConstants(){...} //返回枚举类型的所有枚举
public T cast(Object obj) {...} //将对象强制转换为此Class表示的类或接口。
public <U> Class<? extends U> asSubclass(Class<U> clazz) {...} //强制转换为指定的子类,失败抛出 ClassCastException。
public Type[] getGenericInterfaces(){...} //获取所有实现的接口Type类型,可以用于获取泛型接口。
public native Class<?> getComponentType(); //如果是数组返回其组件类型。否则返回null。
getTypeParameters方法与获取泛型参数:
interface Demo21{}
interface Demo22{}
public Class Demo<T1,T2 extends Integer,T3 extends Demo21 & Demo22>{}
TypeVariable<Class> [] typeParameters = Demo1.class.getTypeParameters();
for(TypeVariable<Class> typeParameter:typeParameters){
System.out.println("变量名称:"+typeParameter.getName());
System.out.println("这个变量在哪声明的:"+typeParameter.getGenericDeclaration());
Type[]bounds = typeParameter.getBounds();
System.out.println("这个变量上边界数量:"+bounds.length);
System.out.println("这个变量上边界清单:");
for (Type bound : bounds) {
System.out.println(bound.getTypeName());
}
}
/**输出
变量名称:T1
这个变量在哪声明的:class com.test.Demo1
这个变量上边界数量:1
这个变量上边界清单:java.lang.Object
变量名称:T2
这个变量在哪声明的:class com.test.Demo1
这个变量上边界数量:1
这个变量上边界清单:java.lang.Integer
变量名称:T3
这个变量在哪声明的:class com.test.Demo1
这个变量上边界数量:2
这个变量上边界清单:
com.test.Demo21
com.test.Demo22
**/
SystemClassLoaderAction
Java类加载器(四)博客中有提到。
定义在ClassLoader
文件中,用于启动通过java.system.class.loader
自定义的系统类加载器。
ClassLoaderHelper
内只有一个方法mapAlternativeName(File)
。用于java.lang.ClassLoader#loadLibrary
方法中加载由java.library.path
和sun.boot.library.path
定义的动态连接库时,如果路径错误可以尝试更换路径加载。
只供特定平台使用,例如见JDK-7157665 : Use ClassLoaderHelper for all native library loads [macosx]。
运行时相关类
System
系统工具类,它不能被实例化。 System类提供的功能包括标准输入、标准输出和错误输出流;访问外部定义的属性和环境变量;一种加载文件和库的方法;以及一种用于快速复制数组的一部分的实用方法。
//初始化System类的方法
private static void initializeSystemClass(){...}
//返回当前时间,单位(毫秒)。其分辨率粒度取决于底层操作系统。有操作系统以几十毫秒为单位测量时间。
public static native long currentTimeMillis();
/**
返回当前时间,单位(纳秒)。此方法提供纳秒精度但不提供纳秒分辨率,也就是说其分辨率不一定比currentTimeMillis高。
292年2^63纳秒可能会造成数值溢出。比较两个时间时应该使用减法计算,而不是比较符号,因为数值可能会溢出。
**/
public static native long nanoTime();
/**
只支持数组类型,且具有相同的组件类型进行拷贝。特别的,不支持组件的原始类型和包装类型之间进行拷贝。不然会抛出ArrayStoreException。
不支持扩容操作,所以任何长度计算出错或长度不够都会抛出IndexOutOfBoundsException。
当复制过程中发生ArrayStoreException时,会停止复制,但不影响已复制数据。
**/
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
//hashCode方法可以被重写,identityHashCode会根据内存地址返回对象的hash值而不管对象是否重写了hashCode方法。
public static native int identityHashCode(Object x);
//系统换行符。在UNIX系统中是"\n",在windows中是"\r\n"
private static String lineSeparator;
public static String lineSeparator() {...}
//见下文系统属性列表
private static Properties props;
public static String getProperty(String key){...}
public static String getProperty(String key, String def){...}
public static Properties getProperties(){...}
public static String setProperty(String key, String value)
public static void setProperties(Properties props){...}
private static native Properties initProperties(Properties props);
public static String clearProperty(String key){...}
/**
系统属性和环境变量在概念上都是名称和值之间的映射。这两种机制都可用于将用户定义的信息传递给Java进程。
环境变量具有更全局的影响,因为它们对定义它们的进程的所有后代可见,而不仅仅是直接的Java子进程。
在不同的操作系统上,它们可能具有细微的不同语义,例如不区分大小写。由于这些原因,环境变量更有可能产生意想不到的副作用。
最好尽可能使用系统属性。当需要全局效果或外部系统接口需要环境变量(例如 PATH)时,应使用环境变量。
在 UNIX 系统上,名称的字母大小写通常很重要,而在Windows系统上通常不重要。
例如,表达式 System.getenv("FOO").equals(System.getenv("foo")) 在Windows上可能为真。
**/
public static String getenv(String name){...}
public static java.util.Map<String,String> getenv(){...}
//终止当前运行的Java虚拟机。参数用作状态码;非零状态码表示异常终止。
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
//尝试运行垃圾收集器。
public static void gc() {
Runtime.getRuntime().gc();
}
//尝试运行任何等待清理的对象的终结方法。
public static void runFinalization() {
Runtime.getRuntime().runFinalization();
}
//已弃用,不安全,可能会多个线程同时操作一个对象
@Deprecated
public static void runFinalizersOnExit(boolean value) {
Runtime.runFinalizersOnExit(value);
}
//加载JNI库,单个文件,不自动查找依赖
public static void load(String filename) {
Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}
//加载JNI库,从java.library.path中自动查找依赖
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
//将库名称映射到表示本机库的特定于平台的字符串。
public static native String mapLibraryName(String libname);
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
private static volatile SecurityManager security = null;
private static volatile Console cons = null;
public static void setIn(InputStream in){...}
private static native void setIn0(InputStream in);
public static void setOut(PrintStream out){...}
private static native void setOut0(PrintStream out);
public static void setErr(PrintStream err){...}
private static native void setErr0(PrintStream err);
public static SecurityManager getSecurityManager(){...}
public static void setSecurityManager(final SecurityManager s){...}
private static synchronized void setSecurityManager0(final SecurityManager s){...}
public static Console console(){...}
public static Channel inheritedChannel(){...}
系统属性列表:
属性 | 描述 |
---|---|
java.version | Java版本 |
java.vendor | Java供应商 |
java.vendor.url | Java供应商网址 |
java.home | Java安装目录 |
java.vm.specification.version | JVM规范版本 |
java.vm.specification.vendor | JVM规范供应商 |
java.vm.specification.name | JVM规范名称 |
java.vm.version | JVM版本 |
java.vm.vendor | JVM供应商 |
java.vm.name | JVM名称 |
java.specification.version | Java规范版本 |
java.specification.vendor | Java规范供应商 |
java.specification.name | Java规范名称 |
java.class.version | class版本号 |
java.class.path | 用于查找包含类文件的目录和 JAR 存档的路径 |
java.library.path | 加载库时的搜索列表 |
java.io.tmpdir | 默认的临时文件路径 |
java.compiler | 使用的JIT编译器名称 |
java.ext.dirs | 扩展目录列表 |
os.name | 操作系统名称 |
os.arch | 操作系统架构 |
os.version | 操作系统版本 |
file.separator | 文件分隔符 (“/” on UNIX) |
path.separator | 路径分隔符 (“:” on UNIX) |
line.separator | 行分隔符 (“\n” on UNIX) |
user.name | 系统账户名称 |
user.home | 用户目录 |
user.dir | 当前工作目录 |
Runtime
应用程序无法创建自己的此类实例。每个Java应用程序都有一个Runtime类的实例,它允许应用程序与运行应用程序的环境进行交互。当前运行时可以从getRuntime方法中获取。
/**
启动关闭流程。第一阶段并行且随机运行所有的shutdown hooks。第二阶段,运行所有注册过finalizationOnExit流程。
如果在JVM已经进入关闭流程后调用此方法,会进入阻塞。
**/
public void exit(int status){
Shutdown.exit(status);
}
/**
添加一个未start的线程。一旦进入shutdown hook流程只能调用halt方法来强制停止虚拟机。
JVM关闭流程开始后无法添加和移除hook。hook并不可信,不能完全依赖。
不要再hook中进行长时间操作。
**/
public void addShutdownHook(Thread hook) {
ApplicationShutdownHooks.add(hook);
}
public boolean removeShutdownHook(Thread hook) {
return ApplicationShutdownHooks.remove(hook);
}
//不进入任何流程,直接强制关闭JVM
public void halt(int status) {
Shutdown.beforeHalt();
Shutdown.halt(status);
}
@Deprecated
public static void runFinalizersOnExit(boolean value) {
Shutdown.setRunFinalizersOnExit(value);
}
// +5重载
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
/**
返回JVM可用的处理器数量。
此值可能会在虚拟机的特定调用期间发生变化。因此,对可用处理器数量敏感的应用程序应偶尔轮询此属性并适当调整其资源使用情况。
**/
public native int availableProcessors();
//返回JVM中可用内存量。调用gc方法可能会导致值增加。
public native long freeMemory();
/**
返回JVM中的内存总量。此方法返回的值可能会随时间变化,具体取决于主机环境。
请注意,保存任何给定类型的对象所需的内存量可能取决于实现。
**/
public native long totalMemory();
//返回JVM将尝试使用的最大内存量。如果没有固有限制,则返回Long.MAX_VALUE值。
public native long maxMemory();
//建议JVM进行垃圾回收
public native void gc();
//尝试运行JVM中对象的终结方法
private static native void runFinalization0();
public void runFinalization() {
runFinalization0();
}
/**
启用/禁用指令跟踪。如果布尔参数为真,则此方法建议 Java 虚拟机在执行时为虚拟机中的每条指令发出调试信息。此信息的格式以及将其发送到的文件或其他输出流取决于主机环境。如果虚拟机不支持此功能,它可能会忽略此请求。跟踪输出的目的地取决于系统。
如果布尔参数为假,此方法会导致虚拟机停止执行它正在执行的详细指令跟踪。
**/
public native void traceInstructions(boolean on);
/**
启用/禁用方法调用的跟踪。如果布尔参数为真,则此方法建议 Java 虚拟机在调用虚拟机时为虚拟机中的每个方法发出调试信息。此信息的格式以及将其发送到的文件或其他输出流取决于主机环境。如果虚拟机不支持此功能,它可能会忽略此请求。
使用参数 false 调用此方法表明虚拟机停止发出每次调用的调试信息。
**/
public native void traceMethodCalls(boolean on);
public void load(String filename){
load0(Reflection.getCallerClass(), filename);
}
synchronized void load0(Class<?> fromClass, String filename){
ClassLoader.loadLibrary(fromClass, filename, true);
}
public void loadLibrary(String libname) {
loadLibrary0(Reflection.getCallerClass(), libname);
}
synchronized void loadLibrary0(Class<?> fromClass, String libname) {
ClassLoader.loadLibrary(fromClass, libname, false);
}
ApplicationShutdownHooks
JVM退出的回调方法有多种类型,用户注册的回调由ApplicationShutdownHooks管理,注册在Shutdown中的第1位中
class ApplicationShutdownHooks {
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
Shutdown.add(1 /* 注册位置 */,
false /* 如果JVM正在退出则不进行注册 */,
new Runnable() {
public void run() {
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
//当JVM正在退出时,用户无法注册回调
hooks = null;
}
}
private ApplicationShutdownHooks() {}
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
static synchronized boolean remove(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook == null)
throw new NullPointerException();
return hooks.remove(hook) != null;
}
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
}
Shutdown
包私有的实用程序类,包含控制虚拟机关闭顺序的数据结构和逻辑。
class Shutdown {
/* 状态常量 */
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
private static int state = RUNNING;
private static boolean runFinalizersOnExit = false;
private static int currentRunningHook = 0;
/** 系统定义的回调,保存在hooks属性的预定位置
[0] Console restore hook (目前还不知道其作用)
[1] Application hooks (ApplicationShutdownHooks是往这一位注册)
[2] DeleteOnExit hook (在File.deleteOnExit方法中使用,用于退出时清理临时文件)
**/
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
private static class Lock { };
private static Object lock = new Lock();
private static Object haltLock = new Lock();
//Runtime.runFinalizersOnExit最终调用到这里
static void setRunFinalizersOnExit(boolean run) {
synchronized (lock) {
runFinalizersOnExit = run;
}
}
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {
if (state > RUNNING)
throw new IllegalStateException("Shutdown in progress");
} else {
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
hooks[slot] = hook;
}
}
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {//此处用锁是为了确保能够感知到在退出过程中注册的回调
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
//通知JVM
static native void beforeHalt();
//此处加锁以避免破坏“在关闭时删除”文件列表。
static void halt(int status) {
synchronized (haltLock) {
halt0(status);
}
}
static native void halt0(int status);
/* Wormhole for invoking java.lang.ref.Finalizer.runAllFinalizers */
private static native void runAllFinalizers();
//运行预定义的关闭流程
private static void sequence() {
synchronized (lock) {
//防止守护线程在DestroyJavaVM启动关闭序列后调用exit
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
/**
在Runtime.exit或中断退出状态回调中被调用。
**/
static void exit(int status) {
boolean runMoreFinalizers = false;
synchronized (lock) {
if (status != 0) runFinalizersOnExit = false;
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and halt */
break;
case FINALIZERS:
if (status != 0) {
//在非零状态下立即停止
halt(status);
} else {
//与旧行为的兼容性:运行更多终结器,然后停止
runMoreFinalizers = runFinalizersOnExit;
}
break;
}
}
if (runMoreFinalizers) {
runAllFinalizers();
halt(status);
}
synchronized (Shutdown.class) {
//同步类对象,导致试图启动shutdown的任何其他线程无限期地停止
beforeHalt();
sequence();
halt(status);
}
}
//当最后一个非守护线程结束时,由JNI DestroyJavaVM过程调用。与exit方法不同,这个方法实际上并不会停止VM。
static void shutdown() {
synchronized (lock) {
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and then return */
case FINALIZERS:
break;
}
}
synchronized (Shutdown.class) {
sequence();
}
}
}
Terminator
用于注册系统信号处理Handler。Signal类最终调用native方法实现信号响应
private static SignalHandler handler = null;
static void setup() {
if (handler != null) return;
SignalHandler sh = new SignalHandler() {
public void handle(Signal sig) {
Shutdown.exit(sig.getNumber() + 0200);
}
};
handler = sh;
try {
Signal.handle(new Signal("HUP"), sh);
} catch (IllegalArgumentException e) {
}
try {
Signal.handle(new Signal("INT"), sh);
} catch (IllegalArgumentException e) {
}
try {
Signal.handle(new Signal("TERM"), sh);
} catch (IllegalArgumentException e) {
}
}
}
Compiler
支持Java到本机代码的编译器和相关服务。Compiler类什么都不做。它用作JIT编译器实现的占位符。
当JVM第一次启动时,它会判断系统属性 java.compiler 是否存在。 (系统属性可通过 System.getProperty(String) 和 System.getProperty(String, String) 访问。如果是这样,则假定它是库的名称(具有依赖于平台的确切位置和类型);System.loadLibrary (java.lang.String) 被调用加载该库。如果加载成功,则调用该库中名为 java_lang_Compiler_start() 的函数。
如果没有可用的编译器,这些方法什么也不做。
public final class Compiler {
public static native boolean compileClass(Class<?> clazz);
public static native boolean compileClasses(String string);
public static native Object command(Object any);
public static native void enable();
public static native void disable();
}
AssertionStatusDirectives
定义断言assert指令在包或类中启用或忽略。JVM使用这个类来传递java命令行标志-enableassertions (-ea)和-disableassertions (-da)所隐含的断言状态指令。
class AssertionStatusDirectives {
String[] classes;
boolean[] classEnabled;
String[] packages;
boolean[] packageEnabled;
//默认情况下,是否启用非系统类中的断言。
boolean deflt;
}
RuntimePermission
此类用于运行时权限。 RuntimePermission包含一个name(也称为“目标名称”)但没有动作列表;您要么拥有命名权限,要么没有。 目标名称是运行时权限的名称。命名约定遵循分层属性命名约定。此外,星号可能出现在名称的末尾,跟在“.”之后,或者单独出现,表示通配符匹配。例如:“loadLibrary.”和“”表示通配符匹配,而“loadLibrary”和“ab”不表示。
可用权限列表见https://docs.oracle.com/javase/8/docs/api/java/lang/RuntimePermission.html
public final class RuntimePermission extends BasicPermission {
public RuntimePermission(String name) {
super(name);
}
public RuntimePermission(String name, String actions) {
super(name, actions);
}
}
SecurityManager
Java作为一种动态语言,会从网络上加载代码来运行。为了保护运行环境不受破坏,形成了“类加载器-安全管理器-访问控制器”保护体系。保证只有JDK代码才能直接访问系统资源、访问系统资源需要收到安全管理规则约束、JDK源码安全。
安全管理器是允许应用程序实现安全策略的类。它允许应用程序在执行可能的不安全或敏感操作之前确定操作是什么以及是否在允许执行操作的安全上下文中尝试。应用程序可以允许或禁止操作。
SecurityManager类包含许多方法,其中名称以单词检查开头。在这些方法执行某些可能敏感的操作之前,通过Java库中的各种方法调用这些方法。安全管理器将通过抛出异常来防止完成操作的机会。如果允许操作,则Security Manager例程只需返回,但如果不允许操作,则抛出SecurityException。此约定的唯一例外是ChecktopleVelWindow,它返回一个布尔值。调用这种检查方法通常如下所示:
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkXXX(argument,...);
}
当前的安全管理器由类系统中的SetSecurityManager方法设置。当前安全管理器是通过GetSecurityManager方法获取的。特殊方法checkPermission(java.security.permission)确定应授予或拒绝指定的访问请求。默认实现调用AccessController.checkPermission(perm);
。SecurityManager中每个其他检查方法的默认实现是调用SecurityManager.checkPermission
方法以确定调用线程是否具有执行所请求的操作的权限。
只有单个权限参数的审核域方法始终在当前执行的线程的上下文中执行安全检查。有时,应该在给定的上下文中进行的安全检查实际上需要从不同的上下文(例如,从工作线程中)完成。getSecurityContext
方法和checkPermission
方法包括上下文参数。核对级别除了权限之外还具有上下文对象的方法使得基于该上下文的访问决策,而不是当前执行线程的访问决策。因此,不同上下文中的代码可以调用该方法,传递权限和先前保存的上下文对象。getSecurityContext
方法返回当前调用上下文的“快照”。 (默认实现返回AccessControlContext对象。):
Object context = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) context = sm.getSecurityContext();
//另一个线程
if (sm != null) sm.checkPermission(permission, context);
权限属于以下类别:文件,套接字,网络,安全性,运行时,属性,AWT,反映和序列化。管理这些各种权限类别的类是java.io.FilePermission
,java.net.SocketPermission
,java.security.SecurityPermission
,java.lang.RuntimePermise
,java.util.PropertyPermission
,java.awt.AwtPermission
,java.lang.reflect.ReflectPermission
,和java.io.SerializablePermission
。
除了前两个(FilePermission 和 SocketPermission)之外的所有都是java.security.BasicPermission
的子类,它本身是顶级权限类java.security.Permission
的抽象子类。 BasicPermission 定义了包含遵循分层属性命名约定的名称的所有权限所需的功能(例如,“exitVM”、“setFactory”、“queuePrintJob”等)。
FilePermission 和 SocketPermission 是顶级权限类java.security.Permission
的子类。与 BasicPermission 子类直接从 Permission 而不是从 BasicPermission 中使用的类相比,此类具有更复杂的名称语法。例如,对于java.io.FilePermission
对象,权限名称是文件(或目录)的路径名。
一些权限类有一个“动作”列表,它告诉对象允许的动作。例如,对于java.io.FilePermission
对象,操作列表(例如“读、写”)指定为指定文件(或指定目录中的文件)授予哪些操作。
还有一个java.security.AllPermission
权限意味着所有权限。它的存在是为了简化可能需要执行需要全部(或大量)权限的多项任务的系统管理员的工作。