JVM & Memory (3) thread

转自:http://spaces.msn.com/songsun/

 

以下继续探讨,说说jvm的线程(thread)及其执行中和内存相关的问题。but今天是写不完了,请保持关注,先写个预告栏咯

记得当年学java之初,总是很鄙夷它,因为那时候对c/c++很痴迷,结果呢,第一个多线程程序还是拿java写出来的。而线程(thread),本是 操作系统的所提供/支持的,所以当初有一段时间,我总在怀疑我那java程序创建的线程到底是不是真正的操作系统线程,现在看来很可笑。

题外,再继续质疑一下: 在有些平台上,比如Linux或者大 部分的unix,只有进程而没有线程的概念的,但这些平台上,进程间通讯的手段极其发达,所以jvm会用进程模拟线程,效果也是一样的。不过,也有一些平 台如sorlaris,在java线程和os内核线程间,存在着不是1对1的关系,实在让我挠头,容以后再去把它搞明白。

简单的说,jvm如何实现多线程呢,首先要有os的支持包括变相的支持,那么jvm通过os提供线程接口创建新线程,这些新线程当然还不能直接执行你的 thread对象的run方法,但它会拥有并执行属于该线程的一个java interpreter实例或者上下文,java解释器再去执行你的run方法,当run方法结束(自然结束时),java interpreter发现没有字节码可以解释了,也宣告结束,OS线程也就自然终结了。至于要实现线程的终止,挂起,恢复,也都可以从OS那里找到相应 的接口,这里不再细说,而主要关注对内存的影响,主要是java stack和native call等问题。

待续


当执行引擎(即java interpreter,对应一个ExecEnv)开始解释执行一个java method以前,它首先初始化自身,即分配自己的initial java stack,栈开始时尺寸很小,随着使用的需要而扩展,存在一个400k的默认长度限制( -Xss<size>可更改该值);ExecEnv有两个重要的标志(或指针),一个是pc,即程序计数器,另一个是optop,即栈顶,也 是最上面一个操作数(即本地变量)的位置。 pc总是指向要执行的byte code(随着每一条byte code的执行,pc自动改变),而optop指向java stack的顶端(也随某些byte code的执行而改动)。当方法执行时,方法的invoker将pc指向method block的code位,同时,在java stack里为该方法新建java frame(每一个方法在编译期就确定了运行它所需的最大stack size,所以,新建java freame时,会按照这个size去检查java stack是否满足,若不足,则扩展java stack,而java stack已经达到最大长度限制时,扩展失败,发生stack overflow),准备工作完成后,还给ExecEnv继续解释和执行;当方法执行完毕时,一条ret字节指令将pc和optop送回前一个java frame所记录的位置。所以,对于某个线程来说,其java stack完整的记录了所深入的每一层java method(每层一个java frame),除了native method,本质上是因为c的堆栈不能记录。

以上是执行java method,若是native method呢?很简单,native method在类加载时,即被jvm安排了一个native invoker在其method block中,继而在ExecEnv执行一条call该native method的字节指令时,native invoker被执行,如果是首次执行,那么这个native mothod的method block的code段尚未挂接,native invoker检查到这个信息,将根据method name和signature搜索系统中已加载的dll的合适挂接点,将找到的挂接点挂在code上,而后调用该code段即可(找不到挂接点,会发生什 么异常你应该清楚)。过程中,除非你的native method中又反调了java method,那么,java stack将毫无变化。

再来关注一下native method的内存模型:native mthod自身的机器码code存在于dll镜像(image)中;nm的本地变量存在于线程堆栈内(指native thread,和java thread是绑定着的);nm用到的静态变量存在于dll的共享变量区中;nm制造的java对象(通过jni手段new来的)依然在java heap;而nm声明的global ref以及local ref存放在jvm的native c heap上。本地方法也会带来内存泄露(c的内存泄露,也可能是java heap的global ref的未释放),特别是c heap的泄露,运行期是极难探测和定位的。

最后,说说调用深度过深(即java stack会很长)带来的负面影响。在前一节,我们知道本地变量都寄存在java stack中。如果调用深度过深,特别是jvm长时间运行在较高深度调用的情形下,意味着在栈上的各frame中,存在着很多变量,它们中的引用类型将一 直保持对java heap上对应对象的引用而导致gc器始终不能释放它们;其次,较长的java stack也给gc器造成了较多的扫描时间(为何要扫描后面再说);第三,method call本身也是一种时间开销,对于很短小的method,call它比它本身代码执行的时间还要多,好比机关枪打蚊子,这也是现代编译器非常讲究inline优化的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值