KB,国美金融未来的RPC框架(一),具有更高的性能,更细的力度,更灵活的配置,更加适应未来。
之前公司为了更加适应互联网架构,一直在推行dubbo服务化,在实际中发现了一些无法解决的坑,例如:循环依赖问题、开发中经常会有接口文件冲突、接口不明确,web无法调用等缺点。
尽管公司的总体架构方向是推行dubbo化,但是我们想做的更好!
在2016年,我们基本可以看到:未来是一个虚拟化的世界,各种虚拟、容器技术例如docker等将会支撑起大量的微服务,整个互联网将活在微服务上。这是潮流所趋,没有一个“集中化”的方案能解决所有问题,没有一个“高可用”的技术可以保证100%的可用。我们能做出的选择,只有不停的将系统拆分,拆分为互不关联、互不影响的独立个体,让大量的独立个体来保障整体的可用与健康发展。
如果把软件也看作生命,过去的“集中式”是一种恐龙式的生命,不停的走向更大更强。—但是恐龙灭绝了,正如当今时代的集中式系统基本灭绝(除了几家银行被IOE等巨头忽悠出来的大集中系统)。
因此我们如果想走得远,就需要向未来的方向靠拢,如果你不想走回头路,那么你需要看的更远!
因此,为了适应将来的docker和微服务,并且改进现有的dubbo的缺点,顺手解决几个来自无线部门的BUG,KB框架诞生了。
—————————————————————————————————————–
KB框架是一款类似dubbo的rpc框架,用于系统间通信,例如你可以把web页面放在一个服务上,然后把logic服务放在另一个服务上。之所以我们会把web,logic,service,dao等拆分到不同的服务器上,为的其实是“复用”,很多架构师和我说了一堆的无用的理由,例如代码可以随时更新,随时发布等,但我认为,将一个流程的不同“层面”拆分到不同的服务上,可以将整个复杂的流程在“逻辑上切割为很多可前后连接的步骤这样就可以在新扩展出业务的时候,选择最佳的步骤进行介入。例如,新开发了一条业务线如下:
A -》 B -》 C -》 D -》 E -》 F -》 G
如果新来了一个业务改造,就可以有7个接入点选择—》比起重新开发一套,这是非常划算的结构。
因此就可以改造成为
A -》 B -》 C -》 D -》 E -》 F -》 G
┕ -》 E’ -》 F’ -》 G’
┕ -》 F’‘-》 G’‘
这种结构会使得业务减少很多的改造,减少很多工作量
但缺陷在于:
1、层之间的语言不通时,会导致转换复杂。例如dubbo服务无法被外网访问
2、层之间的通讯性能消耗比较大,会成为一个新的瓶颈
3、最终会导致系统结构复杂。
针对第一个问题,没有一个人尝试解决
针对第二个问题,很多程序员非常粗暴的对各种RPC框架进行“压力测试”,测出了一堆不靠谱的数据,然后说A框架性能比B框架好。颇有原始人看两个巫师比赛占卜之势。
针对第三个问题,很多公司开发了自己的“监控分析系统”,希望对服务点之间的关系进行“绘图”,最终绘出一个层次清晰的表单,进而描述系统的压力负载,系统消耗数据等等内容。
—————————————————————-
为解决问题1,KB系统采用了标准HTTP+JSON报文,并且支持采用配置的方式支持原生JSON或者Base64编码或者AES加密。其优势在于:“真正的互联网”,支持服务器端对端调用,也支持浏览器对服务端直接调用,也支持APP、IOS客户端对服务端直接调用。
我认为互联不是指服务器与服务器之间的互联,也包含了浏览器、客户端与应用之间的互联。目前的RPC框架基本上全部都是服务器端和服务器端的交互,而没有考虑到随着时代的发展,移动设备的计算能力甚至已经近似等于服务器端,如果仅仅把移动设备看作一个“浏览器”这是多么浪费的行为,未来的时代将会是移动设备可以直接与服务器端交互的时代,因此,KB系统最重要的特征就是:通用的标准报文,支持任意设备访问,支持有或没有注册中心的访问。
关于问题2,性能的问题,很多程序员非常粗暴,简单的对RPC框架调用十万次,算出平均时间就下结论说A框架性能比B框架的性能好。
用狗哥的话讲:这些我家的狗都会。
性能是个复杂的问题:性能是一个“用已有资源得到最优结果”的过程。已有资源包括CPU、带宽、内存、网络质量……….
所谓性能,是一种看锅下菜的工作。例如:
1、在CPU充足,带宽不足的地方,对报文进行压缩,那么系统性能就会表现优秀
2、在内存不足,CPU不足,带宽充足的地方,对报文进行简单处理直接发送,那么系统性能就会表现优秀
3、在网络质量差,但是CPU、带宽、内存比较充足的地方,就比较适合用一些大比例压缩和复杂分段传输的方法以达到优秀性能
。。。。。。
那些调用十万次算平均时间的,只能统计出一个结果:“统计出这个系统中有多少代码”。毕竟计算机是个很实在的东西,代码多了自然慢。
以下是KB与DUBBO的性能对比:
通信层: KB : jetty 对 DUBBO : netty
协议转换层: KB JSONlib DUBBO:ObjectOutputStream
网络层:KB HTTP协议 DUBBO:dubbo协议
………..
从字面看,jetty是应用层框架,而netty是网络层框架,JSONlib是JSON转化包,而ObjectOutputStream
你可以猜测到:KB框架对CPU和内存的需求比DUBBO要高
但是你可以看到:由于使用了JSON协议,在复杂对象的格式转换和网络消耗上,KB框架是比DUBBO有一定优势的
参见https://github.com/eishay/jvm-serializers/wiki 中的json和java原生序列化性能对比
据测试:KB框架的性能和DUBBO的性能基本没有差距。
关于问题3:为了使系统之间调用层次清晰可见,很多公司开发了服务监控框架。但事实上,服务和服务之间的关系复杂,最终将是N纬立体结构,无法用图来描述。做的好点的公司,一般是根据交易唯一ID来进行跟踪,跟踪某条交易在不同服务之间的调用关系,最终可以得到性能、成功率,稳定性等数据。
这儿有两条必走道路:
1、对系统整体进行规划,规划系统的架构统一,避免癌症一般的肆意发展
2、要求服务层级之间“完全的向下兼容”,尽量杜绝既有服务的修改(几乎不可能,但不代表不需要做)
————————————————————————————————————–
接下来是正文:KB框架
内部包含8个子包
kb
kb-client kb客户端
kb-config kb配置
kb-core kb依赖核心
kb-demo kb演示demo
kb-properties kb配置管理
kb-protocol kb协议管理
kb-server kb服务器端
kb-util kb基础工具
入口可以从kb-demo开始看:
——————————————————————————————————————————————————————————–
1、发布服务端,仅需3步
1.1、配置spring文件
spring-server.xml
<?xml version=”1.0” encoding=”UTF-8”?>
<beans xmlns=”http://www.springframework.org/schema/beans“
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance“
xmlns:context=”http://www.springframework.org/schema/context“
xmlns:kb= “http://www.kanmars.cn/kb“
xsi:schemaLocation=”
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.kanmars.cn/kb http://www.kanmars.cn/kb.xsd
”>
<context:annotation-config></context:annotation-config>
<context:component-scan base-package=”cn.kanmars.service”/>
<kb:server id=”kbserver1” bindip=”0.0.0.0” bindport=”9999” minthreadpool=”5” maxthreadpool=”150” register=”localhost:2181” idleTimeout=”30000”/>
<kb:server id=”kbserver2” bindip=”0.0.0.0” bindport=”9998” minthreadpool=”5” maxthreadpool=”150” register=”localhost:2181” idleTimeout=”30000”/>
<kb:properties id=”getAesKeyFromSpring” value=”123456”/>
</beans>
1.2、将一个方法注册到server上
CCC.java
package cn.kanmars.service;
/
Created by baolong on 2016/3/7.
/
@Service
public class CCC {
@KBRouteServer(group = “KB_DEFAULT”,globalname = “cn.kanmars.service.CCC.exec0”,servername=”kbserver1”,key = “NONE”)
public Map exec0(Map reqInfo){
String s = “cn.kanmars.service.CCC.exec0”;
LoggerUtil.print(LoggerUtil.INFO, “调用了服务[“ + s + “]”, null);
return null;
}
@KBRouteServer(group = “KB_DEFAULT”,globalname = “cn.kanmars.service.CCC.exec1”,servername=”kbserver1”,key = “getAesKeyFromSpring”)
public Map exec1(Map reqInfo){
String s = “cn.kanmars.service.CCC.exec1”;
LoggerUtil.print(LoggerUtil.INFO, “调用了服务[“ + s + “]”, null);
return null;
}
}
1.3、启动spring
ServerDemo.java
public class ServerDemo {
public static void main(String[] args) {
ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext(“classpath:spring-server.xml”);
Object cc = c.getBean(“kbserver1”);
}
}
——————————————————————————————————————————————————————————–
2、发布客户端,仅需3步
2.1、配置spring文件
spring-client.xml
2.2、配置访问代理
DDD.java
/
Created by baolong on 2016/3/8.
/
@Service
public class DDD {
@KBRouteClient(register = “localhost:2181”,url = “”,name=”DDD_KB_0308”,group = “KB_DEFAULT”,globalname = “cn.kanmars.service.CCC.exec0”,key=”getAesKeyFromSpring”)
public KBRouteClientProxy kbRouteClientProxy;
public void exec() {
long pre =System.currentTimeMillis();
long now =System.currentTimeMillis();
LoggerUtil.print(LoggerUtil.INFO, “初始时间[“+(now-pre)+”]”, null);
HashMap param = new HashMap();
Map result = null;
for(int i=0;i<1;i++){
try{
//Thread.sleep(2000);
result = kbRouteClientProxy.exec(param);
//———————————–
now =System.currentTimeMillis();
LoggerUtil.print(LoggerUtil.INFO, “第[“+i+”]次调用耗时[“ + (now - pre) + “]”, null);
pre =System.currentTimeMillis();
}catch (Exception e){
e.printStackTrace();
}
}
}
ClientDemo.java
/*
Created by baolong on 2016/3/8.
*/
public class ClientDemo {
public static void main(String[] args){
ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext(“classpath:spring-client.xml”);
DDD ddd = (DDD)c.getBean(DDD.class);
ddd.exec();
}
}
——————————————————————————————————————————————————————————–
从以上内容可以看到KB框架的调用方式很简单,并且原理很易懂:
1、server将某个方法暴露到register代表的zookeeper上,并且对外提供服务
2、client在zookeeper上寻找对应的服务,并且生成代理后调用
与DUBBO的区别是:Dubbo是将一个“接口”暴露在外,而KB系统是将一个“方法”暴露在外,提供给客户端的是一个“属性”,因此KB具有更细的粒度。
之所以这么设计,是因为我们在实际中发现:DUBBO中某个接口里的方法很多,但是真正用的只有一两个,其他的方法都是没用的废方法,并且,如果谁新增了一个方法,会导致所有的开发人员报错,严重阻塞了系统进度。
我们在使用DUBBO时,不得不采取了各种奇葩的方法来避免这种问题。
追根究底是这样的原因:
1,作为调用者,只对被调用者感兴趣,其他无关的内容尽管在一个类,但是没有存在的必要
2,由于强制把不相关的内容聚合在一起,导致这些内容可能出现冲突
3,之所以dubbo将方法和类绑定到一起,是因为作者局限于类和方法的思维,不考虑将方法本身暴露出去的好处。
因此在KB系统中,我们直接采用方法与属性的细力度对方法进行了封装,仅需globalname和group就可以唯一确定一个服务并进行调用。
完美的解决了类的依赖,以及如果系统双方要互相调用,那么必须把class类进行互通的问题(也许一些人会批评我“你完全不懂什么是面对对象”,我会很坦荡的告诉他们:“请你回去看三年面对函数编程再来”)
——————————————————————————————————————————————————————————–
同时,如果你在浏览器中访问如下地址
http://localhost:9999/cn.kanmars.service.CCC.exec0
你会看到如下结果:
{“RSPCODE”:”0002”,”RSPMSG”:{“RSPCODE”:”0002”,”RSPDESC”:”报文格式不正确”},”RSPDESC”:”报文格式不正确”}
这是KB比DUBBO最大的优势:可以在内网由java客户端调用,也可以将服务暴露到外网,由javascript,app,ios等直接调用,在新的互联网时代,一定要了解“互联”的概念,不要局限于局域网。
——————————————————————————————————————————————————————————–
有人会质疑:安全么?
请访问另一个地址
http://localhost:9999/cn.kanmars.service.CCC.exec1
你会看到如下结果:
{“RSPCODE”:”0002”,”RSPMSG”:”yc7h9oGF6iOVKX6QrhHpx3jTpxTbiI6wo6Y1w3nE1xqp7AjahtxrOVLg0WERoZPuljktOifC09jRvWul593iXg==”,”RSPDESC”:”报文格式不正确”}
可以看到RSPMSG为加密后的结果,从配置可以看出来,此配置已经将加密内容配置上去,极大的保障了安全性
@KBRouteServer(group = “KB_DEFAULT”,globalname = “cn.kanmars.service.CCC.exec1”,servername=”kbserver1”,key = “getAesKeyFromSpring”)
同时,密钥可以通过spring的beanFactoryPostProcessor机制储存在各种机制中,如数据库、diamond、配置、环境变量等处
<kb:properties id=”getAesKeyFromSpring” value=”123456”/>
KB系统支持三种通讯加密方式:
key=”NONE” 不加密
key=”BASE64” 采用BASE64编码
key=”其他” 采用aes加密
通过如上加密方式,在通讯报文中,还会有sign签名信息对报文整体进行校验,最终保障通讯安全。
也就是说,如果是内网,就可以采用NONE
如果是安全的外网,可以采用NONE
如果是需要混淆的外网,可以使用混淆后的BASE64
如果是需要严格加密,可以实用AES加密
整个过程配置简单,仅需客户端和服务器端配置的密钥字符串相同即可
服务器端 : @KBRouteServer(group = “KB_DEFAULT”,globalname = “cn.kanmars.service.CCC.exec1”,servername=”kbserver1”,key = “getAesKeyFromSpring”)
客户端 : @KBRouteClient(register = “localhost:2181”,url = “”,name=”DDD_KB_0308”,group = “KB_DEFAULT”,globalname = “cn.kanmars.service.CCC.exec0”,key=”getAesKeyFromSpring”)
——————————————————————————————————————————————————————————–
关于系统架构
。。。。。。。
12点了,改天再写
JavaThread类启动过程与main方法调用
JavaThread对应于每一个java层的Thread,例如调用public static void main(String[] args)时,自动生成一个java线程,而且每new Thread(){()->{}}.start();的时候也会生成一个java线程。
根据一些粗显的知识,java线程中包含虚拟机栈、程序计数器。
虚拟机栈包含:栈帧,每一个方法调用都会生成一个栈帧
栈帧包含: 局部变量表、操作数栈、动态链接、方法返回地址
———————————————————————————————————————————————–
或许大家作为JAVA程序员想象了一遍又一遍的JVM内部实现,但大部分是错误的想法,(例如 :每一个虚拟机栈包含了局部变量表,但在OpenJDK 的JVM中,虚拟机栈和局部变量表是分离的实现) ,今天我们就来看一下,在JVM中,这些东西是怎么实现的。
———————————————————————————————————————————————–
JavaThread是如何启动并运行的
JavaThread的启动位于
java.c:main -> java.c:javaMain -> java.c:InitializeJVM->Thread.cpp:CreateJavaVM
JavaThread内部的重要属性如下:
JavaThread _next; 下一个JavaThread
oop _threadObj;
JavaFrameAnchor _anchor; JavaFrame的锚点
ThreadFunction _entry_point;
JNIEnv _jni_environment;
vframeArray _vframe_array_head; vframe_array的头部
vframeArray _vframe_array_last; vframe_array的尾部
GrowableArray<jvmtiDeferredLocalVariableSet> _deferred_locals_updates; 本地变量表,即localVariable,从此可以看出,本地变量表并非在栈帧中,而是并列的结构
methodOop _callee_target;
oop _vm_result;
oop _vm_result_2;
MemRegion _deferred_card_mark;
ThreadSafepointState _safepoint_state;
address _saved_exception_pc;
volatile oop _exception_oop;
volatile address _exception_pc;
volatile address _exception_handler_pc;
volatile int _exception_stack_size;
volatile int _is_method_handle_return;
———————————————————————————————————————————————–
JavaThread main_thread = new JavaThread();
调用父构造函数: Thread()
代码: new HandleMark(this);
代码: _unhandled_oops = new UnhandledOops(this);
代码: initialize();
代码: _anchor.clear();
代码: pd_initialize();
代码: _anchor.clear();
代码: _is_attaching = is_attaching;
main_thread->set_thread_state(_thread_in_vm);
线程状态共12种
enum JavaThreadState {
_thread_uninitialized = 0, // should never happen (missing initialization)
_thread_new = 2, // just starting up, i.e., in process of being initialized
_thread_new_trans = 3, // corresponding transition state (not used, included for completness)
_thread_in_native = 4, // running in native code
_thread_in_native_trans = 5, // corresponding transition state
_thread_in_vm = 6, // running in VM
_thread_in_vm_trans = 7, // corresponding transition state
_thread_in_Java = 8, // running in Java or in stub code
_thread_in_Java_trans = 9, // corresponding transition state (not used, included for completness)
_thread_blocked = 10, // blocked in vm
_thread_blocked_trans = 11, // corresponding transition state
_thread_max_state = 12 // maximum thread state+1 - used for statistics allocation
};
main_thread->record_stack_base_and_size();
main_thread->initialize_thread_local_storage();
代码: ThreadLocalStorage::set_thread(this);
代码: os::initialize_thread(); 空函数
main_thread->set_active_handles(JNIHandleBlock::allocate_block());
代码: block = new JNIHandleBlock();
代码: return block;
main_thread->set_as_starting_thread()
代码: os::create_main_thread((JavaThread)this)
代码: create_attached_thread(thread)
代码: OSThread osthread = new OSThread(NULL, NULL);
代码: osthread->set_thread_id(os::Linux::gettid()); //设置线程ID为Linux的gettid,内部代码为系统调用syscall(SYS_gettid);
代码: osthread->set_pthread_id(::pthread_self()); //设置pthreadId为当前运行的线程id
代码: os::Linux::init_thread_fpu_state();
代码: osthread->set_state(RUNNABLE);
代码: thread->set_osthread(osthread);
代码: JavaThread jt = (JavaThread )thread;
代码: address addr = jt->stack_yellow_zone_base();
代码: osthread->set_expanding_stack();
代码: os::Linux::manually_expand_stack(jt, addr);
代码: osthread->clear_expanding_stack();
代码: os::Linux::hotspot_sigmask(thread);
以上代码需要注意的是,在内部并没有调用任何os:create_thread()方法,只是将主线程设置为了osthread的threadId,因此此步骤并未生成新的thread,而是使用原thread继续运行
main_thread->create_stack_guard_pages(); 创建警戒页并把该警戒页通过::mmap函数映射到address low_addr = stack_base() - stack_size();处
main_thread->cache_global_variables(); 空函数
Threads::add(main_thread);
代码: p->initialize_queues();
代码: p->set_next(_thread_list);
代码: _thread_list = p;
代码: _number_of_threads++;
代码: ThreadService::add_thread(p, daemon);
oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
main_thread->set_threadObj(thread_object);
java_lang_Thread::set_thread_status(thread_object, java_lang_Thread::RUNNABLE);
在执行完毕Thread.cpp:CreateJavaVM后,在java.c中
mainClassName = GetMainClassName(env, jarfile);
classname = (char )(env)->GetStringUTFChars(env, mainClassName, 0);
mainClass = LoadClass(env, classname);
mainID = (env)->GetStaticMethodID(env, mainClass, “main”, ”([Ljava/lang/String;)V”);
jmethodID mid;
jobject obj = (env)->ToReflectedMethod(env, mainClass,mainID, JNI_TRUE);
mid = (env)->GetMethodID(env,(env)->GetObjectClass(env, obj),”getModifiers”, “()I”);
mods = (env)->CallIntMethod(env, obj, mid);
(env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); //执行java的main方法
代码: env->CallStaticVoidMethod( pClass, pMid, pArgs);
代码: functions->CallStaticVoidMethodV(this,cls,methodID,args);
代码: JavaValue jvalue(T_VOID);
JNI_ArgumentPusherVaArg ap(THREAD, methodID, args); //将参数推送到堆栈中
调用父类构造函数: JNI_ArgumentPusher(thread, JNIHandles::resolve_jmethod_id(method_id)->signature())
调用父类构造函数: SignatureIterator(thread, signature)
代码: _signature = signature;
代码: _parameter_index = 0;
代码: this->_return_type = T_ILLEGAL;
代码: _arguments = NULL;
代码: set_ap(rap);
代码: _ap = rap;
jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK); //执行static方法,参数为ap,返回值为jvalue
代码: methodHandle method(THREAD, JNIHandles::resolve_jmethod_id(method_id));
代码: ResourceMark rm(THREAD);
代码: int number_of_parameters = method->size_of_parameters();
代码: JavaCallArguments java_args(number_of_parameters);
代码: args->set_java_argument_object(&java_args);
代码: args->iterate( Fingerprinter(THREAD, method).fingerprint() ); //???????????+++++++++++++++
代码: result->set_type(args->get_ret_type());
代码: JavaCalls::call(result, method, &java_args, CHECK); invoke the method
代码: JavaCalls::call
代码: JavaCalls::call_helper
代码: JavaCallWrapper link(method, receiver, result, CHECK);
代码: address entry_point = method->from_interpreted_entry();
代码:
代码: StubRoutines::call_stub()( 调用Stub,在java中,每一个方法都会解析为一个Stub
(address)&link,
// (intptr_t)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
args->parameters(),
args->size_of_parameters(),
CHECK
);
代码: result = link.result();
代码: result->set_jobject((jobject)thread->vm_result());
代码: if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
}
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
至此,可以看到
java.c中的launcher创建了JVM环境,然后获取到了java的main方法,然后执行jenv->CallStaticVoidMethod函数,触发了jni_invoke_static函数,之后触发了StubRoutines::call_stub()所代表的函数指针
那么问题来了,StubRoutines是什么东西,是在什么时候产生的?其内部又会对JavaThread的属性产生什么操作?其内部是如何调用到entry_point即method->from_interpreted_entry()的?
可以阅读一下R大的博客http://hllvm.group.iteye.com/group/topic/37707,权当预习。
这个行业前辈众多,需要膜拜之……例如R大,在我刚学Java之时他成名已久
JVM中的线程详解
在JVM的\hotsopt\src\share\vm\runtime\Thread.hpp中可以看到关于THREAD的子类结构
// Class hierarchy
// - Thread
// - NamedThread
// - VMThread
// - ConcurrentGCThread
// - WorkerThread
// - GangWorker
// - GCTaskThread
// - JavaThread
// - LowMemoryDetectorThread
// - CompilerThread
// - WatcherThread
Thread是最基础的基类
重要属性如下:
OSThread _osthread;
在OSThread内部set_thread_id方法可以设置为操作系统级的线程ID pthreadid
在JVM的线程管理中,THREAD是一个数据结构,THREAD中含有抽象的OSThread数据结构,OSThread中的pthreadid或者threadid指向的是操作系统内核级别的线程id
关于操作系统内核级别的线程,大家可以阅读原生C语言的多线程编程接口POSIX threads 简称pthread库
重要函数如下:
virtual void run(); 为最终的线程运行方法
此外有一个工具性质的类Threads
重要属性如下:
static JavaThread _thread_list;
static int _number_of_threads;
static int _number_of_non_daemon_threads;
static int _return_code;
储存了所有正在运行的Java线程
————————————————————————————————————————————————————————————————
可以看到,线程粗分为三类:
NamedThread、JavaThread、WatcherThread
这三类中
NamedThread是一个大类
按照源码中的注释:
// Name support for threads. non-JavaThread subclasses with multiple
// uniquely named instances should derive from this.
max_name_len = 64,最大名称长度为64个字符
重要属性为
char _name;
JavaThread _processed_thread;
也就是说NamedThread中可以有OSThread也可以有JavaThread
JavaThread是JAVA线程
重要属性如下:
JavaThread _next; // The next thread in the Threads list
oop _threadObj; // The Java level thread object
int _java_call_counter; // java调用次数
JavaFrameAnchor _anchor; // JavaFrame锚点
ThreadFunction _entry_point;
JNIEnv _jni_environment;
vframeArray _vframe_array_head;
vframeArray _vframe_array_last;
GrowableArray<jvmtiDeferredLocalVariableSet> _deferred_locals_updates; 本地变量表
oop _vm_result; // Used to pass back an oop result into Java code, GC-preserved
oop _vm_result_2; // Used to pass back an oop result into Java code, GC-preserved
ThreadSafepointState _safepoint_state;
address _saved_exception_pc;
volatile oop _exception_oop;
volatile address _exception_pc;
volatile address _exception_handler_pc;
volatile int _exception_stack_size;
volatile int _is_method_handle_return;
重要方法如下:
void java_suspend();
void java_resume();
int java_suspend_self();
virtual void run();
WatcherThread是用来模拟时间中断
重要属性如下:
static WatcherThread _watcher_thread;
volatile static bool _should_terminate;
————————————————————————————————————————————————————————————————
// - NamedThread
// - VMThread
// - ConcurrentGCThread
// - WorkerThread
// - GangWorker
// - GCTaskThread
NameThread下分为三大类VMThread,ConcurrentGCThread,WorkerThread
首先看VMThread
VMThread位于\hotspot\src\share\vm\runtime\vmThread.hpp中
重要属性如下:
static VM_Operation _cur_vm_operation; 当前正在执行的VM操作
static VMOperationQueue _vm_queue; 所有VM操作的对象
static VMThread _vm_thread; 单例模式的VMThread
重要方法如下:
void evaluate_operation(VM_Operation op); 执行一个VM操作
bool is_VM_thread() const { return true; } //默认为VMThread
bool is_GC_thread() const { return true; } //默认为GCThread
与VMThread配套使用的是VMOperationQueue和VM_Operation
VMOperationQueue位于\hotspot\src\share\vm\runtime\vmThread.hpp中
重要属性如下:
int _queue_length[nof_priorities];
VM_Operation _queue [nof_priorities];
int _queue_counter;
VM_Operation _drain_list;
VM_Operation位于\hotspot\src\share\vm\runtime\Vm_operations.hpp中
重要属性如下:
Thread _calling_thread;
VM_Operation _next;
VM_Operation _prev;
void evaluate();
virtual void doit()
virtual bool doit_prologue()
virtual void doit_epilogue()
VM_ThreadStop位于\hotspot\src\share\vm\runtime\Vm_operations.hpp中
重要属性如下
oop _thread;
oop _throwable;
此外和VMThread相关联的VM_Operation还有
VM_ForceSafepoint
VM_ForceAsyncSafepoint
VM_Deoptimize
VM_DeoptimizeFrame
VM_HandleFullCodeCache
VM_DeoptimizeAll
VM_ZombieAll
VM_Verify
VM_PrintThreads
VM_PrintJNI
VM_FindDeadlocks
VM_ThreadDump
VM_Exit
可从名称中看到其用途
举个简单的栗子,VM_PrintJNI
void VM_Operation::evaluate() {
ResourceMark rm;
if (TraceVMOperation) {
tty->print(“[“);
NOT_PRODUCT(print();)
}
doit();
if (TraceVMOperation) {
tty->print_cr(“]”);
}
}
void VM_PrintJNI::doit() {
JNIHandles::print_on(_out);
}
—————————————————————-
ConcurrentGCThread位于\hotspot\src\share\vm\gc_implementation\shared\concurrentGCThread.hpp中
含义如其名称,并发垃圾回收线程
WorkerThread有两个子类:GangWorker、GCTaskThread
子类极多。启动过程有很大区别,例如
GangWorker在WorkGang::initialize_workers()中GangWorker new_worker = allocate_worker(worker);中创建
例如FlexibleWorkGang _workers->initialize_workers();
GCTaskThread的初始化与启动在
ParallelScavengeHeap::initialize()
_gc_task_manager = GCTaskManager::create(ParallelGCThreads);
在构造函数中调用:initialize();
GCTaskManager::initialize()
set_thread(t, GCTaskThread::create(this, t, processor_assignment[t]));
new GCTaskThread(manager, which, processor_id)
os::create_thread(this, os::pgc_thread)
osthread->set_thread_type(thr_type);
thread->run();
GCTaskThread->run();
GCTask task = manager()->get_task(which()); 即:任何一个进程,只要在manager的任务队列中放入task,后续将会立刻执行
task->do_it(manager(), which());
从这段描述中可以看到,WorkerThread本身即为垃圾回收线程,大致分为两类:
1、GangWorker,作用于SharedHeap,在ShareHeap创建成功后,等待AbstractWorkGang->AbstractGangTask _task中获取到task,然后执行
2、GCTaskThread,作用于parallelscavenge,在ParallelScavengeHeap创建成功后,生成GCTaskManager,进而生成任务队列SynchronizedGCTaskQueue _queue,等待有程序将任务放入队列,然后执行
—————————————————————-
下文我们来看一下VMThread的启动过程
VMThread是用于JVM虚拟机操作
其启动过程为随着JVM虚拟机java.c一起启动
调用堆栈序列为:
java.c:main->javaMain->InitializeJVM->CreateJavaVM->JNI_CreateJavaVM->Threads::create_vm
VMThread::create(); 生成单例模式的_vm_thread
_vm_thread = new VMThread();
_vm_queue = new VMOperationQueue();
os::create_thread(vmthread, os::vm_thread) 给VMThread创建真实线程
OSThread osthread = new OSThread(NULL, NULL);
pd_initialize();
_startThread_lock = new Monitor(Mutex::event, “startThread_lock”, true); 锁监视器,第一个和第三个参数均没有用,只有第二个参数会作为Monitor的名名称
内部具有_LockWord关键字,同时具有CASPTR宏Atomic::cmpxchg_ptr用来对数据进行原子性操作,底层采用AMD64汇编指令cmpxchgq来完成原子性操作
Monitor::Monitor() { ClearMonitor(this); }
void Monitor::ClearMonitor (Monitor m, const char name) {
m->_owner = NULL ;
m->_snuck = false ;
if (name == NULL) {
strcpy(m->_name, “UNKNOWN”) ;
} else {
strncpy(m->_name, name, MONITOR_NAME_LEN - 1);
m->_name[MONITOR_NAME_LEN - 1] = ‘\0’;
}
m->_LockWord.FullWord = 0 ;
m->_EntryList = NULL ;
m->_OnDeck = NULL ;
m->_WaitSet = NULL ;
m->_WaitLock[0] = 0 ;
}
可看到锁的初始值是m->_LockWord.FullWord = 0 ;
此处需要注意:Monitor中的lockWord为SplitWord
union SplitWord { // full-word with separately addressable LSB
volatile intptr_t FullWord ;
volatile void Address ;
volatile jbyte Bytes [sizeof(intptr_t)] ;
}
因此,lockWord初始值为0
而ILock中代码如下intptr_t v = CASPTR (&_LockWord, 0, _LBIT) ;
锁定后_LockWord = 1,解锁后_LockWord = 0;
初始值为未锁定0
vmthread->set_osthread(osthread);
pthread_t tid;
int ret = pthread_create(&tid, &attr, (void ()(void)) java_start, thread);
osthread->set_pthread_id(tid);
在java_start函数中
while (osthread->get_state() == INITIALIZED) {
sync->wait(Mutex::_no_safepoint_check_flag); 开始等待
等待函数的写法如下:
Thread const Self = Thread::current() ;
set_owner(NULL);
wait_status = IWait (Self, timeout) ; 开始等待–>内部实现细节需要研究
set_owner (Self) ;
}
thread->run(); 开始调用thread->run函数
os::start_thread(vmthread);
调用: pd_start_thread(thread);
调用: Monitor* sync_with_child = osthread->startThread_lock();
调用: MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
调用: sync_with_child->notify(); 通知java_start继续运行
调用: thread->run()
调用: this->loop();
代码: while(true)
代码: _cur_vm_operation = _vm_queue->remove_next();
代码: evaluate_operation(_cur_vm_operation);
代码: _cur_vm_operation->evaluate();
代码: _cur_vm_operation->doit();
在最终的doit()函数中,即执行的是VM_Operation的操作。
例如:
void VM_PrintThreads::doit() {
Threads::print_on(_out, true, false, _print_concurrent_locks);
}
这个就是VMThread的执行步骤,以及最终调用函数的过程。
JVM中垃圾回收线程的创建
本文描述了垃圾回收线程的创建过程,和在gc过程中,垃圾回收线程的任务分配
垃圾回收线程是在代回收堆GenCollectedHeap的父类SharedHeap的构造方法中创建的,可见代码
\hotspot\src\share\vm\memory\shareHeap.cpp中
构造方法: SharedHeap::SharedHeap(CollectorPolicy policy_)
代码: FlexibleWorkGang _workers = new FlexibleWorkGang(“Parallel GC Threads”, ParallelGCThreads,true,false);
代码: _workers->initialize_workers();
在FlexibleWorkGang中即为垃圾回收线程的创建
构造方法: FlexibleWorkGang 设置_active_workers数量为ParallelGCThreads,默认为0,但是会根据gc策略和CPU数量进行修改参见函数Abstract_VM_Version::nof_parallel_worker_threads
父类构造方法: WorkGang 设置_total_workers = workers;
父类构造方法: AbstractWorkGang::AbstractWorkGang
_monitor = new Monitor(Mutex::leaf,”WorkGroup monitor”,are_GC_task_threads);
_terminate = false;
_task = NULL;
_sequence_number = 0;
_started_workers = 0;
_finished_workers = 0;
在_workers->initialize_workers();中即为worker的初始化,参见代码\hotspot\src\share\vm\utilities\Workgroup.cpp
调用: WorkGang::initialize_workers()
代码: if (are_ConcurrentGC_threads()) {
worker_type = os::cgc_thread; 判断是并发垃圾回收线程
} else {
worker_type = os::pgc_thread; 判断是否是并行垃圾回收线程
}
代码: for (int worker = 0; worker < total_workers(); worker += 1) {
GangWorker new_worker = allocate_worker(worker); 申请一个worker内存
代码: GangWorker new_worker = new GangWorker(this, which);
_gang_workers[worker] = new_worker;
os::create_thread(new_worker, worker_type) 创建一个worker线程
代码: \hotspot\src\os\linux\vm\Os_linux.cpp
代码: OSThread osthread = new OSThread(NULL, NULL);
代码: osthread->set_thread_type(thr_type);
代码: thread->set_osthread(osthread);
代码: pthread_t tid;
int ret = pthread_create(&tid, &attr, (void ()(void)) java_start, thread);
代码: os::start_thread(new_worker);
代码: OSThread osthread = thread->osthread();
代码: osthread->set_state(RUNNABLE);
代码: pd_start_thread(thread);
代码: sync_with_child->notify(); 触发os函数,即 java_start函数
调用: static void java_start(Thread thread)
代码: thread->run(); 此处的thread是在上步骤生成的GangWorker
调用: GangWorker.run()
调用: initialize(); 初始化一些信息
调用: loop(); 循环操作
代码: WorkData data;
代码: gang()->internal_worker_poll(&data); 此处的gang()是指FlexibleWorkGang,AbstractWorkGang::internal_worker_poll(WorkData data)
data->set_terminate(terminate());
data->set_task(task()); 此处的task为AbstractGangTask的子类,但是FlexibleWorkGang在构造的时候,没有指定task,因此为空
data->set_sequence_number(sequence_number());
代码: data.task()->work(part); 此处的task为AbstractGangTask的子类,
包括:CMConcurrentMarkingTask、G1ParFinalCountTask、G1ParNoteEndTask、G1ParScrubRemSetTask、CMRemarkTask、
CMSParRemarkTask、CMSRefEnqueueTaskProxy、ParRebuildRSTask、G1ParVerifyTask、G1ParTask、
G1ParCleanupCTTask、ParKnownGarbageTask、ParNewRefProcTaskProxy、ParNewRefEnqueueTaskProxy、ParNewGenTask
在concurrentmarksweep中使用的是CMSParRemarkTask和CMSRefEnqueueTaskProxy
在parnew中使用的是ParNewRefProcTaskProxy、ParNewRefEnqueueTaskProxy、ParNewGenTask
CMSParRemarkTask任务的位置
入口 CMSCollector::collect(bool full, bool clear_all_soft_refs, size_t size, bool tlab)
调用: CMSCollector::acquire_control_and_collect(bool full, bool clear_all_soft_refs)或者
调用: CMSCollector::do_mark_sweep_work(bool clear_all_soft_refs, CollectorState first_state, bool should_start_over) 或者 VM_CMS_Initial_Mark::doit()
调用: CMSCollector::collect_in_foreground(bool clear_all_soft_refs) 或者CMSCollector::do_CMS_operation(CMS_op_type op)
调用: CMSCollector::checkpointRootsFinal(bool asynch,bool clear_all_soft_refs, bool init_mark_was_synchronous)
调用: CMSCollector::checkpointRootsFinalWork(bool asynch,bool clear_all_soft_refs, bool init_mark_was_synchronous)
调用: CMSCollector::do_remark_parallel()
代码: CMSParRemarkTask tsk(this,cms_space, perm_space,n_workers, workers, task_queues());
代码: workers->run_task(&tsk);
代码: tsk.work(0);
从上可知,由某个操作,例如内存分配,触发一个收集内存垃圾的动作,进而调用了属于该堆的worker的runtask函数,将task进行执行操作,调用work函数。
我们从更细节的角度来看一下gc的触发与执行
从$$23123!@#$%^&@#$%^&(%$^&*() –》发生了惨剧,作者写了三个小时的内容,因为一个随意的关闭,丢失了一部分从java.c启动到创建VMTHREAD,到VMTHREAD的安全点通知,到执行collect_as_vm_thread到do_collection,到安全点的恢复……….的文档……
已经晚上12:54,还在等待隔壁的不靠谱系统发版,没有心情写了,听一会儿歌,明天写VMTHREAD的启动过程和垃圾回收过程。………………….
由于JVM是一个多平台的可移植语言,人力无法全部精通,因此本文描述的是:基于X86_64系列CPU、Linux\Unix类操作系统、GCC编译器的指令序列。
本文描述JVM中对象的分配过程
我们需要从java开始看对象是如何分配的。
1、JAVA中对象的分配,例如HashMap m = new HashMap();
通过javac编译成字节码后是如下效果
0: new #2; //class java/util/HashMap
3: dup
4: invokespecial #3; //Method java/util/HashMap.”<init>”:()V
7: astore_1
翻译为中文是:
0 new新建一个内存区域,区域的类型为class java/util/HashMap,
将引用值压入栈顶
3 复制0申请出的内存区域的指针并压入栈顶
4 执行特殊方法 构造函数Method java/util/HashMap.”<init>”:()V
执行完成后,令3压入栈顶的指针弹出
7 将0压入栈顶的引用,存入局部变量表1中
总结:
java语言中,对象的分配是通过创建对象内存,和调用对象的构造方法两个过程来实现的。
因此,我们需要关注new字节码和invokespecial字节码的作用方式
2、new关键字的作用机制
JVM的解释器是采用一种叫做模版解释器的技术,所谓template的技术,将指令序列的顶部进行分析,进而总结成“模版”,继而汇编成为汇编语言,的执行技术。
解释器的模版表位于
\hotspot\src\share\vm\interpreter\templateTable.hpp
\hotspot\src\share\vm\interpreter\templateTable.cpp
\hotspot\src\share\vm\interpreter\ByteCodes.hpp
\hotspot\src\share\vm\interpreter\ByteCodes.cpp
关于new这个字节码,对应的TemplateTable的static void _new()函数
以X86_64机器为例,代码位于\hotspot\src\cpu\x86\vm\templateTable_x86_64.cpp中
代码为汇编代码,比较冗长,只挑关键的列出:
movptr(rsi, Address(rsi, rdx,Address::times_8, sizeof(constantPoolOopDesc))); 获取到instanceKlass
movl(rdx, Address(rsi, Klass::layout_helper_offset_in_bytes() + sizeof(oopDesc))); 从instanceKlass中获取到instance对象的大小
// Allocate the instance
// 1) Try to allocate in the TLAB
// 2) if fail and the object is large allocate in the shared Eden
// 3) if the above fails (or is not applicable), go to a slow case
if (UseTLAB) {
movptr(rax, Address(r15_thread, in_bytes(JavaThread::tlab_top_offset()))); 获取到tlab的开始位移
lea(rbx, Address(rax, rdx, Address::times_1)); 在rbx寄存器中放入XX地址
cmpptr(rbx, Address(r15_thread, in_bytes(JavaThread::tlab_end_offset()))); 获取到tlab的结束位移
jcc(Assembler::above, allow_shared_alloc ? allocate_shared : slow_case); 如果…..跳转到在共享新生代分配
movptr(Address(r15_thread, in_bytes(JavaThread::tlab_top_offset())), rbx); 移动tlab的顶部指针 ->即缓存区释放出的这部分内存,即是申请的Tlab内存
……跳转到header初始化或者Object初始化
}
if (allow_shared_alloc) {
bind(allocate_shared); 打LABEL 共享新生代分配
ExternalAddress top((address)Universe::heap()->top_addr()); 获取到当前堆的顶部
ExternalAddress end((address)Universe::heap()->end_addr()); 获取到当前堆的底部
lea(RtopAddr, top);
lea(RendAddr, end);
movptr(rax, Address(RtopAddr, 0));
Label retry;
bind(retry); 开始循环
lea(rbx, Address(rax, rdx, Address::times_1));
cmpptr(rbx, Address(RendAddr, 0));
jcc(Assembler::above, slow_case);
if (os::is_MP()) { 增加内存屏障,保证读可见
lock();
}
cmpxchgptr(rbx, Address(RtopAddr, 0)); 原子性更新heap->top_addr并增加内存
jcc(Assembler::notEqual, retry); 如果不成果,则重新跳转到retry中执行
incr_allocated_bytes(r15_thread, rdx, 0);
}
if (UseTLAB || Universe::heap()->supports_inline_contig_alloc()) {
bind(initialize_object);
decrementl(rdx, sizeof(oopDesc));
jcc(Assembler::zero, initialize_header);
// Initialize object fields
xorl(rcx, rcx); // use zero reg to clear memory (shorter code)
shrl(rdx, LogBytesPerLong); // divide by oopSize to simplify the loop
{
Label loop;
bind(loop);
movq(Address(rax, rdx, Address::times_8,sizeof(oopDesc) - oopSize), rcx);
decrementl(rdx);
jcc(Assembler::notZero, loop);
}
// initialize object header only.
bind(initialize_header);
if (UseBiasedLocking) {
movptr(rscratch1, Address(rsi, Klass::prototype_header_offset_in_bytes() + klassOopDesc::klass_part_offset_in_bytes()));
movptr(Address(rax, oopDesc::mark_offset_in_bytes()), rscratch1);
} else {
movptr(Address(rax, oopDesc::mark_offset_in_bytes()), (intptr_t) markOopDesc::prototype()); // header (address 0x1)
}
xorl(rcx, rcx); // use zero reg to clear memory (shorter code)
store_klass_gap(rax, rcx); // zero klass gap for compressed oops
store_klass(rax, rsi); // store klass last
jmp(done);
}
// slow case 慢分配方式
bind(slow_case);
get_constant_pool(c_rarg1);
get_unsigned_2_byte_index_at_bcp(c_rarg2, 1);
call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), c_rarg1, c_rarg2); 调用运行时解释器_new,位于\hotspot\src\share\vm\interpreter\interpreterRuntime.cpp
oop obj = klass->allocate_instance(CHECK); 在堆中分配内存
位于\hotspot\src\share\vm\oops\instanceKlass.cpp
代码: i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL); 即调用当前的堆进行分配内存
代码: HeapWord* obj = common_mem_allocate_init(size, false, CHECK_NULL);
代码: post_allocation_setup_obj(klass, obj, size);
verify_oop(rax);
// continue 申请完毕
__ bind(done);
从以上步骤,即可看到:new字节码,在JVM中的运行机制。(需要熟悉汇编,汇编语言会根据机型,操作系统,编译器,有不同的写法。建议大家读懂一种,然后再开始,例如,我学的是X86_64CPU,LINUX操作系统,GCC编译器)
下文,我们将从JVM的角度,看一下new关键字,在触发了slow case慢分配之后,在JVM堆中是如何运行的。
1、入口:
入口为\hotspot\src\cpu\x86\vm\templateTable_x86_64.cpp中的_new函数的slow case的label。
代码: call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), c_rarg1, c_rarg2);
调用: \hotspot\src\share\vm\interpreter\interpreterRuntime.cpp
InterpreterRuntime::_new(JavaThread thread, constantPoolOopDesc pool, int index)
调用: oop obj = klass->allocate_instance(CHECK);
调用: instanceOop instanceKlass::allocate_instance(TRAPS) 位于:
调用: instanceOop i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
调用: oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS)
调用: HeapWord obj = common_mem_allocate_init(size, false, CHECK_NULL);
调用: HeapWord obj = common_mem_allocate_noinit(size, is_noref, CHECK_NULL);
调用: HeapWord result = CollectedHeap::allocate_from_tlab(THREAD, size);
HeapWord result = Universe::heap()->mem_allocate(size,is_noref,false,&gc_overhead_limit_was_exceeded);
调用: HeapWord GenCollectedHeap::mem_allocate(size_t size,bool is_large_noref,bool is_tlab,bool gc_overhead_limit_was_exceeded)
调用: collector_policy()->mem_allocate_work(size,is_tlab,gc_overhead_limit_was_exceeded);
调用: HeapWord GenCollectorPolicy::mem_allocate_work(size_t size,bool is_tlab,bool gc_overhead_limit_was_exceeded)
代码: HeapWord* result = NULL;
result = gen0->par_allocate(size, is_tlab);//尝试从新生代或者tlab上分配,如果分配成果则返回
MutexLocker ml(Heap_lock); 尝试加锁后从堆中分配
result = gch->attempt_allocation(size, is_tlab, first_only);//依次尝试从堆的各内存代中分配内存
result = expand_heap_and_allocate(size, is_tlab); //尝试扩展内存代并分配内存
VM_GenCollectForAllocation op(size, is_tlab, gc_count_before);
VMThread::execute(&op); 触发一次GC
continue; 如果还是失败,则重试分配流程
调用: init_obj(obj, size);
代码: const size_t hs = oopDesc::header_size();
代码: ((oop)obj)->set_klass_gap(0);
代码: Copy::fill_to_aligned_words(obj + hs, size - hs); 对对象进行初始化,因此field int 的初始值为0,但是在方法中int i 不初始化会报错。
调用: post_allocation_setup_obj(klass, obj, size);
代码: post_allocation_setup_common(klass, obj, size);
代码: post_allocation_setup_no_klass_install(klass, obj, size);
代码: if (UseBiasedLocking && (klass() != NULL)) { obj->set_mark(klass->prototype_header());} 如果使用偏向锁,则设置obj的mark
代码: else obj->set_mark(markOopDesc::prototype());
代码: post_allocation_install_obj_klass(klass, oop(obj), (int) size);
代码: obj->set_klass(klass());
代码: post_allocation_notify(klass, (oop)obj);
代码: LowMemoryDetector::detect_low_memory_for_collected_pools(); 低内存通知
代码: JvmtiExport::vm_object_alloc_event_collector(obj); JVMTI工具
至此之后,一个new指令,调用了tlab指令,调用了堆的分配内存指令,调用了堆的setup_obj指令,将对象分配成功,初始化成果,完成了对象创建的流程。
至于java对象创建后的invokespecial调用构造函数,下期再讲。
以 UseSerialGC为例
JVM在创建堆结构时,会沿用如此堆栈顺序
Universe::initialize_heap() Universe对象初始化堆结构
代码: GenCollectorPolicy gc_policy = new MarkSweepPolicy(); 创建垃圾回收策略为标记清除垃圾回收对象
代码: Universe::_collectedHeap = new GenCollectedHeap(gc_policy); 创建堆为代回收堆
代码: Universe::heap()->initialize(); 堆初始化
1、创建垃圾回收策略为标记清除垃圾回收对象new MarkSweepPolicy()
initialize_all()
调用: initialize_flags(); 初始化一些标记,例如NewSize,MaxNewSize,PermSize,MaxPermSize,MinPermHeapExpansion,MaxPermHeapExpansion,MinHeapDeltaBytes,SharedReadOnlySize,SharedReadWriteSize,SharedMiscDataSize等
调用: initialize_size_info(); 初始化一些大小,例如min_gen0_size,initial_gen0_size,max_gen0_size等
调用: initialize_generations(); 初始化代信息
调用: initialize_perm_generation(PermGen::MarkSweepCompact); 初始化永久代信息
代码: _permanent_generation = new PermanentGenerationSpec(pgnm, PermSize, MaxPermSize, SharedReadOnlySize, SharedReadWriteSize, SharedMiscDataSize, SharedMiscCodeSize); 初始化永久代信息
代码: _generations = new GenerationSpecPtr[number_of_generations()]; 初始化特殊的代信息指针
代码: _generations[0] = new GenerationSpec(Generation::ParNew, _initial_gen0_size, _max_gen0_size); 并行回收的年轻代特殊对象
_generations[0] = new GenerationSpec(Generation::DefNew, _initial_gen0_size, _max_gen0_size); 普通的年轻代特殊对象
代码: _generations[1] = new GenerationSpec(Generation::MarkSweepCompact, _initial_gen1_size, _max_gen1_size); 标记清楚的代对象特殊对象
2、创建代回收堆,并设置垃圾回收策略为标记清除垃圾回收对象Universe::_collectedHeap = new GenCollectedHeap(gc_policy);
调用构造方法: GenCollectedHeap(GenCollectorPolicy policy)
调用父类构造方法: SharedHeap(CollectorPolicy policy)
调用父类构造方法: CollectedHeap() 一些参数的设置
代码: const size_t max_len = size_t(arrayOopDesc::max_array_length(T_INT));
const size_t elements_per_word = HeapWordSize / sizeof(jint);
代码: if ((UseParNewGC ||(UseConcMarkSweepGC && CMSParallelRemarkEnabled) ||UseG1GC) &&ParallelGCThreads > 0) {
FlexibleWorkGang _workers = new FlexibleWorkGang(“Parallel GC Threads”, ParallelGCThreads,true,false);
}
_workers->initialize_workers();
代码: _gen_policy(policy),_gen_process_strong_tasks(new SubTasksDone(GCH_PS_NumElements)),
3、对堆进行格式化Universe::heap()->initialize();
调用: GenCollectedHeap-::initialize();
调用: CollectedHeap::pre_initialize();
代码: _defer_initial_card_mark = ReduceInitialCardMarks && can_elide_tlab_store_barriers() && (DeferInitialCardMark || card_mark_must_follow_store());
代码: char heap_address = allocate(alignment, perm_gen_spec, &total_reserved, &n_covered_regions, &heap_rs); 分配代内存,映射到指定位置
代码: total_reserved = _gen_specs[0]->max_size() + _gen_specs[1]->max_size() + perm_gen_spec->max_size() + perm_gen_spec->misc_data_size() + perm_gen_spec->misc_code_size(); 计算总可占用的最大内存大小
代码: _n_covered_regions = _gen_specs[0]->n_covered_regions() + _gen_specs[1]->n_covered_regions() + perm_gen_spec->n_covered_regions() 计算总可占用的最大内存分片数量
代码: char heap_address = Universe::preferred_heap_base(total_reserved, Universe::UnscaledNarrowOop); 设置一些这样或者那样的属性
代码: ReservedSpace heap_rs = ReservedHeapSpace(total_reserved, alignment,UseLargePages, heap_address); 申请内存并创建到指定映射
调用父类构造函数: ReservedSpace::ReservedSpace(size_t size, size_t alignment, bool large, char requested_address, const size_t noaccess_prefix)
调用函数: void ReservedSpace::initialize(size_t size, size_t alignment, bool large, char requested_address,const size_t noaccess_prefix, bool executable)
代码: char base =os::reserve_memory(size, NULL, alignment); 申请内存并映射
调用: os::reserve_memory()
代码: return anon_mmap(requested_addr, bytes, (requested_addr != NULL));
代码: char addr = (char)::mmap(requested_addr, bytes, PROT_READ|PROT_WRITE,flags, -1, 0); 调用原生Linux函数mmap,申请到一个可读可写的内存映射,大小为bytes
代码: char _base = addr;
代码: size_t _size = size;
代码: size_t _noaccess_prefix = prefix_align;
代码: size_t _alignment = noaccess_prefix;
调用: protect_noaccess_prefix(size)
调用父类函数: ReservedSpace::protect_noaccess_prefix(const size_t size) 对内存的起始位置进行调整
代码: _reserved = MemRegion((HeapWord)heap_rs.base(),(HeapWord)(heap_rs.base() + heap_rs.size())); 创造MemRegion内存分片对象
代码: HeapWord _start = start;
代码: size_t _word_size;
代码: _rem_set = collector_policy()->create_rem_set(_reserved, n_covered_regions); 创造原子性内存块集合
调用: CollectorPolicy::create_rem_set(MemRegion whole_heap,int max_covered_regions)
代码: CardTableRS res = new CardTableRS(whole_heap, max_covered_regions); 创造card table
调用父类构造函数: GenRemSet() // A GenRemSet provides ways of iterating over pointers accross generations.
内部全是虚函数,都在CardTableRS中实现
代码: _ct_bs = new CardTableModRefBSForCTRS(whole_heap, max_covered_regions);
调用父类构造函数: ModRefBarrierSet(max_covered_regions)
代码: _kind = BarrierSet::CardTableModRef
代码: HeapWord low_bound = _whole_heap.start();
代码: HeapWord high_bound = _whole_heap.end();
代码: MemRegion _covered = new MemRegion[max_covered_regions]; // The covered regions should be in address order.这个分片必须按内存地址进行排序
代码: MemRegion _committed = new MemRegion[max_covered_regions]; // The committed regions correspond one-to-one to the covered regions.这个是否已提交分片必须与covered regions一一对应
代码: MemRegion _guard_region = MemRegion((HeapWord)guard_page, _page_size);//The last card is a guard card, and we commit the page for it so
//we can use the card for verification purposes. We make sure we never
//uncommit the MemRegion for that page.
最后一个card是一个警戒card,我们能用这块内存来实现某种指定的意图,我们需要保证我们从不操作这块内存
代码: typedef jbyte CardPtr;
typedef CardPtr CardArr;
CardArr _lowest_non_clean = NEW_C_HEAP_ARRAY(CardArr, max_covered_regions);
size_t _lowest_non_clean_chunk_size = NEW_C_HEAP_ARRAY(size_t, max_covered_regions)
uintptr_t _lowest_non_clean_base_chunk_index = NEW_C_HEAP_ARRAY(uintptr_t, max_covered_regions)
int _last_LNC_resizing_collection = NEW_C_HEAP_ARRAY(int, max_covered_regions)
// This is an array, one element per covered region of the card table.
// Each entry is itself an array, with one element per chunk in the
// covered region. Each entry of these arrays is the lowest non-clean
// card of the corresponding chunk containing part of an object from the
// previous chunk, or else NULL.
英语很好懂,不解释。大意是每个array的元素是另一个array,每个二级array储存的是在当前region分片中的元素的chunk数组
因此,可知JVM内存分配中采用BarrierSet来对内存的每一个chunk进行管理
代码: set_bs(_ctbs);
代码: set_barrier_set(rem_set()->bs());
代码: _perm_gen = perm_gen_spec->init(heap_rs, PermSize, rem_set());
调用: PermanentGenerationSpec::init(ReservedSpace rs,size_t init_size,GenRemSet *remset)
代码: ReservedSpace perm_rs = rs.first_part(_max_size, UseSharedSpaces,UseSharedSpaces); 获取到第一块可用的内存?
代码: ReservedSpace result(base(), partition_size, alignment, special(),executable());
代码; addr = os::attempt_reserve_memory_at(size, requested_address); 申请一块内存并放入ReservedSpace(此处与上处代码相同,走的是requested_address不为空的分支)
代码: ReservedSpace shared_rs = rs.last_part(_max_size); 申请获取最后一块内存(此处需要研究,在申请第一块和最后一块之后,是否中间的内存就全部申请完成)
代码: clear_incremental_collection_failed
至此内存代分配结束;
最近看了一些JVM内存结构,但纸上得来终觉浅,绝知此事要躬行。看别人画的,总归是别人的,所以自己画一个
JVM内存入口位于如下三个位置中
hotspot\src\share\vm\memory\universe.hpp
hotspot\src\share\vm\memory\universe.inline.hpp
hotspot\src\share\vm\memory\universe.cpp
这个入口之后是JVM堆内存的初始化、分配策略、垃圾回收策略、堆的扩大与收缩,年轻代的管理,老年代的管理,永久区的管理。
因此画图展示。

Universe作为内存的核心,创建了垃圾回收策略和堆结构。
而上图中的每一个类,都是比较重要的数据+算法结构。
通过对每个描述对象的加工,完成了堆的申请、初始化、扩展、收缩等操作。
—————
后续会从如下角度来解释这个内存核心:
1、堆是如何创建、映射、扩展、收缩的?
2、堆的种类
3、常用参数对堆种类的影响
4、对象的创建过程
5、对象在堆代中的移动
6、常用垃圾回收策略
最近半年一直在反思瀑布模式和敏捷模式的使用。
因为我的第一家公司是传统软件公司,软件流程包含了一整套的初始需求、方案设计、招投标、需求分析、概要设计、详细设计、开发、测试、试运行、全面上线、后续改造与维护等流程,虽说知道的不深,但也还算是了解。因此可以说,我对传统的瀑布模式是比较熟悉的。
现在的这家公司,因为是互联网企业,所以要求速度要快,快速响应最火的热点。但去年这家公司用瀑布模式比较多,速度慢且费时费力。因此今年在大力推进敏捷+微服务。
我很赞同之前我的老板和我说的一句话:“没有任何一个系统是按部就班就能做成功的”,因此我即不认同标准规矩的瀑布模式,也不认同疯狂激进的敏捷模式。
举例说明A:在上一家公司瀑布流程标准的时代,我负责的一个社保项目,由于开发团队对项目的需求和流程十分熟悉,配合熟练,因此,我们跳过了概要设计和试运行阶段。
举例说明B:之前有业内BAT人员来给我们讲解敏捷模式,各种疯狂,例如表的结构不需要设计,放一个大JSON,不需要测试流程直接上线、不需要产品设计人员…….但是,太激进了,我们无法相信这个能起到实际效果。更倾向认为,这个是程序员为了个人的懒惰的目的而做的一种宣传。毕竟,这个行业有太多的程序员和太多的退役程序员,他们的号召力太强悍(但程序员有个缺点:术业有专攻,程序员做出的东西,有95%以上都是复杂而没有用途的“技术牛角尖”)
因此我不认同标准规矩的瀑布模式,也不认同疯狂激进的敏捷模式。
去年,领导要求规范化,但是那会儿我们的团队磨合不够,举个例子:写个API接口,六个人有四种完全不同的写法;对于分布式服务,一群人认识不清,只有一个人大概了解的,但是也是一知半解无法说服其他人;对于系统的总体架构,修改频繁,甚至对于一个英文的中文解释都有N多个,“支付渠道、支付通道、支付路由………..”
因此,无法立刻出一个统一的规范标准,唯一的可行之路,就是先实现一个基本可用的版本,然后在实践中探索规范。——其副作用就是:当时很多人认为我们的行为是不规范的。
在这一年中,领导屡屡提及规范化,要求我们做项目要按照标准流程去做。我们也响应领导号召,尽最大努力的增加规范,例如,版本管理流程、DBA执行规范、代码评审规范、代码总结规范、接口文档规范,部门培训规范…………..
但我很担忧有一天我们会成为一个庞大的,繁杂的,无人认识的系统,然后大家散伙各找各妈。
因此我在后半年一直在反思瀑布模式和敏捷模式的使用。
直到前段时间总结了我们的做法,略有所得:
—————————————————————————————————————————————————————————-
如果说:项目的稳定+流程的规范是一个重型的极端,而立刻快速的上线是一个轻型的极端
重型项目工程(瀑布) | 轻型项目工程(敏捷) | ||||
流程要求 | 100 | 流程要求 | 0 | ||
版本管理 | 100 | 版本管理 | 10 | ||
数据库管理 | 100 | 数据库管理 | 10 | ||
配置管理 | 100 | 配置管理 | 10 | ||
开发人员培训 | 100 | 开发人员培训 | 100 | ||
上线速度 | 10 | 上线速度 | 100 | ||
技术稳定性 | 100 | 技术稳定性 | 100 | ||
DBA执行规范 | 100 | DBA执行规范 | 10 | ||
代码评审规范 | 100 | 代码评审规范 | 10 | ||
代码总结规范 | 100 | 代码总结规范 | 10 | ||
接口文档规范 | 100 | 接口文档规范 | 0 | ||
…… | …… | ||||
从这张图上看到:无论是瀑布模式还是敏捷模式,无论是重型项目工程还是轻型项目工程,做的都是相同的东西,仅是需要的努力程度不同。
因此,我很反感那些说“敏捷比瀑布好”的,也很反感那些说“瀑布比敏捷好”的。
我认为,瀑布和敏捷,是相同性质工作的两个极端,而作为项目负责人,是一名老司机的角色,在道路上既不能靠左路边行驶,也不能太靠右边行驶—这容易掉沟里。
项目负责人的职责,是动态的根据需求,调整这辆车在路的位置:
1、不能掉沟里,让车无法前进—》管理要求太重则负担太重,管理要求太轻则稳定性不足,都会导致项目无法推进
2、需要满足乘客的要求———-》领导就是乘客,乘客经常说“师父靠一下边,我拍照”,领导经常说,这个不规范,要增加管理要求,或者这个没用的东西给去掉
3、需要避开前方的障碍———-》开车时,路上会有石头,你要避开,而项目推进时,前方经常有障碍,例如:隔壁友邦系统不给力,前方有技术难点,后方有依赖系统在催。。。。。。
4、需要选择最近的路线———-》需要选择最近的路线,开车是为了省油。项目负责是为了节省程序员的成本。
5、、、、、、、、、、
因此,我推荐的方式,是根据环境,根据总体需求,根据团队的目标,选择那些需求要用,那些需求要舍弃,最终目的是:整车人安全到站,大家开开心心回家过年。
如何做到这个目标呢?:
1、凡是引入一个需求,这个需求必须是可以反向去除的
举反例说明:引入配置管理部的规范,提交SVN和BUG号要绑定才能提交,这个略微有点违反这个思想,因为加上这个功能,那么就永远无法去除了。此类需求一多,则系统会越来越重,并且无法减肥。
举正例说明:引入了PRE环境测试需求,要求项目上线前才PRE进行测试,这个规范就是可以反向去除的,因为我们在DEV-UAT环境测试完,如果项目紧急,可以直接对UAT封版,UAT测试,UAT回归,UAT验收,直接上线,这种需求就是可增加可删除的。
2、明确我们的目标,我们是用成果说话的,没有成果,一切都没有意义
举反例说明:考英语四级,努力的考了3次,每天被单词到晚上10点,报各种培训班,订各种资料,阅读各种英文资料,故事可歌可泣听的都哭了………………….然而没考过,这就从一个悲情故事变成了一个可笑的故事。没结果就没有意义。
举正例说明:高中时,你肯定有一个同学,平日不好好学习,但是考试成绩很好,因此老师各种优待,家长各种宠爱………………….成果比一切都要重要。
3、明确没有什么是必须的,包括:规范、稳定、DBA、总结、文档、接口……….
如果你认为“XX是必须的”,那你的人生已经失败了一半,因为你没有认识到:“成果是最重要的,而附属的那些东西是不重要的,当然,如果这个成果必须由一个附属的去支撑,那么附属也同样重要”
举反例说明:隔壁系统,具有各种规范,具有各种的要求,具有各种的管理,一个项目里一个开发三个测试……然而导致项目推诿严重,项目无法推进
举正例说明:我们系统的初创时期,由于一无所有,因此不需要“僵化流程”,不需要“冗长规范”,并且能扛着领导的“一定要规范”的压力没有增加这些内容,所以项目推进迅速,成果显著,部门绩效极高,高层非常认可。
4、项目负责人一定要有责任心:
4.1、了解项目各方的需求人:产品需求、领导需求、测试需求、程序员需求、友邻部门需求、各项目干系人的需求
4.2、分析这些需求哪些是必须的,哪些是可延后实现的,哪些是可以舍弃的
4.3、仅最大努力,把必须的需求实现,把可延后实现的需求放到同期实现,把舍弃的需求尽量列入计划
4.4、此时会有个问题:项目负责人的责任心,因为在此情况下,会出现委托人问题,各方要做需求实现,但是项目负责人会为了工作的轻松程度,尽可能的把可延后的需求延后实现。此时需要各方的博弈,最终结果会是各方得到一个平衡,项目成功上线,满足了各方的需求。
这就是项目负责人的意义:平衡各方需求,把一件事情抹平。同样而言,如果一个项目负责人延后了太多的项目,就会被换掉,这是自然规律。
以上是鄙人狂妄的见解,请不认可的同志们就当是笑谈吧
——————————————————————————
因此说:项目负责人这个工作,是艺术而非技术。但艺术水准是无法衡量的。
人类趋于稳定,不想冒风险,因此我一直致力于成为一个技术人员,因为技术水平是可衡量的。就比如13年初那会儿,会Hadoop就比其他人牛……再比如今年,精通docker就比其他人牛……
这就是为什么很多人喜欢做IT,IT有一条非常稳定的技术路线,沿着路线走,你总会成功的。
说明地址http://android-mirror.bugly.qq.com:8080/include/usage.html
1、 Config User Defined Sites
Add this url to “User Defined Sites”: http://android-mirror.bugly.qq.com:8080/android/repository/addon.xml
2、Config Proxy
Http Proxy Server : android-mirror.bugly.qq.com
HttpProxy Port:8080
Make sue to select: Force https://… sources to be fetched using http://…