`

java中的进程与线程及java对象的内存结构

阅读更多
 1、实现线程的三种方式:
 

使用内核线程实现

    内核线程(Kernel Thread, KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型。轻量级进程要消耗一定的内核资源(如内核线程的栈空间),而且系统调用的代价相对较高,因此一个系统支持轻量级进程的数量是有限的。
 

使用用户线程实现

 

    广义上来讲,一个线程只要不是内核线程,那就可以认为是用户线程(User Thread,UT),而狭义的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到线程存在的实现,用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型。
    使用用户线程的优势在于不需要系统内核的支援,劣势在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理,因而使用用户线程实现的程序一般都比较复杂,现在使用用户线程的程序越来越少了。Java、Ruby层语言都曾经使用过用户线程,最终又都放弃了使用它。
 

混合实现

 

    混合环境下,既存在用户线程,又存在轻量级进程。用户线程还是完全建立在用户空间中,而操作系统所支持的轻量级进程则作为用户线程和内核线程之间的桥梁。这种混合模式下,用户线程与轻量级进程的数量比是不定的,是M:N的关系。许多Unix系列的系统,都提供了M:N的线程模型实现。

 

 

2、java线程与操作系统之间的调度关系

 

      以前的老java自己实现了线程库,也就是说java的线程并不和操作系统的线程对应,jvm在操作系统上面是一个进程,当这个进程被操作系统调度到后,jvm内部实现的线程库再调度java线程,这就是最初的多对一的关系(使用用户线程实现),为什么是这样呢?考虑到以前的操作系统内核,比如linux,在以前都不直接支持线程,用户线程和内核线程是多对一的关系,solaris一度也是这样,所以java当然心有余而力不足了,你操作系统都不能完美支持线程,你让我实现不是难为我吗?在那个年代,java多线程的调度完全是自主的,操作系统根本不知道java是多线程的,调度策略完全自己实现,单cpu下肯定是分时的,多cpu下就看jvm会不会建立多cpu上的多jvm实例了。
      到了后来,操作系统内核纷纷都支持了多线程(windows开始就支持),那么java也要考虑推卸一些责任了,这样java线程就和操作系统线程一一对应(操作系统调度,使用内核线程实现)或多多对应了(操作系统和JVM一起调度,混合实现),这个时候,如果是一一对应,那么线程的调度完全交给了操作系统内核。Linux从内核2.6开始使用NPTL (Native POSIX Thread Library)支持,但这时线程本质上还轻量级进程。 Java里的线程是由JVM来管理的,它如何对应到操作系统的线程是由JVM的实现来确定的。Linux 2.6上的HotSpot使用了NPTL机制,JVM线程跟内核轻量级进程(LWP)有一一对应的关系线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度,举个例子,在linux下,只要一个Thread.run就会调用一个fork产生一个线程。ava线程在Windows及Linux平台上的实现方式,现在看来,是内核线程的实现方式。这种方式实现的线程,是直接由操作系统内核支持的——由内核完成线程切换,内核通过操纵调度器(Thread Scheduler)实现线程调度,并将线程任务反映到各个处理器上内核线程是内核的一个分身。程序一般不直接使用该内核线程,而是使用其高级接口,即轻量级进程(LWP),也即线程。其调度关系如下图所示
 
(说明:KLT即内核线程Kernel Thread,是“内核分身”。每一个KLT对应到进程P中的某一个轻量级进程LWP(也即线程),期间要经过用户态、内核态的切换,并在Thread Scheduler 下反应到处理器CPU上。)
      现在的Java使用的线程调度方式就是抢占式调度。每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(在Java中,Thread.yield()可以让出执行时间,但是要获得执行时间的话,线程本身是没有办法的)。在这种实现线程调度的方式下,线程的执行时间是系统可控的,也不会因为一个线程而导致整个进程阻塞。另外,抢占式线程调度也与线程优先级有关,不过线程优先级并不是很靠谱,原因是Java的线程是被映射到系统的原生线程上来实现的,所以线程调度最终还是由操作系统说了算,虽然现在好恩多操作系统都有提供线程优先级的概念,但是并不见得能与Java线程的优先级一一对应。因此,不能太依赖优先级。
 

3、Java 进程与操作系统进程

 

 在 JDK 的代码中,只提供了 ProcessImpl 类来实现 Process 抽象类。其中引用了 native 的 create, close, waitfor, destory 和 exitValue 方法。在 Java 中,native 方法是依赖于操作系统平台的本地方法,它的实现是用 C/C++ 等类似的底层语言实现。我们可以在 JVM 的源代码中找到对应的本地方法,然后对其进行分析。JVM 对进程的实现相对比较简单,以 Windows 下的 JVM 为例。在 JVM 中,将 Java 中调用方法时的传入的参数传递给操作系统对应的方法来实现相应的功能。对应关系如下图:



 

每个 JVM 中创建的进程都对应了操作系统中的一个进程。但是,Java 为了给用户更好的更方便的使用,向用户屏蔽了一些与平台相关的信息,这为用户需要使用的时候,带来了些许不便。

在使用 C/C++ 创建系统进程的时候,是可以获得进程的 PID 值的,可以直接通过该 PID 去操作相应进程。但是在 JAVA 中,用户只能通过实例的引用去进行操作,当该引用丢失或者无法取得的时候,就无法了解任何该进程的信息。

当然,Java 进程在使用的时候还有些要注意的事情:

  1. Java 提供的输入输出的管道容量是十分有限的,如果不及时读取会导致进程挂起甚至引起死锁。
  2. 当创建进程去执行 Windows 下的系统命令时,如:dir、copy 等。需要运行 windows 的命令解释器,command.exe/cmd.exe,这依赖于 windows 的版本,这样才可以运行系统的命令。
  3. 对于 Shell 中的管道 ‘ | ’命令,各平台下的重定向命令符 ‘ > ’,都无法通过命令参数直接传入进行实现,而需要在 Java 代码中做一些处理,如定义新的流来存储标准输出,等等问题。

总之,Java 中对操作系统的进程进行了封装,屏蔽了操作系统进程相关的信息。同时,在使用 Java 提供创建进程运行本地命令的时候,需要小心使用。

一般而言,使用进程是为了执行某项任务,而现代操作系统对于执行任务的计算资源的配置调度一般是以线程为对象(早期的类 Unix 系统因为不支持线程,所以进程也是调度单位,但那是比较轻量级的进程,在此不做深入讨论)。创建一个进程,操作系统实际上还是会为此创建相应的线程以运行一系列指令。特别地,当一个任务比较庞大复杂,可能需要创建多个线程以实现逻辑上并发执行的时候,线程的作用更为明显。因而我们有必要深入了解 Java 中的线程,以避免可能出现的问题。

从JDK1.5中开始引入了Java.lang.Process相关的类,从而让JVM可以通过调用对应操作系统平台的api来创建进程。一般而言,使用进程是为了执行某项任务,而现代操作系统对于执行任务的计算资源的配置调度一般是以线程为对象。创建一个进程,操作系统实际上还是会为此创建相应的线程以运行一系列指令。当需要执行一个比较庞大的的复杂任务时,就可能需要创建多个线程以实现逻辑上并发执行的时候,线程的作用更加明显。
 
4、Java线程的实现

从概念上来说,一个 Java 线程的创建根本上就对应了一个本地线程(native thread)的创建,两者是一一对应的。 问题是,本地线程执行的应该是本地代码,而 Java 线程提供的线程函数是 Java 方法,编译出的是 Java 字节码,所以可以想象的是, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。

以下是 Thread#start 方法的示例:

 public synchronized void start() { 
     …
     start0(); 
     …
 }

可以看到它实际上调用了本地方法 start0, 该方法的声明如下:

private native void start0();

Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的 . 这个方法放在一个 static 语句块中,这就表明,当该类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法。

 private static native void registerNatives(); 
  static{ 
       registerNatives(); 
  }

本地方法 registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如代码清单 2 所示。

清单 2
 JNIEXPORT void JNICALL 
 Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ 
   (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); 
 } 
 static JNINativeMethod methods[] = { 
    {"start0", "()V",(void *)&JVM_StartThread}, 
    {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, 
	 {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, 
	 {"suspend0","()V",(void *)&JVM_SuspendThread}, 
	 {"resume0","()V",(void *)&JVM_ResumeThread}, 
	 {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, 
	 {"yield", "()V",(void *)&JVM_Yield}, 
	 {"sleep","(J)V",(void *)&JVM_Sleep}, 
	 {"currentThread","()" THD,(void *)&JVM_CurrentThread}, 
	 {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, 
	 {"interrupt0","()V",(void *)&JVM_Interrupt}, 
	 {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, 
	 {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, 
	 {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, 
	 {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, 
 };

到此,可以容易的看出 Java 线程调用 start 的方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎样的逻辑呢。实际上,我们需要的是(或者说 Java 表现行为)该方法最终要调用 Java 线程的 run 方法,事实的确如此。 在 jvm.cpp 中,有如下代码段:

 JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 
	…
	 native_thread = new JavaThread(&thread_entry, sz); 
	…

这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry,如清单 3 所示。

清单 3
 static void thread_entry(JavaThread* thread, TRAPS) { 
    HandleMark hm(THREAD); 
	 Handle obj(THREAD, thread->threadObj()); 
	 JavaValue result(T_VOID); 
	 JavaCalls::call_virtual(&result,obj, 
	 KlassHandle(THREAD,SystemDictionary::Thread_klass()), 
	 vmSymbolHandles::run_method_name(), 
 vmSymbolHandles::void_method_signature(),THREAD); 
 }

可以看到调用了 vmSymbolHandles::run_method_name 方法,这是在 vmSymbols.hpp 用宏定义的:

 class vmSymbolHandles: AllStatic { 
	…
	 template(run_method_name,"run") 
	…
 }

至于 run_method_name 是如何声明定义的,因为涉及到很繁琐的代码细节,本文不做赘述。感兴趣的读者可以自行查看 JVM 的源代码。


 

 

 

 

 

 
当我们new一个java对象时其所占的内存空间,比我们预想的要多,因为除了对象本身的信息外,jvm还会给对象分配一个元数据,用来描述对象的相关信息。
这个元数据包括三个部分:
Class:一个指向Class信息的地址,用来描述对象的类型。
Flags:一些标志,用来描述对象的状态,包括对象的hashcode,及对象是否为数组。
Lock:对象的同步信息,用来表明当前对象是否被同步。
 
下图是一个32位机器上一个Integer对象内存布局


 
 
对于数组对象,它的元数据里面多一个字段Size,用来表示数组的长度
其内存布局如下


 
 
 
对于更复杂一些的对象,比如对象内部又引用了其它对象,下面看下String对象的内存布局
 
  • 大小: 27 KB
  • 大小: 33.9 KB
  • 大小: 33.5 KB
  • 大小: 30.2 KB
  • 大小: 51.3 KB
  • 大小: 50 KB
  • 大小: 101.2 KB
  • 大小: 32.5 KB
分享到:
评论

相关推荐

    JAVA线程与进程的区别

    "JAVA线程与进程的区别" JAVA语言中,线程(Thread)和进程(Process)是两个基本概念,它们都是操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。但是,它们之间有着本质的区别。 ...

    Java 的多线程,程序、进程和线程的概念31

    在IT领域,尤其是Java编程中,多线程是不可或缺的一部分,它使得程序能够同时执行多个任务,极大地提高了程序的效率和响应性。本教程“Java的多线程”由孙鑫老师主讲,旨在帮助初学者从入门到精通地掌握这一关键技能...

    java 查看JVM中所有的线程的活动状况

    在命令行中运行`jstack <pid>`(pid是Java进程的ID),即可查看该进程的线程状态。 总结来说,Java提供了多种方式来查看JVM中的线程活动状况,无论是通过编程还是使用命令行工具,都能帮助开发者诊断和优化多线程...

    操作系统中的进程、线程与Java的多线程.pdf

    "操作系统中的进程、线程与Java的多线程" 操作系统中的进程是指特定的代码序列在指定数据集上的一次执行活动,是指并行程 序的的一次执行过程。在Windows 95中,就是一个EXE文件的执行过程。这是一个动态概念,具有...

    Java并发编程进程和线程之由来Java开发Java经验技

    Java并发编程是Java开发中的重要领域,涉及到进程与线程的概念,这两个概念是理解多任务执行的基础。在现代计算机系统中,进程和线程被广泛用于实现高效的资源管理和任务调度。 **1. 进程** 进程是操作系统分配...

    java实现守护进程,有单独的监听进程, 两个或多个进程,两个或多个jvm

    `bin`目录可能包含编译后的可执行文件或脚本,用于启动和管理这些Java进程。为了深入了解实现细节,需要查看源代码并理解其逻辑。 总的来说,Java实现守护进程、监听进程以及管理多个进程和JVM涉及多线程、网络编程...

    输出java进程的jstack信息示例分享 通过线程堆栈信息分析java线程

    为了获取这些信息,我们可以使用jstack工具,它是Java开发工具包(JDK)的一部分,能够输出Java进程的线程堆栈跟踪信息。 jstack命令通常可以输出以下类型的信息: 1. 线程的完整堆栈跟踪,包括本地方法。 2. 显示...

    深入解析:Java中的线程与进程

    本文将详细探讨Java中的线程和进程的区别,以及它们在程序设计和运行时的角色和重要性 理解线程和进程的区别对于Java程序员来说至关重要。线程提供了一种高效的方式来实现并发执行,而进程则是操作系统资源分配的...

    进程与线程--小练习

    在计算机科学领域,进程与线程是操作系统中最基础且至关重要的概念。进程是程序执行时的一个实例,每个进程都有自己的独立内存空间,包括代码、数据、堆栈等资源。线程则是进程内的一个执行单元,它共享进程的内存...

    进程和线程详解

    进程和线程是计算机操作系统中的两个基本概念,对于任何软件开发者,尤其是系统级或服务器端开发者来说,理解和掌握它们至关重要。下面将详细解释这两个概念,以及它们在实际应用中的作用。 首先,我们来理解“进程...

    73道Java面试题合集-多线程与进程

    在Java编程领域,多线程和进程是两个关键的概念,对于任何有志于从事Java开发的程序员来说,理解和掌握它们至关重要。...通过深入学习和实践,开发者能够编写出更加高效、安全的多线程Java应用程序。

    java 多线程设计模式 进程详解

    《JAVA多线程设计模式》PDF 下载 《Java线程 高清晰中文第二版》中文第二版(PDF) 前言 第一章 线程简介 Java术语 线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口...

    java多媒体与多线程处理实验

    本次实验旨在深入探索Java中多线程处理与多媒体应用的核心技术,具体目标包括: 1. **理解线程概念**:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单元。 2. **线程状态与...

    Java 的多线程,程序、进程和线程的概念

    在计算机科学中,程序、进程和线程是操作系统的基础概念,尤其在Java编程语言中,理解和掌握这些概念对于开发高效、并发的软件至关重要。本文将深入探讨Java的多线程特性,以及程序、进程和线程的基本定义和它们之间...

    JAVA教程解析Java的多线程机制

    子进程与父进程拥有不同的可执行代码和数据内存空间,而同一进程中多个线程共享数据内存空间,每个线程有自己的执行堆栈和程序执行上下文(Context)。 由于这些区别,线程有时被称为轻型进程(LightWeight Process...

    Java雷电游戏,主要用SWING和Java的进程,纯Java代码写的,主要为开发人员对Java线程的认识

    Java雷电游戏是一款基于SWING图形用户界面库和Java进程管理技术开发的纯Java应用程序,旨在帮助开发者加深对Java线程的理解。通过分析这款游戏的源代码,我们可以学习到多个Java编程的重要知识点。 首先,SWING是...

Global site tag (gtag.js) - Google Analytics