C#运行时相互关系浅析

本文主要讲述运行时类型、对象、线程栈和托管堆之间的相互关系,静态方法、实例方法和虚方法的区别,以及内存的分配和回收。

线程栈:在一个进程中可能包含多个线程,一个线程在创建的时候,会分配到一个大小1MB大小的栈,栈用于存储方法的实参、形参以及方法内部的局部变量,栈是从高位内存地址向地位地址构建的,由于栈有先进后出的特点,所以先定义的变量后被回收。

下面来看一个简单的例子,让你更了解线程栈

由于线程栈是从高位开始分配内存,先分配的我就画在上面了,在调用F1();方法时,分配内存的顺序是:name->n->F2的返回地址->Age->name;回收内存的顺序当然是反过来的。在一个方法中,应该包含一些序幕代码,进行一些初始化工作,还有一些尾声代码,等方法执行完成之后做一些回收工作。由于方法的返回地址先分配,在方法执行完成的时候回到返回地址,递归太深就容易出现栈溢出,请看我的《递归再一次让哥震惊了》,因为参数、局部变量都必须等到方法返回的时候才能回收。

在介绍托管堆之前先看看两个简单的类:

publicclassPerson
{
privateintheight;
publicvoidSetHeight(intheight)
{
this.height = height;
}
publicvirtualvoidSay(stringword) { }
publicstaticstringHead()
{
return"my head";
}
publicstaticintAge = 100;
}
publicclassStudent : Person
{
publicoverridevoidSay(stringword)
{
Console.WriteLine(word);
}
} 

staticvoidMain(string[] args)
{
Person student = newStudent();
student.Say("Hello cth");
student.SetHeight(172);
Person.Head();
Console.ReadLine();
}

CLR会在第一次访问一个对象时加载该对象,在这里,定义变量student时会为Person对象在线程栈中分配内存,第一次加载吗,在构造一个Student对象之前先要加载Student对象,并为Student类型对象分配内存,并构建一个Student对象。对象的地址存入线程栈中的局部变量student 中,我们知道类型对象的内容包含:类型对象指针、同步索引块、静态字段和方法(静态的和非静态的),不管是类型对象、还是实例类型都必须有类型对象指针、同步索引块;我们知道静态字段属于类,被这个类的所有实例共享,当然静态字段的内存是在类型本身中分配的,方法也是类的所有实例共享的,他的内存也是在类型本身中分配的,在每一个类型对象中都有一个方法表,类中定义的方法都有一个对应的项。

在构造一个对象的实例时,只需要为类型对象指针、同步索引块、该对象的实例字段分配内存,对于对象实例来说,类型对象指针可以让实例访问类型对象中德静态字段、方法等。

Student是线程栈中的定义的一个局部变量,保存Student的一个实例的在托管堆中的地址,所以他可以访问Student对象中的字段,方法,其实访问方法是通过类型对象指针访问类型对象Student中的方法表中对象的项。

Say方法的执行过程:变量student指向的是一个Student对象,调用的当然是Student类型对象中的Say方法,尽管在定义student的时候是Person类型,因为他是引用类型,他指向的是托管堆中Student对象的内存,然后遍历该对象的方法表,找到该方法调用。

特别说明虚方法,JIT在虚方法中加了一些额外的代码,方法每次调用的时候都会执行这些代码,这些代码会检查发出调用的变量,然后根据这个变量找到其应用的对象,然后调用这个对象的方法,若没有这些代码,你觉得CLR是调用父类的方法还是调用之类的方法呢,虚方法带来方便的同时,也多了这些必须的检查的代码。

SetHeight方法的执行过程:和Say方法前面是一样,只是在遍历Student对象的方法表时没有找到该方法,我们知道父类中定义的非private方法都可以被子类继承,是因为每个类型都定义了一个字段引用了他的基类,如果一个类调用的方法那个方法不是自己定义的,那么编译器会回溯类层次结构,一直到基类Object,找到相关的方法并调用,如果没有找到相关的方法就报了异常呗。所以SetHeight方法其实调用的是Person中的SetHeight方法。

Head方法的执行:由于Head方法是静态方法和上面两个方法有所不同,调用静态方法的时候,CLR会定位与静态方法对象的类型对象,然后在对应实例对象对象的方法表中查找相关的记录项,如果没有找到,同样会回溯。

当执行完student.SetHeight(172);时,student在也没有被引用,成为垃圾,在其所在的方法返回之前将会被回收,也就是说student实例对象被回收,释放其所在的内存,而类型对象不会被回收,类型对象的生成周期是:对象被加载到CLR中,直到其所在的AppDomain卸载。静态字段是他所引用类型的跟,所以被静态类型引用的对象永远不会被回收,如果其引用的是一个集合对象,并向其中不断的加入元素的话,就会造成内存泄露。

以上就是关于C#运行时相互关系的全部内容,希望对大家的学习有所帮助。

(0)

相关推荐

  • 让应用程序只运行一个实例的实现方法

    在我们的程序当中如果要实现类似<360软件管家>的功能,就要解决两个问题,首先是要判断该程序已有一个实例在运行,其次是要将已运行的应用程序实例激活,同时退出第二个应用程序实例. 对于第一个问题,我们可以通过设置命名互斥对象或命名信标对象,在程序启动的时候检测互斥对象或信标对象,如互斥对象或信标对象已存在,则可以判断此程序已有一个实例正在运行. 第二个问题是如何找到已经运行的应用程序实例,如果我们能够找到已运行实例主窗口的指针,即可调用SetForegroundWindow来激活该实例.我们可以

  • 解决C#程序只允许运行一个实例的几种方法详解

    本文和大家讲一下如何使用C#来创建系统中只能有该程序的一个实例运行.要实现程序的互斥,通常有下面几种方式,下面用 C# 语言来实现:方法一:使用线程互斥变量. 通过定义互斥变量来判断是否已运行实例.把program.cs文件里的Main()函数改为如下代码: 复制代码 代码如下: using System;using System.Windows.Forms;using System.Runtime.InteropServices;namespace NetTools{    static cl

  • C#确保只有一个实例在运行的方法

    本文实例讲述了C#确保只有一个实例在运行的方法.分享给大家供大家参考.具体实现方法如下: public static Process RunningInstance() { Process current = Process.GetCurrentProcess(); Process[] processes = Process.GetProcessesByName (current.ProcessName); //查找相同名称的进程 foreach (Process process in proc

  • C#简单实现防止多个程序运行的方法

    本文实例讲述了C#简单实现防止多个程序运行的方法.分享给大家供大家参考,具体如下: /// <summary> /// 应用程序的主入口点. /// </summary> [STAThread] static void Main() { System.Diagnostics.Process[] ps = System.Diagnostics.Process.GetProcessesByName(System.Diagnostics.Process.GetCurrentProcess

  • bat脚本实例实现只允许运行一个实例(安装程序、创建快捷方式脚本)

    复制代码 代码如下: ;我的第一个安装脚本!include "MUI2.nsh"!define DIR "D:\workspace\nsis\files" Name "安装程序"Icon "${DIR}\setup128.ico"OutFile "setup.exe"InstallDir "$PROGRAMFILES\kaserv"RequestExecutionLevel admin

  • C# WinForm 判断程序是否已经在运行,且只允许运行一个实例,附源码

    我们开发WinFrom程序,很多时候都希望程序只有一个实例在运行,避免运行多个同样的程序,一是没有意义,二是容易出错. 为了更便于使用,笔者整理了一段自己用的代码,可以判断程序是否在运行,只运行一个实例,而且能实现当程序在运行时,再去双击程序图标,直接呼出已经运行的程序. 下面看代码,只需在程序的入口文件中加如下代码即可: static class Program { /// <summary> /// 应用程序的主入口点. /// </summary> [STAThread] s

  • C#编程中设置程序只可被运行一次的方法

    防止程序运行多个实例的方法有多种,如:通过使用互斥量和进程名等.而我想要实现的是:在程序运行多个实例时激活的是第一个实例,使其获得焦点,并在前端显示. 主要用到两个API 函数: ShowWindowAsync 该函数设置由不同线程产生的窗口的显示状态. SetForegroundWindow 该函数将创建指定窗口的线程设置到前台,并且激活该窗口.键盘输入转向该窗口,并为用户改各种可视的记号.系统给创建前台窗口的线程分配的权限稍高于其他线程. 代码如下: 引用以下命名空间: using Syst

  • C#判断某程序是否运行的方法

    本文实例讲述了C#判断某程序是否运行的方法,分享给大家供大家参考. 具体实现方法如下: [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); [DllImp

  • C#如何防止程序多次运行的技巧

    一.引言 最近发现很多人在论坛中问到如何防止程序被多次运行的问题的,如: http://social.msdn.microsoft.com/Forums/zh-CN/6398fb10-ecc2-4c03-ab25-d03544f5fcc9, 所以这里就记录下来,希望给遇到同样问题的朋友有所参考的,同时也是对自己的一个积累.在介绍具体实现代码之前,我们必须明确解决这个问题的思路是什么的?下面只要分享我的一个思考的这个问题的方式: 1.当我们点击一个exe文件时,此时该exe程序将会运行,我们可以看

  • C#运行时相互关系浅析

    本文主要讲述运行时类型.对象.线程栈和托管堆之间的相互关系,静态方法.实例方法和虚方法的区别,以及内存的分配和回收. 线程栈:在一个进程中可能包含多个线程,一个线程在创建的时候,会分配到一个大小1MB大小的栈,栈用于存储方法的实参.形参以及方法内部的局部变量,栈是从高位内存地址向地位地址构建的,由于栈有先进后出的特点,所以先定义的变量后被回收. 下面来看一个简单的例子,让你更了解线程栈 由于线程栈是从高位开始分配内存,先分配的我就画在上面了,在调用F1();方法时,分配内存的顺序是:name->

  • 在Docker容器中不需要运行sshd的原因浅析

    当开始使用Docker时,人们经常问:"我该如何进入容器?",其他人会说"在你的容器里运行一个SSH服务器".但是,从这篇博文中你将会了解到你根本不需要运行SSHd守护进程来进入你的容器.当然,除非你的容器就是一个SSH服务器. 运行SSH服务器是很想当然的,因为它提供了进入容器的简便方式.在我们公司基本上每个人都最少使用过一次SSH.我们中有很大一部分人每天都会使用它,并且他们很熟悉公钥与私钥,无密码登录,密钥代理,甚至有时会使用端口转发和其他不常用的功能.正因如

  • 深入理解Java运行时数据区_动力节点Java学院整理

    JVM体系结构和运行时数据区概述 要理解JVM的运行时数据区, 必须先要理解JVM的体系结构, 因为虚拟机的体系结构基本上解释了"为什么会有这些运行时数据区" . JVM的体系结构如下: 由此可见, 运行时数据区的划分, 是和JVM的体系结构相关的. 本文主要介绍运行时数据区的划分, 对体系结构不做深入的讲解. 简单概括一下, 类加载器子系统用于将class文件加载到虚拟机的运行时数据区中(准确的说应该是方法区) . 可以认为执行引擎是字节码的执行机制, 一个线程可以看做是一个执行引擎

  • Android 在程序运行时申请权限的实例讲解

    这里我们以拨打电话申请权限来写个小例子,也就是CALL_PHONE,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限,在Android6.0系统出现之前,拨打电话功能的实现其实非常简单,修改activity_mainxml中的代码,如下: <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android=&qu

  • JavaScript 对引擎、运行时、调用堆栈的概述理解

     随着JavaScript越来越流行,越来越多的团队广泛的把JavaScript应用到前端.后台.hybrid 应用.嵌入式等等领域. 这篇文章旨在深入挖掘JavaScript,以及向大家解释JavaScript是如何工作的.我们通过了解它的底层构建以及它是怎么发挥作用的,可以帮助我们写出更好的代码与应用.据 GitHut 统计显示,JavaScript 长期占据GitHub中 Active Repositories 和 Total Pushes 的榜首,并且在其他的类别中也不会落后太多. 如果

  • matplotlib运行时配置(Runtime Configuration,rc)参数rcParams解析

    什么是运行时配置(Runtime Configuration,rc) Matplotlib使用matplotlibrc配置文件来自定义图形的各种属性,称之为rc配置或rc参数(rcParams).通过rc参数可以修改matplotlib绝大多数属性的默认值,包括窗体大小.每英寸的点数.线条宽度.颜色.样式.坐标轴.坐标和网络属性.文本.字体等. 运行时配置的默认值 运行时配置的默认值存放在默认的matplotlibrc文件中. matplotlibrc文件与rcParams的关系 rcParam

  • Java三个类加载器及它们的相互关系

    一.什么是类加载器? 虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称为"类加载器" 类加载器可以说是Java语言的一项创新,也是Java语言流行的重要原因之一,它最初是为了满足Java Applet的需求而开发出来的.虽然目前Java Applet技术基本上已经"死掉",但类加载器却在类层次划分.OSGi.

  • 面试时必问的JVM运行时数据区详解

    目录 前言 正文 1.运行时数据区(Run-Time Data Areas) 1)程序计数器(Program Counter Register) 2)Java虚拟机栈(Java Virtual Machine Stacks) 3)本地方法栈(Native Method Stacks) 4)堆(Heap) 5)方法区(Method Area) 6)运行时常量池(Run-Time Constant Pool) 2.Java 中有哪几种常量池? 3.class 文件常量池 4.运行时常量池 5.字符串

  • JAVA JVM运行时数据区详解

    目录 一.前言 二.运行时数据区整体概架构 三.程序计数器 四.虚拟机栈 1.栈的特点 2.栈帧的内部结构 3.局部变量表 4.操作数栈 5.动态链接 6.方法返回地址 五.本地方法栈 六.堆 1.设置堆大小的参数 2.对象分配过程 3.堆中的GC 4.内存分配策略 5.什么是TLAB 6.堆是分配对象存储的唯一选择吗? 七.方法区 1.方法区概述 2.设置方法区内存大小 3.如何解决OOM问题? 4.方法区存储什么 5.方法区的演进细节 6.方法区的GC 总结 一.前言 这是JVM系列文章的第

  • Java虚拟机运行时栈的栈帧

    目录 Java虚拟机栈概述 局部变量表 操作数栈 动态连接 方法的返回地址 结合javap命令理解栈帧 Java虚拟机栈概述 Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同.虚拟机栈描述的是Java方法执行的内存模型:栈帧(Stack Frame)是用于支持Java虚拟机进行方法调用和执行的数据结构,它是虚拟机栈中的栈元素.每个方法在执行的同到都会创建一个栈帧用于存储局部变量表.操作数栈.动态链接.方法出口等信息. 在编译程序代码的

随机推荐