由于 Spring 源码非常多,博客中贴源码会占用大量篇幅,阅读困难。详细分析部分会以 commit 提交形式关联源码提交,画图例来说明源码整体逻辑。

Bean 生命周期主体逻辑

相关代码:Bean的基本创建流程、lazyInit、循环依赖

Bean 对象创建基本流程

通过最开始的关键时机点分析,我们知道Bean创建⼦流程⼊⼝在 AbstractApplicationContext#refresh() ⽅法的 finishBeanFactoryInitialization(beanFactory) 处执行的。

整体调用顺序如下:

1
2
3
4
5
6
7
8
AbstractApplicationContext#refresh
AbstractApplicationContext#finishBeanFactoryInitialization
DefaultListableBeanFactory#preInstantiateSingletons
AbstractBeanFactory#getBean
AbstractBeanFactory#doGetBean
DefaultSingletonBeanRegistry#getSingleton
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBean

我们从 AbstractApplicationContext#finishBeanFactoryInitialization 开始分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}

// Register a default embedded value resolver if no BeanFactoryPostProcessor
// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}

// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}

// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);

// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();

// 实例化所有立即加载的单例 Bean
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}

DefaultListableBeanFactory#preInstantiateSingletons

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}

// 存放 BeanNames
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

// 触发所有非延迟加载单例 Bean 的初始化,主要步骤是 getBean
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
// 合并父 BeanDefinition 对象
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 工厂 Bean:&BeanName
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else { // 实例化 Bean
getBean(beanName);
}
}
}

// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
smartInitialize.end();
}
}
}

AbstractBeanFactory#getBeanAbstractBeanFactory#doGetBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}

protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {

// 解析 BeanName,如果以 & 开头则去掉 & ,如果是别名则获取到真正的名字
String beanName = transformedBeanName(name);
Object beanInstance;

// 从缓存获取 Bean(注意点:三级缓存)
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
// 如果存在即返回
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 针对 FactoryBean 做处理
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

else {
// prototype 类型的 Bean 不支持循环依赖
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}

// 检查父工厂中是否已存在该对象
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}

// 标记
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}

StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
.tag("beanName", name);
try {
if (requiredType != null) {
beanCreation.tag("beanType", requiredType::toString);
}
// 合并父子 Bean 属性
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);

// 处理 dependsOn 配置
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}

// 创建 Bean 实例
// Create bean instance.
if (mbd.isSingleton()) {
// 单例
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建 Bean
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

else if (mbd.isPrototype()) {
// 原型
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
}
}
catch (BeansException ex) {
beanCreation.tag("exception", ex.getClass().toString());
beanCreation.tag("message", String.valueOf(ex.getMessage()));
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
finally {
beanCreation.end();
}
}

return adaptBeanInstance(name, beanInstance, requiredType);
}

DefaultSingletonBeanRegistry#getSingleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
// 丛单例池中获取 Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 是否正在销毁,异常
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// 验证完真正开始创建对象,先标识该 Bean 正在被创建
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 传进来的 Lambda 表达式: singletonFactory ,并调用 getObject
// (即上一步骤中的 () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { ... } })
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}

AbstractAutowireCapableBeanFactory#createBeanAbstractAutowireCapableBeanFactory#doCreateBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

if (logger.isTraceEnabled()) {
logger.trace("Creating instance of bean '" + beanName + "'");
}
// 拿到 mbd
RootBeanDefinition mbdToUse = mbd;

// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}

// Prepare method overrides.
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}

try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}

try {
// doCreateBean 方法进行创建 Bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 创建 Bean 实例,仅调用构造方法,尚未设置属性
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}

// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// 初始化 Bean 实例:属性填充
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性填充
populateBean(beanName, mbd, instanceWrapper);
// 调用初始化方法,引用 BeanPostProcessor 后置处理器
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}

if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}

return exposedObject;
}

lazy-init 加载机制的基本流程

在配置文件中设置 lazy-init

1
2
<bean id="person" class="io.github.linweiwang.bean.Person" lazy-init="true">
</bean>

DefaultListableBeanFactory#preInstantiateSingletonsif (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { 可知 lazy-init 的 Bean 在容器启动时是不会进行实例化的,通常是第⼀次进⾏ context.getBean() 时进⾏触发

整体调用顺序如下:

1
2
3
4
5
6
7
8
9
AbstractApplicationContext#getBean
DefaultListableBeanFactory#getBean(Class<T> requiredType)
DefaultListableBeanFactory#getBean
DefaultListableBeanFactory#resolveBean
DefaultListableBeanFactory#resolveNamedBean
DefaultListableBeanFactory#resolveNamedBean
AbstractBeanFactory#getBean
AbstractBeanFactory#doGetBean

DefaultListableBeanFactory#resolveNamedBean

1
2
3
4
5
6
7
8
9
10
@Nullable
private <T> NamedBeanHolder<T> resolveNamedBean(
String beanName, ResolvableType requiredType, @Nullable Object[] args) throws BeansException {
// AbstractBeanFactory#getBean 里面会调用 doGetBean
Object bean = getBean(beanName, null, args);
if (bean instanceof NullBean) {
return null;
}
return new NamedBeanHolder<T>(beanName, adaptBeanInstance(beanName, bean, requiredType.toClass()));
}

循环依赖基本流程

循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A 依赖于B,B 依赖于 C,C ⼜依赖于 A。

单例 Bean 构造器参数循环依赖(⽆法解决)只能拋出 BeanCurrentlyInCreationException 异常。

Prototype 原型 Bean循环依赖(⽆法解决)对于原型 Bean 的初始化过程中不论是通过构造器参数循环依赖还是通过 setXxx ⽅法产⽣循环依赖,Spring 都会直接报错处理。

单例 Bean 的Field 属性的循环依赖(可以解决),Spring 采⽤的是提前暴露对象的⽅法(也即三级缓存)。

示例

1
2
3
4
5
6
<bean id="a" class="io.github.linweiwang.bean.A">
<property name="b" ref="b"/>
</bean>
<bean id="b" class="io.github.linweiwang.bean.B">
<property name="a" ref="a"/>
</bean>

AbstractAutowiredCapableBeanFactory#doGetBean() 入手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 处理循环依赖:单例、允许循环依赖
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 加入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 初始化 Bean 实例:属性填充
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性填充
populateBean(beanName, mbd, instanceWrapper);
// 调用初始化方法,引用 BeanPostProcessor 后置处理器
exposedObject = initializeBean(beanName, exposedObject, mbd);
}

AbstractAutowiredCapableBeanFactory#populateBean

1
2
3
4
if (pvs != null) {
// 处理属性
applyPropertyValues(beanName, mbd, bw, pvs);
}

AbstractAutowiredCapableBeanFactory#applyPropertyValues

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String propertyName = pv.getName();
Object originalValue = pv.getValue();
if (originalValue == AutowiredPropertyMarker.INSTANCE) {
Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod();
if (writeMethod == null) {
throw new IllegalArgumentException("Autowire marker for property without write method: " + pv);
}
originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true);
}
// 真正去处理属性值
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;
boolean convertible = bw.isWritableProperty(propertyName) &&
!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
}

BeanDefinitionValueResolver#resolveValueIfNecessary

1
2
3
4
5
6
7
// 检查属性是否需要运行时引用另外一个 Bean
// We must check each value to see whether it requires a runtime reference
// to another bean to be resolved.
if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
return resolveReference(argName, ref);
}

BeanDefinitionValueResolver#resolveReference

1
2
3
// 获取 Bean
resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
bean = this.beanFactory.getBean(resolvedName);

继续调用 AbstarctBeanFactory#getBean 这时候要创建引用的 Bean 即 B。

整体调用流程为:

1
2
3
4
5
6
7
8
AbstractAutowiredCapableBeanFactory#doGetBean()
DefaultSingletonBeanRegistry#addSingletonFactory // 加入三级缓存
AbstractAutowiredCapableBeanFactory#populateBean // 属性填充
AbstractAutowiredCapableBeanFactory#applyPropertyValues
BeanDefinitionValueResolver#resolveValueIfNecessary
BeanDefinitionValueResolver#resolveReference
AbstarctBeanFactory#getBean

下面进入循环:即创建 B 的过程中,需要 A:

创建 B 的时候,发现需要依赖 A,此时 B 依然需经历上述步骤,为了获取 A 到了 this.beanFactory.getBean(resolvedName)

注意此时处于 B 的初始化过程,B 需要获取 A 。在这个过程中:由于此时 A 已经在三级缓存中了,在获取 A 的 doGetBean 过程中 Object sharedInstance = getSingleton(beanName); 会从缓存中获取 A。获取到之后返回给 B。

DefaultSingletonBeanRegistry#getSingleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 从一级缓存 singletonObjects 获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 从二级缓存 earlySingletonObjects 获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 从三级缓存 singletonFactories 获取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 从三级缓存 singletonFactories 转移至 二级缓存 earlySingletonObjects
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}

此时 B 已经获取到所有属性后已经完全装配了,在 DefaultSingletonBeanRegistry#getSingleton 中调用 addSingleton(beanName, singletonObject);

DefaultSingletonBeanRegistry#addSingleton

1
2
3
4
5
6
7
8
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}

此时 B 已经创建完成。 A 后续也获取到 B 完成创建。

从上述过程中可以分析构造方法的注入是无法循环依赖的,因为无法提前创建出 A 或者 B 放入缓存中。(new 一个对象即便不设置任何属性值也需要调用其构造方法)。

Bean 生命周期详细分析

所有增强器的创建和调用可以通过在每个方法打断点,通过分析堆栈信息来分析。

BeanFactory 后置增强

BeanFactory 的后置增强位于 AbstractApplicationContext#invokeBeanFactoryPostProcessors(beanFactory)

此部分对应 BeanFactory 和 Bean 的后置增强实现。首先分析下三种后置增强处理器的核心接口

  • BeanFactoryPostProcessor:BeanFactory 的后置增强处理器
    • BeanDefinitionRegistryPostProcessor
  • BeanPostProcessor:Bean 的后置增强处理器,可以改变 Bean
    • InstantiationAwareBeanPostProcessor
      • SmartInstantiationAwareBeanPostProcessor
    • MergedBeanDefinitionPostProcessor
    • DestructionAwareBeanPostProcessor
  • InitializingBean:Bean 初始化进行后续设置

自定义增强器实现上述结构,并 override 上述接口中的方法。 以某一个 Bean 的创建为例子。
相关代码:BeanFactory 和 Bean 的后置增强处理

Spring 底层默认会有一个 BeanDefinitionRegistryPostProcessor 类即:org.springframework.context.annotation.internalConfigurationAnnotationProcessor 类型为 ConfigurationClassPostProcessor 并且实现了 PriorityOrdered 接口。核心方法 ConfigurationClassPostProcessor#doProcessConfigurationClass,用于解析 @Configuration 配置类,参考代码 ConfigurationClassPostProcessor 解析配置类

注册 Bean 后置增强器

Bean 的后置增强的创建位于 AbstractApplicationContext#registerBeanPostProcessors(beanFactory) 此处仅作创建,不会调用增强器的方法,参考代码 Bean 后置增强器的创建

调用 Bean 的 SmartInstantiationAwareBeanPostProcessors#predictBeanType 后置增强器的方法

Bean 的后置增强的调用位于 AbstractApplicationContext#registerListeners() 。相关源码:SmartInstantiationAwareBeanPostProcessors#predictBeanType 的执行

初始化创建非懒加载的 Bean

AbstractApplicationContext#finishBeanFactoryInitialization(beanFactory) 相关源码: 初始化创建非懒加载的 Bean

AbstractAutowiredCapableBeanFactory#createBean

这一步真正的去创建 Bean,核心调用逻辑 doCreateBean ,相关源码: doCreateBean 核心逻辑

createBeanInstance

三次缓存的处理(后续详细分析)和 createBeanInstance

initializeBean

相关代码提交记录:https://github.com/linweiwang/spring-framework-5.3.33

IoC 容器三种启动方式

XML

JavaSE:

1
2
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml")
ApplicationContext context = new FileSystemXmlApplicationContext("C:/beans.xml")

JavaWeb

1
通过 web.xml 配置 ContextLoaderListener,指定 Spring 配置文件。

XML+注解

因为有 XML ,所以和纯 XML 启动方式一样

注解

JavaSE

1
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class)

JavaWeb

1
通过 web.xml 配置 ContextLoaderListener,指定 Spring 配置文件。

BeanFactory 是 Spring 框架中 IoC 容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ ApplicationContext 是它的⼀个⼦接⼝,所以 ApplicationContext 是具备 BeanFactory 提供的全部功能力的。
通常,我们称 BeanFactory 为 SpringIOC 的基础容器,ApplicationContext 是容器的⾼级接⼝,⽐
BeanFactory 要拥有更多的功能,⽐如说国际化⽀持和资源访问(XML、Java 配置类)等等。

下面以纯 XML 依赖原有 spring-research 来跟踪源码。

IoC 容器初始化主体流程

分析 new ClassPathXmlApplicationContext("spring-config.xml");

ClassPathXmlApplicationContext.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   // 创建 ClassPathXmlApplicationContext,加载 XML 中的定义信息,并且自动 refresh 容器(Context)
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
// 调用重载方法
this(new String[] {configLocation}, true, null);
}

public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {

// 初始化父类
super(parent);
// 设置配置文件
setConfigLocations(configLocations);
// refresh context
if (refresh) {
refresh();
}
}

进入 refresh() 方法,在父类 AbstractApplicationContext 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Override
public void refresh() throws BeansException, IllegalStateException {
// 对象锁
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

// 刷新前的预处理
// Prepare this context for refreshing.
prepareRefresh();

// 获取 BeanFactory: 默认实现是 DefaultListableBeanFactory
// 加载 BeanDefinition 并注册到 BeanDefinitionRegistry
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// BeanFactory 预准备工作
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// BeanFactory 准备工作完成后的后置处理,留给子类实现
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 实例化实现了 BeanFactoryPostProcessor 接口的 Bean,并调用该接口方法
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 BeanPostProcessor (Bean 的后置处理器),在创建 Bean 的前后执行
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();

// 初始化 MessageSource 组件:国际化、消息绑定、消息解析等
// Initialize message source for this context.
initMessageSource();

// 初始化事件派发器
// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// 初始化其他特殊的 Bean,在容器刷新的时候子类自定义实现:如创建 Tomcat、Jetty 等 Web 服务器
// Initialize other special beans in specific context subclasses.
onRefresh();

// 注册应用监听器(即实现了 ApplicationListener 接口的 Bean)
// Check for listener beans and register them.
registerListeners();

// 初始化创建非懒加载的单例 Bean、填充属性、调用初始化方法( afterPropertiesSet,init-method 等)、调用 BeanPostProcessor 后置处理器
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// 完成 context 刷新,调用 LifecycleProcessor 的 onRefresh 方法并发布 ContextRefreshedEvent
// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}

整体流程如下:

1 刷新前的预处理: prepareRefresh();
 主要是一些准备工作设置其启动日期和活动标志以及执行一些属性的初始化。
 
 2 初始化 BeanFactory: obtainFreshBeanFactory();

  • 如果有旧的 BeanFactory 就删除并创建新的 BeanFactory
  • 解析所有的 Spring 配置文件,将配置文件中定义的 bean 封装成 BeanDefinition,加载到BeanFactory 中(这里只注册,不会进行 Bean 的实例化)

3 BeanFactory 预准备工作:prepareBeanFactory(beanFactory);
配置 BeanFactory 的标准上下文特征,例如上下文的 ClassLoader、后置处理器等。

4 BeanFactory 准备工作完成后的后置处理,留给子类实现:postProcessBeanFactory(beanFactory);
空方法,如果子类需要,自己去实现

5 调用 Bean 工厂后置处理器:invokeBeanFactoryPostProcessors(beanFactory);

实例化和调用所有BeanFactoryPostProcessor,完成类的扫描、解析和注册。
BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它。

6 注册 BeanPostProcesso:registerBeanPostProcessors(beanFactory);
所有实现了 BeanPostProcessor 接口的类注册到 BeanFactory 中。

7 初始化 MessageSource 组件:initMessageSource();
初始化MessageSource组件(做国际化功能;消息绑定,消息解析)

8 初始化事件派发器:initApplicationEventMulticaster();
初始化应用的事件派发/广播器 ApplicationEventMulticaster。

9 初始化其他特殊的 Bean:onRefresh();
空方法,模板设计模式;子类重写该方法并在容器刷新的时候自定义逻辑。
例:SpringBoot 在 onRefresh() 完成内置 Tomcat 的创建及启动

10 注册应用监听器:registerListeners();
向事件分发器注册硬编码设置的 ApplicationListener,向事件分发器注册一个 IoC 中的事件监听器(并不实例化)

11 初始化创建非懒加载的单例 Bean:finishBeanFactoryInitialization(beanFactory);
初始化创建非懒加载的单例 Bean、填充属性、调用初始化方法( afterPropertiesSet,init-method 等)、调用 BeanPostProcessor 后置处理器,是整个 Spring IoC 核心中的核心。

12 完成 context 刷新:finishRefresh();
完成 context 刷新,调用 LifecycleProcessor 的 onRefresh 方法并发布 ContextRefreshedEvent

获取 BeanFactory 子流程

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

AbstractApplicationContext.java

1
2
3
4
5
6
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 对 BeanFactory 进行刷新操作,默认实现 AbstractRefreshableApplicationContext#refreshBeanFactory
refreshBeanFactory();
// 返回上一步 beanFactory,默认实现 AbstractRefreshableApplicationContext#getBeanFactory
return getBeanFactory();
}

AbstractRefreshableApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
protected final void refreshBeanFactory() throws BeansException {
// 判断是否已有 BeanFactory
if (hasBeanFactory()) {
// 销毁 Bean
destroyBeans();
// 关闭 BeanFactory
closeBeanFactory();
}
try {
// 实例化 DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 设置序列化 ID
beanFactory.setSerializationId(getId());
// 自定义 BeanFactory 的一些属性:allowBeanDefinitionOverriding、allowCircularReferences
// allowBeanDefinitionOverriding 是否允许覆盖
// allowCircularReferences 是否允许循环依赖
customizeBeanFactory(beanFactory);
// 加载应用中的 BeanFactory
loadBeanDefinitions(beanFactory);
// 赋值给当前 beanFactory 属性
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
DefaultListableBeanFactory beanFactory = this.beanFactory;
if (beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
return beanFactory;
}

BeanDefinition 加载解析及注册子流程

继续分析 AbstractRefreshableApplicationContext#refreshBeanFactory 中的 loadBeanDefinitions 的实现方法在 AbstractXmlApplicationContext#loadBeanDefinitions 中(若是注解在 AnnotationConfigWebApplicationContext)

AbstractXmlApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 给指定的 BeanFactory 创建一个 XmlBeanDefinitionReader 进行读取和解析 XML
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// 给 XmlBeanDefinitionReader 设置上下文信息
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// 提供给子类上西安的模板方法:自定义初始化策略
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
// 真正的去加载 BeanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 从 Resource 资源对象加载 BeanDefinitions
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 从 XML 配置文件加载 BeanDefinition 对象
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

reader.loadBeanDefinitions(configLocations); 中调用了 AbstractBeanDefinitionReader#loadBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
// 如果有多个配置文件,循环读取加载,并统计数量
for (String location : locations) {
count += loadBeanDefinitions(location);
}
return count;
}

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 获取上下文的 ResourceLoader (资源加载器)
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}

// 判断资源加载器是否为 ResourcePatternResolver 类型
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 统一加载转换为 Resource 对象
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 通过 Resource 对象加载 BeanDefinitions
int count = loadBeanDefinitions(resources);
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// 否则以绝对路径转换为 Resource 对象
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}

@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
count += loadBeanDefinitions(resource);
}
return count;
}

@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
for (Resource resource : resources) {
// 加载
count += loadBeanDefinitions(resource);
}
return count;
}

loadBeanDefinitions(resource); 调用了 XmlBeanDefinitionReader#loadBeanDefinitions 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}

Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}

try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
// 把 XML 文件流封装为 InputSource 对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 执行加载逻辑
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {

try {
// 读取 XML 信息,将 XML 中信息保存到 Document 对象中
Document doc = doLoadDocument(inputSource, resource);
// 再解析 Document 对象,封装为 BeanDefinition 对象进行注册
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 获取已有 BeanDefinition 的数量
int countBefore = getRegistry().getBeanDefinitionCount();
// 注册 BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 计算出新注册的 BeanDefinition 数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}

documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 会进入 DefaultBeanDefinitionDocumentReader#registerBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}

protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);

if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}

preProcessXml(root);
// 真正解析 XML
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

this.delegate = parent;
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 解析默认标签元素:"import", "alias", "bean"
parseDefaultElement(ele, delegate);
}
else {
// 解析自定义标签
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 解析自定义标签
delegate.parseCustomElement(root);
}
}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// import 标签
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// alias 标签
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// bean 标签:着重分析
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 嵌套 bean 标签
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 解析 bean 标签为 BeanDefinitionHolder 里面持有 BeanDefinition
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 若有必要装饰 bdHolder (bean 标签内有自定义标签的情况下)
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 完成 BeanDefinition 的注册
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); 调用了 BeanDefinitionReaderUtils#registerBeanDefinition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {

// 注册
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}

registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); 调用了 DefaultListableBeanFactory#registerBeanDefinition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {

Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");

if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}

BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
// 注册逻辑
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}

if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}

整体调用链如下

1
2
3
4
5
6
7
8
9
10
11
12
13
AbstractRefreshableApplicationContext#refreshBeanFactory
AbstractXmlApplicationContext#loadBeanDefinitions
AbstractBeanDefinitionReader#loadBeanDefinitions // 加载 BeanDefinition
XmlBeanDefinitionReader#loadBeanDefinitions
XmlBeanDefinitionReader#doLoadBeanDefinitions // 读取 XML 为 Document
XmlBeanDefinitionReader#registerBeanDefinitions // 真正开始注册
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
DefaultBeanDefinitionDocumentReader#parseDefaultElement
DefaultBeanDefinitionDocumentReader#processBeanDefinition
BeanDefinitionReaderUtils#registerBeanDefinition
DefaultListableBeanFactory#registerBeanDefinition

注意事项:

  1. 使用 2024-03-14 发布的 Spring 5.3.33 版本
  2. IDE 工具使用了 Intellij IDEA,同时为了简化不必要的内容没单独配置 Gradle 环境
  3. JDK 版本采用 Eclipse Temurin 1.8/11 均可

下载源码

下载 SpringFramework 源码,本次选择 5.3.33 版本,发布日期 2024-03-14,通过 Intellij IDEA 打开。

Gihub地址: https://github.com/spring-projects/spring-framework/releases/tag/v5.3.33

配置 Gradle 环境

由于国内下载 gradle 比较慢可以考虑使用腾讯云镜像源,在 /gradle/wrapper/gradle-wrapper.properties 中修改 distributionUrl

1
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-7.5.1-bin.zip

同时仓库地址可修改为阿里云,在 build.gradle 中配置阿里云镜像

1
2
3
4
5
6
7
8
9
10
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/spring/'}
maven { url 'https://maven.aliyun.com/repository/jcenter/'}
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/'}
mavenLocal()
mavenCentral()
}
}

在 Gradle 先 Reload All Gradle Projects,再执行 build 命令进行编译。

期间碰到报错信息,报错详情:

1
2
spring-core\src\main\java\org\springframework\core\CoroutinesUtils.java:74: 警告: [deprecation] AccessibleObject中的isAccessible()已过时
if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) {

解决方法:在 org.springframework.core.CoroutinesUtils#invokeSuspendingFunction 加上 @SuppressWarnings("deprecation")

若控制台输出中文乱码,Intellij IDEA 中设置 vm.properties

1
-Dfile.encoding=UTF-8

新建测试 Module

通过 gradle 新建一个 module,取名为 spring-research

module 新增完成后会自动在 settings.gradle 中引用 spring-research 模块,若没有自动引入可以手动加入

1
include 'spring-research'

在 spring-research 中的 build.gradle 中引入 spring-context

1
2
3
4
5
6
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
// 引入 spring-context 依赖
api(project(":spring-context"))
}

可选项:此外由于 Spring 源码工程配置了 checkStyle,在做测试类的时候有些方法不能够满足 Spring 的规范要求(由于使用 Intellij IDEA 自动生成或者格式化的代码不满足要求,比如 tab indent,实体类 this 指向,包括 import 隔行等规则),可以通过在 src/checkstyle/checkstyle.xml 中添加过滤规则:

1
2
3
<module name="BeforeExecutionExclusionFileFilter">  
<property name="fileNamePattern" value="^.*\\spring\-research\\.*$"/>
</module>

新建实体类 Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.github.linweiwang.bean;

public class Person {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

在 resources.properties 中新建 spring-config.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="io.github.linweiwang.bean.Person">
<property name="name" value="王"/>
<property name="age" value="18"/>
</bean>
</beans>

在 Main 中调用 SpringContext 获取 Bean 的实例

1
2
3
4
5
6
7
8
9
10
11
12
package io.github.linweiwang;

import io.github.linweiwang.bean.Person;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Person person = context.getBean(Person.class);
System.out.println(person);
}
}

运行成功即环境搭建成功!

1 垃圾回收相关算法

垃圾回收器首先要做的就是,判断一个对象是存活状态还是死亡状态,死亡的对象将会被标识为垃圾数据并等待收集器进行清除。

  • 判断一个对象是否为死亡状态的常用算法有两个:引用计数器算法可达性分析算法
  • 垃圾回收的常见算法有以下几个:标记-清除算法标记-复制算法、标记-整理算法。

1.1 引用计数算法(Reference Counting)

在创建对象时关联一个与之相对应的计数器,当此对象被使用时加 1,相反销毁时 -1。当此计数器为 0 时,则表示此对象未使用,可以被垃圾收集器回收。

引用计数算法的优缺点很明显,其优点是垃圾回收比较及时,实时性比较高,只要对象计数器为 0,则可以直接进行回收操作;而缺点是无法解决循环引用的问题。

循环引用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RCTest {
static class Test {
public Test object = null;
}

public static void main(String[] args) {
Test object1 = new Test();
Test object2 = new Test();

object1.object = object2;
object2.object = object1;

object1 = null;
object2 = null;
}
}

1.2 可达性分析算法(Reachability Analysis)

指从对象的起点(GC Roots)开始向下搜索,如果对象到 GC Roots 没有任何引用链相连时,也就是说此对象到 GC Roots 不可达时,则表示此对象可以被垃圾回收器所回收。

GC Root

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 栈帧中的局部变量表中的 reference 引用所引用的对象
  • 方法区中 static 静态引用的对象
  • 方法区中 final 常量引用的对象
  • 本地方法栈中 JNI(即Native方法) 引用的对象
  • Java虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError) 等,还有系统类加载器
  • 所有被同步锁持有的对象,比如被 synchronized 持有的对象
  • 反映Java虚拟机内部情况的 JMXBean、 JVMTI 中注册的回调、 本地代码缓存等

finalize()

当使用可达性分析判断一个对象不可达时,并不会直接标识这个对象为死亡状态,而是先将它标记为“待死亡”状态再进行一次校验。

校验的内容就是此对象是否重写了 finalize() 方法,如果该对象重写了 finalize() 方法,那么这个对象将会被存入到 F-Queue 队列中,等待 JVM 的 Finalizer 线程去执行重写的 finalize() 方法,在这个方法中如果此对象将自己赋值给某个类变量时,则表示此对象已经被引用了。因此不能被标识为死亡状态,其他情况则会被标识为死亡状态。

四种引用类型

  • 强引用(Strong Reference):强引用是使用最普遍的引用。如果一个对象具有强引用,即便发生OOM那垃圾回收器绝不会回收它。
  • 软引用(Soft Reference):如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  • 弱引用(Weak Reference):用来描述那些非必须对象, 但是它的强度比软引用更弱一些, 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作, 无论当前内存是否足够, 都会回收掉只 被弱引用关联的对象。 在JDK 1.2版之后提供了 WeakReference 类来实现弱引用。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  • 虚引用(Phantom Reference):如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用必须和引用队列 (ReferenceQueue)联合使用。

1.3 标记-清除算法(Mark-Sweep)

标记-清除算法属于最早的垃圾回收算法,它是由标记阶段和清除阶段构成的。标记阶段会给所有的存活对象做上标记,而清除阶段会把没有被标记的死亡对象进行回收。而标记的判断方法就是前面讲的引用计数算法和可达性分析算法。

问题: 产生内存空间的碎片化,标记-清除算法执行完成之后会产生大量的不连续内存,这样当程序需要分配一个大对象时,因为没有足够的连续内存而导致需要提前触发一次垃圾回收动作。

1.4 标记-复制算法(Mark-Copy)

将内存分为大小相同的两块区域,每次只使用其中的一块区域,这样在进行垃圾回收时就可以直接将存活的东西复制到新的内存上,然后再把另一块内存全部清理掉。

问题:内存的可用率大幅降低:虽然可以解决内存碎片的问题,但因为需要将内存分为大小相同的两块内存,那么内存的实际可用量其实只有原来的一半。

1.5 标记-整理算法(Mark-Compact)

由两个阶段组成的:标记阶段和整理阶段。标记阶段和标记-清除算法的标记阶段一样,整理阶段不是直接对内存进行清除,而是把所有存活的对象移动到内存的一端,然后把另一端的所有死亡对象全部清除。

2 垃圾回收器

HotSpot 中常使用的垃圾收集器主要包括 7 个:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS 和 G1(Garbage First)收集器。

2.1 分代收集理论

收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数) 分配到不同的区域之中存储。

在 Java 堆划分出不同的区域之后,垃圾收集器可以每次只回收其中某一个或者某些部分的区域,所以有了 Minor GC 、Major GC、Full GC 这样的回收类型的划分;也能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了“标记-复制算法”“标记-清除算 法”“标记-整理算法”等针对性的垃圾收集算法。

2.2 垃圾收集器分类

  • 串行回收器(Serial):串行垃圾回收是为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,不适合交互性强的服务器环境
  • 并行回收器(Parallel):多个垃圾收集器线程并行工作,同样会暂停用户线程,适用于科学计算、大数据后台处理等多交互场景
  • 并发回收器(CMS):用户线程和垃圾回收线程同时执行,不一定是并行的,可能是交替执行,可能一边垃圾回收,一边运行应用线程,不需要停顿用户线程,互联网应用程序中经常使用,适用对响应时间有要求的场景
  • G1回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发地对其进行垃圾回收

  • 串行回收器:Serial、Serial Old
  • 并行回收器:ParNew、Parallel Scavenge、Parallel Old
  • 并发回收器:CMS
  • 新生代垃圾收集器:Serial 、 ParNew 、Parallel Scavenge
  • 老年代垃圾收集器:Serial Old 、 Parallel Old 、CMS
  • 整堆收集器:G1、ZGC、Shenandoah
  • 特殊:Epsilon 不进行垃圾回收

组合:

  • Serial + Serial Old
  • Serial + CMS
  • ParNew + Serial Old
  • ParNew + CMS
  • Parallel Scavenge + Serial Old
  • Parallel Scavenge + Parallel Old
  • G1
  • ZGC
  • Epsilon
  • Shenandoah

其中: Serial Old 作为 CMS 出现 Concurrent Mode Failure 失败的后备预案

JDK8 废弃:Serial + CMS、ParNew + Serial Old

JDK8 默认:Parallel Scavenge + ParallelOld

JDK9 移除:Serial + CMS、ParNew + Serial Old

JDK9 默认:G1

JDK11 新增:Epsilon (实验)

JDK11 新增:ZGC (实验)

JDK12 新增:Shenandoah(实验)

JDK13 更新:ZGC 支持的最大堆大小从 4TB 增加到 16TB

JDK14 更新:ZGC 支持在 Windows 上作为实验功能

JDK14 弃用:Parallel Scavenge + Parallel Old

JDK14 移除:CMS

JDK15 正式:ZGC 不再标记为实验功能可以在生产环境中使用

GC性能指标

  • 吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
  • 暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间
  • 内存占用:Java 堆所占内存的大小
  • 收集频率:垃圾收集的频次

2.3 Serial 收集器

单线程收集器,“单线程”的意义不仅仅说明它只会使用一个CPU或一个收集线程去完成垃圾收集工作;更重要的是它在垃圾收集的时候,必须暂停其他工作线程(Stop The World),直到垃圾收集完毕;

对于单CPU环境来说,由于Serial收集器没有线程间的交互,专心做垃圾收集自然可以做获得最高的垃圾收集效率

使用方式:-XX:+UseSerialGC

(图源自《深入理解Java虚拟机(第2版)》)

2.4 ParNew 收集器

ParNew 收集器实质上是 Serial 收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括 Serial 收集器可用的所有控制参数、 收集算法、 Stop The World、 对象分配规则、 回收策略等都与 Serial 收集器完全一致。

ParNew 收集器在单 CPU 服务器上的垃圾收集效率绝对不会比 Serial 收集器高;但是在多 CPU 服务器上,效果会明显比 Serial 好。

使用方式:-XX:+UseParNewGC
设置线程数: XX:ParllGCThreads

(图源自《深入理解Java虚拟机(第2版)》)

2.5 Parallel Scavenge 收集器

和 ParNew 收集器类似,是一个新生代收集器。使用复制算法的并行多线程收集器。Parallel Scavenge 是 Java1.8 默认的收集器,特点是并行的多线程回收,以吞吐量(Throughput)优先。适合后台运算,交互不多的任务,如批量处理,订单处理,科学计算等。

  • Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量
  • 自适应调节策略:自动指定年轻代、Eden、Suvisor区的比例

使用方式:-XX:+UseParallelGC
分别是控制最大垃圾收集停顿时间: -XX:MaxGCPauseMillis
吞吐量大小 -XX:GCTimeRatio
设置年轻代线程数 XX:ParllGCThreads
自适应调节年轻代、Eden、Suvisor区的比例 -XX:+UseAdaptiveSizePolicy

2.6 Serial Old 收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。 这个收集器的主要意义也是供客户端模式下的 HotSpot 虚拟机使用。

  • 在 JDK1.5 及之前,与 Parallel Scavenge 收集器搭配使用(JDK1.6 后有 Parallel Old 收集器可搭配)
  • 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用

使用方式:-XX:+UseSerialGC

另外:Parallel Scavenge 收集器架构中本身有 PS MarkSweep 收集器来进行老年代收集,并非直接调用 Serial Old 收集器,但 PS MarkSweep 收集器与 Serial Old 的实现几乎是一样的,所以在官方的许多资料中都是直接以 Serial Old 代替 PS MarkSweep 进行讲解。

(图源自《深入理解Java虚拟机(第2版)》)

2.7 Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。

JDK1.6 及之后用来代替老年代的 Serial Old 收集器;(在此之前,如果新生代选择了 Parallel Scavenge 收集器,老年代除了 Serial Old(PS MarkSweep) 收集器以外别无选择,其他表现良好的老年代收集器,如 CMS 无法与它配合工作。)

在Server模式,多CPU的情况下;在注重吞吐量以及CPU资源敏感的场景,就有了 Parallel Scavenge 加 Parallel Old 收集器的应用组合;

使用方式:-XX:+UseParallelOldGC

(图源自《深入理解Java虚拟机(第2版)》)

2.8 CMS 收集器

CMS(concurrent mark sweep)是以获取最短垃圾收集停顿时间为目标的收集器,CMS 收集器的关注点尽可能缩短垃圾收集时用户线程的停顿时间,停顿时间越短就越适合与用户交互的程序验,CMS 收集器使用的算法是标记-清除算法实现的;

CMS垃圾收集过程

1)初始标记(Initial-Mark)阶段:这个阶段程序所有的工作线程都将会因为 Stop-the-Wold 机制而出现短暂的的暂停,这个阶段的主要任务标记处 GC Roots 能够关联到的对象。一旦标记完成后就恢复之前被暂停的的所有应用。 由于直接关联对象比较小,所以这里的操作速度非常快。

2)并发标记(Concurrent-Mark)阶段:从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要暂停用户线程,用户线程可以与垃圾回收器一起运行。

3)重新标记(Remark)阶段:由于并发标记阶段,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间因为用户继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常比初始标记阶段长一些,但也远比并发标记阶段时间短。

4)并发清除(Concurrent-Sweep)阶段: 此阶段清理删除掉标记判断已经死亡的对象,并释放内存空间。由于不需要移动存活对象,所以这个阶段可以与用户线程同时并发运行。

(图源自《深入理解Java虚拟机(第2版)》)

并发可达性分析与三色标记(Tri-color Marking)

可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析。 垃圾回收器的工作流程大体如下:

  1. 标记出哪些对象是存活的,哪些是垃圾(可回收);
  2. 进行回收(清除/复制/整理),如果有移动过对象(复制/整理),还需要更新引用。

三色标记(Tri-color Marking)作为工具来辅助推导,把遍历对象图过程中遇到的对象,按照“是否访问过”这个条件标记成以下三种颜色:

  • 白色:尚未访问过。
  • 黑色:本对象已访问过,而且本对象引用到的其他对象 也全部访问过了。
  • 灰色:本对象已访问过,但是本对象引用到的其他对象尚未全部访问完。全部访问后,会转换为黑色。

假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:

  1. 初始时,所有对象都在 【白色集合】中;
  2. 将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
  3. 从灰色集合中获取对象:
    • 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中
    • 将本对象 挪到 【黑色集合】里面
  4. 重复步骤3,直至【灰色集合】为空时结束
  5. 结束后,仍在【白色集合】的对象即为 GC Roots 不可达,可以进行回收

当 Stop The World 时,对象间的引用是不会发生变化的,可以轻松完成标记。 而当需要支持并发标记时,即标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。

多标-浮动垃圾

假设已经遍历到E(变为灰色了),此时应用执行了 objD.fieldE = null

此刻之后,对象E/F/G是“应该”被回收的。然而因为 E 已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存。

这部分本应该回收但是没 有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响应用程序的正确性,只是需要等到下一轮垃圾回收中才被清除。

漏标

假设GC线程已经遍历到E(变为灰色了),此时应用线程先执行了:

1
2
3
var G = objE.fieldG;
objE.fieldG = null; // 灰色E 断开引用 白色G
objD.fieldG = G; // 黑色D 引用 白色G

此时切回 GC 线程继续跑,因为 E 已经没有对 G 的引用了,所以不会将 G 放到灰色集合;尽管因为 D 重新引用了 G,但因为D已经是黑色了,不会再重新做遍历处理。

最终导致的结果是:G 会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的。

漏标只有同时满足以下两个条件时才会发生:

  • 灰色对象断开了白色对象的引用:即灰色对象原来成员变量的引用发生了变化
  • 黑色对象重新引用了该白色对象:即黑色对象成员变量增加了新的引用

从代码的角度看:

1
2
3
var G = objE.fieldG;   // 1. 读取对象 E 的成员变量 fieldG 的引用值(对象 G)
objE.fieldG = null; // 2. 对象 E 往其成员变量 fieldG 写入 null 值
objD.fieldG = G; // 3. 对象 D 往其成员变量 fieldG 写入 对象G

只要在上面这三步中的任意一步将对象 G 记录起来,然后作为灰色对象再进行遍历即可。
比如放到一个特定的集合,等初始的 GC Roots 遍历完(并发标记),该集合的对象遍历即可(重新标记)。

重新标记是需要 STW 的,因为应用程序一直在跑的话,该集合可能会一直增加新的对象,导致永远都跑不完。当然,并发标记期间也可以将该集合中的大部分先跑了,从而缩短重新标记STW的时间,这个是优化问题了。

CMS收集器三个缺点

  1. CMS 收集器对 CPU 资源非常敏感。CMS 默认启动的回收线程数是 (处理器核心数量 + 3) / 4
  2. CMS 收集器无法处理浮动垃圾,可能出现 Concurrent Mode Failure 失败而导致另一次 Full GC 的产生
  3. 空间碎片:CMS是一款基于标记-清除算法实现的收集器,所有会有空间碎片的现象
    • 开关参数 -XX:+UseCMS-CompactAtFullCollection (默认是开启的,JDK 9开始废弃),用于在 CMS 收集器不得不进行 Full GC 时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,是无法并发的
    • 另外一个参数 -XX:CMSFullGCsBeforeCompaction(JDK 9开始废弃),这个参数的作用是要求 CMS 收集器在执行过若干次(数量由参数值决定) 不整理空间的 Full GC 之后,下一次进入 Full GC 前会先进行碎片整理(默认值为0,表 示每次进入Full GC时都进行碎片整理)。

2.9 G1 收集器

Garbage First是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。

特点

  • G1 把内存划分为多个独立的区域 Region
  • G1 仍然保留分代思想,保留了新生代和老年代,但他们不再是物理隔离,而是一部分Region的集合
  • G1 能够充分利用多CPU、多核环境硬件优势,尽量缩短 STW 的时间
  • G1 整体整体采用标记整理算法,局部是采用复制算法,不会产生内存碎片
  • G1 的停顿可预测,能够明确指定在一个时间段内,消耗在垃圾收集上的时间不超过设置时间
  • G1 跟踪各个Region里面垃圾的价值大小,会维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限事件内高效的收集垃圾

Region区域

G1 不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个独立区域(Region),每一个 Region 都可以根据需要扮演新生代的 Eden 空间、 Survivor 空间、老年代空间。

① 使用G1收集器时,它将整个 Java 堆划分成约2048个大小相同的独立 Region 块,每个 Region 块大小根据堆空间的实际大小而定,为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。
② 虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。
③ G1垃圾收集器还增加了一种新的内存区域,叫做 Humongous 内存区域。主要用于存储大对象,如果超过 1.5 个 Region,就放到 H 区。一般被视为老年代。

G1 GC过程
G1 提供了两种 GC 模式,Young GC 和 Mixed GC,两种均是完全 Stop The World 的。

  • Young GC:选定所有年轻代里的 Region,通过控制年轻代的 Region 个数,即年轻代内存大小,来控制 Young GC 的时间开销。
  • Mixed GC:选定所有年轻代里的 Region,外加根据 global concurrent marking 统计得出收集收益高的若干老年代 Region。在用户指定的开销目标范围内尽可能选择收益高的老年代 Region。

初始标记(Initial Mark,STW):和 CMS 一样只标记 GC Roots 直接关联的对象
并发标记(Concurrent Mark):进行 GC Roots Traceing 过程
最终标记(Remark,STW):修正并发标记期间,因程序运行导致发生变化的那一部分对象
筛选回收(Cleanup,STW):根据时间来进行价值最大化收集

(图源自《深入理解Java虚拟机(第2版)》)

G1 Young GC

  • 执行 YoungGC 前:堆分为大约2000个区域。最小大小为 1Mb,最大大小为 32Mb。蓝色区域保存老年代对象,绿色区域保存年轻对象。
  • 执行 YoungGC 时:将存活的对象(即复制或移动)到一个或多个幸存者区域。如果满足老化阈值,则某些对象将被提升到老年代区域。
  • 执行 YoungGC 后:最近升级的对象以深蓝色显示。幸存者区域为绿色。

总结:

  • 堆是单个内存空间,分为多个区域。
  • 年轻代内存由一组非连续区域组成。
  • 年轻一代的垃圾收集器或年轻的 GC 出现 STW 。将停止所有应用程序线程以进行操作。
  • 年轻的 GC 使用多个线程并行完成。
  • 将活动对象复制到新的幸存者或老年代的地区。

G1 Mix GC

  • 初始标记阶段(Initial Marking,STW):存活的对象的初始标记背负在年轻的垃圾收集器上。在日志中,此标记为 GC pause (young)(inital-mark) 。
  • 并发标记阶段(Concurrent Marking):如果找到空白区域(如“ X”所示),则在 Remark 阶段将其立即删除。另外,计算确定活跃度的信息。
  • 最终标记阶段(Remark,STW):空区域将被删除并回收。现在可以计算所有区域的区域活跃度。
  • 筛选回收阶段/复制清理阶段(Copying/Cleanup,STW): G1选择“活度”最低的区域,这些区域可以被最快地收集。然后与年轻的GC同时收集这些区域。这在日志中表示为[GC pause (mixed)] 。因此,年轻代和老年代都是同时收集的。
  • 筛选回收阶段-(复制/清理)阶段之后:选定的区域已被收集并压缩为图中所示的深蓝色区域和深绿色区域。

总结

  • 并发标记阶段
    • 活动信息是在应用程序运行时同时计算的。
    • 该活动信息标识在疏散暂停期间最适合回收的区域。
    • 像 CMS 中没有清扫阶段。
  • 最终标记阶段
    • 使用开始快照(SATB)算法,该算法比 CMS 使用的算法快得多。
    • 完全回收空区域。
  • 筛选回收阶段
    • 同时回收年轻一代和老一代。
    • 老年代地区是根据其活跃度来选择的

2.10 ZGC、Epsilon、Shenandoah

Epsilon

JDK11 新增的GC,一个处理内存分配但不实现任何实际内存回收机制的GC,一旦可用的Java堆耗尽,JVM将关闭。如果有 System.gc() 调用,实际上什么也不会发生, 因为没有内存回收,这个实现可能会警告用户尝试强制GC是徒劳。

使用方式:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC

用途:

  • 性能测试(它可以帮助过滤掉 GC 引起的性能假象)
  • 内存压力测试(例如知道测试用例应该分配不超过 1GB 的内存,我们可以使用 -XX:+UnlockExperimentalVMOptions –XX:+UseEpsilonGC -Xmx1g,如果程序有问题则程序会崩溃)
  • 非常短的 JOB 任务(GC 清理可能会让时间更长)
  • VM 接口测试
  • Last-drop 延迟&吞吐改进

ZGC

ZGC的设计目标是:支持 TB 级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于 DRAM 和冷对象置于 NVMe 闪存),或压缩堆。

  • JDK11 新增:ZGC (实验) -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
  • JDK13 更新:ZGC 支持的最大堆大小从 4TB 增加到 16TB
  • JDK14 更新:ZGC 支持在 Windows 上作为实验功能
  • JDK15 正式:ZGC 不再标记为实验功能可以在生产环境中使用 -XX:+UseZGC

Shenandoah

JDK12 新增的GC,低暂停时间垃圾收集器(实验性)Shenandoah的暂停时间与堆大小无关,这意味着无论堆是200MB还是200GB,都将具有相同的一致暂停时间。它的 evacuation 阶段工作能通过与正在运行中 Java 工作线程同时进行(即并发,concurrent),从而减少 GC 的停顿时间。

使用方法: -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

某些 JDK 会在 build 时通过 --with-jvm-features=-shenandoahgc 来禁用 Shenandoah。

内容为之前学习笔记整理,如果有问题请指正!(引用图片已标记来源)

1 类的加载

类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。

类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载器并不需要等到某个类被首次主动使用时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了 .class 文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

加载 .class 文件的方式:

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载 .class 文件
  • 从专有数据库中提取 .class 文件
  • 将Java源文件动态编译为 .class 文件

执行顺序

  • 类加载子系统负责从文件系统或是网络中加载 .class 文件,class文件在文件开头有特定的文件标识。
  • 把加载后的class类信息存放于方法区,除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定;
  • 如果调用构造器实例化对象,则该对象存放在堆区;

2 类的生命周期

  • 加载(Loading)
  • 连接(Linking)
    • 验证(Verification)
    • 准备(Preparation)
    • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading)

加载阶段(Loading)

此阶段用于查到相应的类(通过类名进行查找)并将此类的字节流转换为方法区运行时的数据结构,然后再在内存中生成一个能代表此类的 java.lang.Class 对象,作为其他数据访问的入口。

(需要注意的是加载阶段和连接阶段的部分动作有可能是交叉执行的,比如一部分字节码文件格式的验证,在加载阶段还未完成时就已经开始验证了。)

  • 预加载:虚拟机启动时加载,加载的是 JAVA_HOME/lib/ 下的 rt.jar 下的 .class 文件 (可以写一个空的 main 函数,设置虚拟机参数为 -XX:+TraceClassLoading 来获取类加载信息)
  • 运行时加载:虚拟机在用到一个 .class 文件的时候,会先去内存中查看一下这个 .class 文件有没有被加载,如果没有就会按照类的全限定名来加载这个类。
    • 获取 .class 文件的二进制流 (没有规定二进制字节流要必须从哪里来或者怎么来,所以留下了可扩展的空间)
    • 将类信息、静态变量、字节码、常量这些 .class 文件中的内容放入方法区中
    • 在内存中生成一个代表这个 .class 文件的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。一般这个Class是在堆里的,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的

验证阶段(Verification)

此步骤主要是为了验证字节码的安全性,如果不做安全校验的话可能会载入非安全或有错误的字节码,从而导致系统崩溃,它是 JVM 自我保护的一项重要举措。

验证的主要动作大概有以下几个:

  • 文件格式校验包括常量池中的常量类型、Class 文件的各个部分是否被删除或被追加了其他信息等;
  • 元数据校验包括父类正确性校验(检查父类是否有被 final 修饰)、抽象类校验等;
  • 字节码校验,此步骤最为关键和复杂,主要用于校验程序中的语义是否合法且符合逻辑;
  • 符号引用校验,对类自身以外比如常量池中的各种符号引用的信息进行匹配性校验。

准备阶段(Preparation)

此阶段是用来初始化并为类中定义的静态变量分配内存的,这些静态变量会被分配到方法区上。

HotSpot 虚拟机在 JDK 1.7 之前都在方法区,而 JDK 1.8 之后此变量会随着类对象一起存放到 Java 堆中。

  • 这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中
  • 这个阶段赋初始值的变量指的是那些不被 final 修饰的 static 变量
    • 比如 public static int value = 123,在准备阶段过后是 0 而不是 123 ,给 value 赋值为123的动作将在初始化阶段才进行
    • 比如 public static final int value = 123; ,在准备阶段,虚拟机就会给 value 赋值为123。

解析阶段(Resolution)

此阶段主要是用来解析类、接口、字段及方法的,解析时会把符号引用替换成直接引用。

  • 符号引用:是指以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;
  • 直接引用:是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

符号引用和直接引用有一个重要的区别:使用符号引用时被引用的目标不一定已经加载到内存中;而使用直接引用时,引用的目标必定已经存在虚拟机的内存中了。

初始化(Initialization)

初始化阶段 JVM 就正式开始执行类中编写的 Java 业务代码了。到这一步骤之后,类的加载过程就算正式完成了。

3 Java类加载器

  • 启动类加载器(BootStrapClassLoader):C/C++ 实现
  • 其他类加载器(Extension Class Loader、System Class Loader、User-Defined ClassLoader):Java 实现,规范定义自定义加载器是指派生于抽象类 ClassLoder 的类加载器

启动类加载器(Bootstrap ClassLoader)

C/C++ 实现,嵌套在 JVM 中,并不继承自 java.lang.ClassLoader,没有父加载器。用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar 或 sun.boot.class.path 路径下的内容),用于提供JVM自身需要的类。

扩展类加载器(Extension ClassLoader)

Java 实现,派生于 ClassLoader。由 sun.misc.Launcher$ExtClassLoader 实现。用来从 java.ext.dirs 系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的JAR 放在此目录下,也会自动由扩展类加载器加载。

应用类加载器(Application ClassLoader),又称系统类加载器

Java 实现,派生于 ClassLoader,父类加载器为扩展类加载器。由 sun.misc.Lanucher$AppClassLoader 实现。程序中默认的类加载器,一般来说,Java 应用的类都是由它来完成加载的,它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库。通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

用户自定义类加载器(User-Defined ClassLoader)

在日常的 Java 开发中,类加载几乎是由三种加载器配合执行的,在必要时我们还可以自定义类加载器,来定制类的加载方式。

类加载器包含示意图

ClassLoader 相关类图

重点关注 ClassLoader、ExtClassLoader、AppClassLoader。

4 双亲委派模型

过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。

好处:使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。同时也避免了多份同样字节码的加载。

Java 中实现双亲委派的代码都集中在 java.lang.ClassLoaderloadClass() 方法中,简单分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded // 1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { // 2. 判断一下是否有父加载器,若有交给父加载器加,否则调用 BootstrapClassLoader 加载。
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) { // 3. 如果第二步骤依然没有找到指定的类,那么调用当前类加载器的 findClass 方法来完成类加载。
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

// 自定义类加载器就可以重写 finalClass 方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

我们分析下 ExtClassLoader 与 AppClassLoader 源码,这两个类都是 sun.misc.Launcher 类的静态内部类

Launcher 类的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/**
* This class is used by the system to launch the main application. Launcher类
*/
public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher(); // 1. Luncher 类加载后会从此静态变量调用到默认的构造方法
private static String bootClassPath =
System.getProperty("sun.boot.class.path");

public static Launcher getLauncher() {
return launcher;
}

private ClassLoader loader;

public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader(); // 2. 扩展类加载器
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}

// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl); // 3. 应用类加载器(系统类加载器)
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}

// Also set the context class loader for the primordial thread. // 4. 设置 ContextClassLoader 为应用类加载器(系统类加载器)
Thread.currentThread().setContextClassLoader(loader);

// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
// init FileSystem machinery before SecurityManager installation
sun.nio.fs.DefaultFileSystemProvider.create();

SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
}

// ...


/*
* The class loader used for loading installed extensions. 关于 ExtClassLoader
*/
static class ExtClassLoader extends URLClassLoader {

// ...

private static ExtClassLoader createExtClassLoader() throws IOException {
try {
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().

return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
final File[] dirs = getExtDirs(); // 4. 获取扩展类加载器加载的目录文件
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
return new ExtClassLoader(dirs); // 5. 创建一个扩展类加载器进行返回
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}

void addExtURL(URL url) {
super.addURL(url);
}

/*
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
SharedSecrets.getJavaNetAccess().
getURLClassPath(this).initLookupCache(this);
}

private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs"); // 1. 将加载变量 java.ext.dirs 的值指示的路径下的类
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator); // 2. 按照 File.pathSeparator 进行 split
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs; // 3. 返回 File 对象数组
}


// ...

}

/**
* The class loader used for loading from java.class.path. 关于 AppClassLoader
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {

// ...

public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path"); // 1. 加载 java.class.path
final File[] path = (s == null) ? new File[0] : getClassPath(s);

// Note: on bugid 4256530
// Prior implementations of this doPrivileged() block supplied
// a rather restrictive ACC via a call to the private method
// AppClassLoader.getContext(). This proved overly restrictive
// when loading classes. Specifically it prevent
// accessClassInPackage.sun.* grants from being honored.
//
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}

final URLClassPath ucp;

/*
* Creates a new AppClassLoader
*/
AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
ucp.initLookupCache(this);
}

/**
* Override loadClass so we can checkPackageAccess. // 0. 重写 loadClass 方法:checkPackageAccess 之后调用 super.loadClass
*/
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}

if (ucp.knownToNotExist(name)) {
// The class of the given name is not found in the parent
// class loader as well as its local URLClassPath.
// Check if this class has already been defined dynamically;
// if so, return the loaded class; otherwise, skip the parent
// delegation and findClass.
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}

return (super.loadClass(name, resolve));
}

// ...
}
}

5 自定义类加载器

自定义类加载器的目的:

  • 隔离加载类:模块隔离,把类加载到不同的应用选中。比如 Tomcat 这类 Web 应用服务器,内部自定义了好几中类加载器,用于隔离 Web 应用服务器上的不同应用程序。
  • 修改类加载方式:除了 Bootstrap 加载器外,其他的加载并非一定要引入。根据实际情况在某个时间点按需进行动态加载。
  • 扩展加载源:可以实现从其他途径加载 class 文件。
  • 防止源码泄漏:Java 代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。

通过对双亲委派模型源码的解读,我们可以分析出两种自定义类加载器的做法:

  • 重写 loadClass 方法:不推荐,因为会破坏双亲委派模型
  • 重写 findClass 方法:推荐

采用 findClass 方法自定义类加载器实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class MyClassLoader extends ClassLoader{

private String dir;
public static final String fileType = ".class";

public MyClassLoader(String dir) {
this.dir = dir;
}

public MyClassLoader(ClassLoader parent, String dir) {
super(parent);
this.dir = dir;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 定义输入和输出流
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;

String fileName = dir + name + fileType;
try {
bis = new BufferedInputStream(new FileInputStream(fileName));
baos = new ByteArrayOutputStream();

// 读取字节数据
int len;
byte[] data = new byte[1024];
while ((len = bis.read(data)) != -1) {
baos.write(data, 0, len);
}
byte[] byteCode = baos.toByteArray();

// 字节数组转为Class对象
Class<?> definedClass = defineClass(null, byteCode, 0, byteCode.length);
return definedClass;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return null;
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
public class MyClassLoaderTest {

public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader("D:/");
try {
Class<?> dogClass = classLoader.loadClass("Test");
System.out.println(dogClass.getClassLoader().getClass().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

内容为之前学习笔记整理,如果有问题请指正!

1 内存布局总体结构

根据 JVM 规范,JVM 内存共分为虚拟机栈(Virtual Machine Stacks)、堆(Heap)、方法区(Method Area)、程序计数器(Program Counter Registers)、本地方法栈(Native Method Stacks)五个部分。

JVM Runtime Data Area

  • Java 8 之前在堆(Heap)中除了年轻代(YongGen)、老年代(OldGen)之外还存在一个永久代(PremGen)
    • 永久代存放:类的元数据、静态变量和常量
    • 方法区(Method Area)存在于永久代之中
    • 运行时常量池(Runtime Constant Pool)存在于方法区(Method Area)中
  • Java 8 及之后的版本,彻底移除了持久代(PermGen),而使用 元空间(Metaspace) 来进行替代
    • 永久代中的 类元信息(class metadata) 转移到了 本地内存(Native Memory) 而不是虚拟机
    • 永久代中的 字符串常量池(interned Strings)类静态变量(class static variables) 转移到了堆( Heap)中
    • 永久代参数(PermSize 与 MaxPermSize)失效,替换为元空间参数(MetaspaceSize 与 MaxMetaspaceSize)
  • Java 8 为什么要将永久代替换成Metaspace?
    • 字符串存在永久代中,容易出现性能问题和内存溢出。
    • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
    • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
    • Oracle 可能会将 HotSpot 与 JRockit 合二为一,JRockit 没有所谓的永久代。
  • 废除永久代的好处
    • 由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间
    • 将运行时常量池从 PermGen 分离出来,与类的元数据分开,提升类元数据的独立性。
    • 将元数据从 PermGen 剥离出来到 Metaspace,可以提升对元数据的管理同时提升GC效率。

2 程序计数器(Program Counter Register)

用于执行引擎在线程切换。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

  • 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
  • 如果线程正在执行的是一个Native方法,这个计数器值则为 Undefined
  • 程序计数器是线程私有的,它的生命周期与线程相同,每个线程都有一个

3 Java 虚拟机栈(Java Virtual MachineStacks)

Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,生命周期和线程相同。

Java虚拟机栈和线程同时创建,用于存储栈帧(Stack Frame)。每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

栈帧

栈里的每条数据,就是栈帧。在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦完成相应的调用,则出栈。所有的栈帧都出栈后,线程也就结束了。每个栈帧,都包含四个区域:

  • 局部变量表(Local Variable Table):用于存放方法参数和方法内定义的局部变量。 包括8种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。
  • 操作数栈(Operand Stack):是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。
  • 动态连接(Dynamic Linking):将符号引用转换成直接引用。
  • 返回地址(Return Address):方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

在Java虚拟机规范中,对这个区域规定了两种异常状况:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;
  • 如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

4 本地方法栈(Native Method Stack)

基本功能与虚拟机栈非常相似,服务的对象是 native 方法。本地方法栈也是线程私有的,它的生命周期与线程相同,每个线程都有一个。

在 HotSpot 虚拟机中直接就把本地方法栈和虚拟机栈合二为一。

会和 Java 虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

5 堆(Heap)与 元空间(Metaspace)

堆是什么:

  • 在虚拟机启动的时候创建。
  • 堆中的数据是线程所共享的,目的就是存放对象实例。
  • 堆是 垃圾收集器管理 的主要区域。
  • 堆是虚拟机所管理的内存中最大的一块,由于现在收集器基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代(JDK 1.7以及之前还存在永久代);新生代又可以分为:Eden 空间、From Survivor空间、To Survivor空间,默认占比 8:1:1。
  • 堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(可以通过 -Xms-Xmx 控制)。
  • 方法结束后,堆中对象不会马上移出仅仅在垃圾回收的时候时候才移除。
  • 如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

另外:从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local AllocationBuffer,TLAB)。

JVM-HEAP-1.7-1.8

  • Yong Gen:1个Eden Space和2个Suvivor Space(from 和to)。主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。
  • Old Gen(Tenured Gen): 主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。
  • 默认 -XX:NewRatio=2 , 标识新生代占1 , 老年代占2 ,新生代占整个堆的1/3
  • 默认 -XX:SurvivorRatio=6,标识Eden 空间:From Survivor空间:To Survivor空间 = 8:1:1

对象分配内存的工作流程图

对象分配内存

GC相关概念

  • form survivor 又称 s0
  • to survivor 又称 s1
  • 部分收集:Partial GC
    • 新生代收集:Minor GC / Young GC
      • 年轻代空间不足触发, 这里年轻代指的是Eden满。Survivor满不会引发GC
    • 老年代收集:Major GC / Old GC
      • 老年代空间不足时,会尝试触发MinorGC. 如果空间还是不足,则触发Major GC,如果Major GC , 内存仍然不足,则报错OOM
    • 混合收集:Mixed GC
      • G1垃圾回收器会混合回收, region 区域回收
  • 整堆收集:Full GC
    • 用System.gc() , 系统会执行Full GC ,不是立即执行
    • 老年代空间不足时触发
    • 方法区空间不足时触发

关于元空间(Metaspace)的单独说明

  • 在 JDK1.7 之前,HotSpot 虚拟机把方法区当成永久代来进行垃圾回收

  • 从 JDK1.8 开始,HotSpot 虚拟机移除永久代,并把方法区移至元空间

  • 永久代与元空间的区别

    • 永久代在物理上是堆的一部分,和新生代、老年代的地址是连续的,而元空间属于本地内存
    • 在原来的永久代划分中,永久代用来存放类的元数据信息、静态变量以及常量池等。现在类的元信息存储在元空间中,静态变量和常量池等并入堆中。

6 方法区(Method Area)与 运行时常量池(Runtime Constant Pool)

在 HotSpot 虚拟机上 GC 分代收集扩展至方法区,使用了永久代来实现方法区(JDK1.7及以前,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间)。

元空间、永久代是方法区具体的落地实现。方法区看作是一块独立于Java堆的内存空间,它主要是用来存储所加载的类信息的。方法区是一个规范,只不过取代永久代的是元空间(Metaspace)。

  • 与堆类似,方法区是被各个线程共享的内存区域
  • 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

类加载器将Class文件加载到内存之后,将类的信息存储到方法区中

  • 类信息:类全名、直接父类的全名、修饰符、实现的接口列表
  • 类的属性信息:名称、类型、修饰符
  • 类的方法信息:返回类型、参数数量和类型、修饰符、字节码bytecodes、操作数栈、局部变量表及大小(abstract和native方法除外)、异常表

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

  • 静态常量池:存放编译期间生成的各种字面量与符号引用。在字节码文件中即 .class 文件。
  • 运行时常量池:常量池表在运行时的表现形式。在方法区。
  • 编译后的字节码文件中包含了类型信息、域信息、方法信息等。通过ClassLoader将字节码文件的常量池中的信息加载到内存中,存储在了方法区的运行时常量池中。

7 直接内存(Direct Memory)

直接内存(Direct Memory) 并不是虚拟机运行时数据区的一部分。

  • 直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显
  • 直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显

在JDK 1.4中新加入了NIO(New Input/Output) 类, 引入了一种基于通道(Channel) 与缓冲区 (Buffer)的 I/O 方法,它可以使用Native函数库直接分配堆外内存, 然后通过一个存储在Java堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。避免了 在Java堆和Native堆中来回复制数据。

直接内存的大小并不受到 JVM 堆大小的限制,甚至不受到 JVM 进程内存大小的限制。它只受限于本机总内存(RAM 及 SWAP 区或者分页文件)大小以及处理器寻址空间的限制。

DirectBuffer并没有真正向OS申请分配内存,其最终还是通过调用 Unsafe 的 allocateMemory() 来进行内存分配。不过 JVM 对 Direct Memory 可申请的大小也有限制,可用 -XX:MaxDirectMemorySize=1M 设置,这部分内存不受JVM垃圾回收管理。

内容为之前学习笔记整理,如果有问题请指正!

1 什么是JVM

JVM 即 Java Virtual Machine,中文名为 Java虚拟机。

一般情况下

  • C/C++ 程序,编译成二进制文件后,就可以直接执行了;
  • Java 需要使用 javac 编译成 .class 文件,还需要使用 Java 命令去主动执行它。JVM 就是识别 .class 后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数。

Java 是一门抽象程度特别高的语言,提供了自动内存管理等一系列的特性。这些特性直接在操作系统上实现是不太可能的。而且有了 JVM 这个抽象层之后,Java 就可以实现跨平台了。Java 跨平台的意义在于一次编译,处处运行。而C/C++程序则需要对应不同的平台、架构分别编译成二进制文件。 现在的一些 JVM 的扩展语言,比如 Clojure、JRuby、Groovy 等,编译到最后都是 .class 文件,Java 语言的维护者,只需要控制好 JVM 这个解析器,就可以将这些扩展语言无缝的运行在 JVM 之上了。

从官网截取几张图来理解 JVM:

An overview of the software development process.

Through the Java VM, the same application is capable of running on multiple platforms.

2 JVM JRE JDK的关系

JVM不能单独搞定 class 的执行,它还需要一个基本的类库,比如怎么操作文件、怎么连接网络等。JVM加上这些基础类库,就组成
了 Java 的运行时环境,也就是我们常说的 JRE(Java Runtime Environment)。

另外Java的开发者非常慷慨的实现了一些开发的工具,比如 javac、java、jar 等,是 Java 开发的核心。JRE加上这些工具就组成了 JDK(Java Development Kit))。

JVM-JRE-JDK

3 JVM 分块概述

  • 运行时数据区 (Runtime Data Areas):分为五大区域,方法区(Method Area)、堆(Heap)、虚拟机栈(Virtual Machine Stacks)、程序寄存器(Program Counter Registers)、本地方法栈(Native Method Stacks)
  • 类加载器 (ClassLoader):负责加载程序中的类和接口
  • 执行引擎 (Execution Engine):通过类装载器装载的,被分配到JVM的运行时数据区的字节码会被执行引擎执行
  • 垃圾收集 (Garbage Collect): 自动管理内存并进行垃圾回收

4 JVM 的不同实现

因为 JVM 本质上是一种规范,所以在 Java 发展的过程中产生了很多不同的实现,下面表格是目前常见的 JVM 实现

虚拟机名称 介绍
HotSpot Oracle JDK和OpenJDK都使用HotSpot VM的相同核心
JRockit JRockit 也属 Oracle,目前为止 Oracle 一直在推进与 HotSpot 融合互补(大致上是在HotSpot的基础上,移植JRockit的优秀特性)
Azul Zulu 由Azul Systems根据HostPot为基础改进的高性能低延迟的JVM(最初针对专有硬件Vega系统,2010年发布了Zing VM面向通用x86平台)
OpenJ9 是IBM开发的高度模块化的JVM,目前已经捐献给Eclipse基金会
GraalVM 基于HotSpot / OpenJDK,它具有多语言功能,可以透明地混合和匹配支持的语言。

更多的JVM实现方案请查看 List of Java virtual machines - Wikipedia

内容为之前学习笔记整理,如果有问题请指正!

0%