首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【寻找Linux的奥秘】第九章:自定义SHELL

【寻找Linux的奥秘】第九章:自定义SHELL

作者头像
code_monnkey_
发布于 2025-06-02 01:10:49
发布于 2025-06-02 01:10:49
10000
代码可运行
举报
文章被收录于专栏:知识分享知识分享
运行总次数:0
代码可运行
QQ20250530-182747
QQ20250530-182747

前言

本专题将基于Linux操作系统来带领大家学习操作系统方面的知识以及学习使用Linux操作系统。前面我们认识并熟悉了进程的基本概念以及操作,那么本章让我们对前面所学进行融会贯通,来自定义编写一下我们使用的命令行解释器,也就是shell。本章我们要学习的是——自定义shell的编写。

1.目标

Shell 是一种用于与操作系统交互的命令行界面程序。它充当用户和操作系统内核之间的中介,通过用户输入的命令来执行操作,提供与操作系统的互动。

具体来说,Shell 可以做以下几件事:

  • 命令解释和执行:Shell 接受用户输入的命令,解析并传递给操作系统的内核执行。例如,用户可以通过 Shell 执行文件操作(如复制、移动、删除文件)、程序启动、系统管理等操作。
  • 脚本编程:Shell 还支持脚本编程,允许用户将一系列命令写入一个文件中,形成一个脚本(如 Bash 脚本)。这些脚本可以自动化任务,执行复杂的操作。
  • 交互式环境:Shell 提供一个交互式环境,用户可以在其中执行命令、查看命令输出、管理系统进程等。

我们在Linux中最常用的shell就是Bash(Bourne Again Shell)。今天我们将在bash上实现一个自定义的shell,不过由于所学知识有限,我们的自定义shell主要可以进行命令解释和执行,来贯通一下我们之前所学的内容,并且对这些知识有更深刻地认识。

下面来介绍一下我们实现自定义shell的目标:

  • 能处理普通命令
  • 能处理内建命令
  • 帮助我们理解内建命令、本地变量、环境变量这些概念
  • 帮助我们理解shell的运行原理

2. 运行原理

既然要实现一个自定义shell,那么我们就需要先知道shell的运行原理,从而搭出一个大致的框架。

下面以这个与shell典型的互动为例:

QQ20250601-113423
QQ20250601-113423

下图的用时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为bash的⽅块代表,它随着时间的流逝从左向右移动。shell从⽤⼾读⼊字符串"ls"后建⽴⼀个新的子进程,然后在子进程中运⾏ls并等待子进程结束,结束后读入"ps"同理。 如下图所示:

QQ20250601-114826
QQ20250601-114826

因此,shell读取一行输入,建立一个新的子进程,在子进程中运行指定程序并等待子进程结束,然后循环往复。

所以我们要写一个自定义shell,就需要循环下列过程:

  • 获取命令⾏
  • 解析命令⾏
  • 建⽴⼀个⼦进程(fork)
  • 替换⼦进程(exec)
  • ⽗进程等待⼦进程退出(wait)

根据这个过程,我们的框架就有了,更多的细节让我们在实现的过程中再一一发现并解决。

3. 实现

像我们使用的shell底层是用C语言编写的,为了简化一些较为繁琐的操作,接下来我们的编写采用C、C++混编,主体以C语言为主,对于特定的地方也会使用C++的一些语法。

我们实现的shell较为简单,这里就不分文件进行编写,都在一个.cc文件(Linux传统C++文件后缀)中进行编写。

接下来让我们创建好Makefile文件,方便我们进行编译:

QQ20250601-143607
QQ20250601-143607

最后,创建我们的myshell.cc文件,开始编写我们的程序:

QQ20250601-143631
QQ20250601-143631
3.1 打印命令行提示符

首先观察我们的使用的shell,可以发现shell总是会先打印出命令行提示符,然后处于阻塞状态等待用户输入。所以我们的第一步就是打印命令行提示符:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
    while(true)
    {
        //1.输出命令行提示符 
        PrintCommandPrompt();
    }

    return 0; 
}

分析一下命令行提示符,我们发现它是由[用户名@主机名 当前工作目录]$组成的,并且用户名、主机名、当前工作目录我们都可以从环境变量中获取,那么打印命令行提示符就变得非常简单了,我们需要用到之前提过的getenv函数:

QQ20250601-145134
QQ20250601-145134
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//包一下头文件,以C++的风格
#include <cstdlib>

//获得当前工作路径
const char* GetPwd()
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

//获得当前主机名
const char* GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

//获得当前用户名
const char* GetUser()
{
    const char *user = getenv("USER");
    return user == NULL ? "None" : user;
}

我们的自定义shell是在bash上创建的,所以当执行时我们的自定义shell就是bash的一个子进程,所以我们自定义shell的环境变量表是继承于bash的。

此时我们的需要把获得的当前工作路径进行拆分,获得当前工作目录,我们借助C++中stringrfind函数和substr函数可以很轻松的做到。其中rfind用于倒着从字符串查找,substr用来裁剪字符串,我们只需要使用rfind找到第一个’/'的位置,然后用substr把相对应的位置裁剪出来即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//对于查找的字符我们可以定义一个宏,便于修改
#define SLASH "/"

std::string GetPwdDir(const char *pwd)
{
    std::string dir = pwd;
    if(dir == SLASH)
        return SLASH;
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) 
        return "BUG";
    return dir.substr(pos + 1);
}

准备工作都做完了,接下来就可以打印我们的命令行提示符了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//我们把最后一个字符改为#,方便一会与bash进行区分
#define FORMAT "[%s@%s %s]# " 
#define CMDLINE_MAX 1024

//C++的模块化设计
void MakeCommandPrompt(char *out, int size)
{
    //将命令行提示符制作完后放入字符串中
    snprintf(out, size, FORMAT, GetUser(), GetHostName(), GetPwdDir(GetPwd()).c_str());
}

void PrintCommandPrompt()
{
    char prompt[CMDLINE_MAX];
    MakeCommandPrompt(prompt, CMDLINE_MAX);
    printf("%s", prompt);
    fflush(stdout);
}

这样,我们的命令行提示符就以及可以打印完成了。

3.2 获取命令行参数

接下来我们就要获取命令行参数了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()                            
{
    InitEnv();            
                    
    while(true)      
    {                           
        //1.输出命令行提示符     
        PrintCommandPrompt();
        
        //2.获取命令行参数
        char commandline[CMDLINE_MAX];
        if(!GetCommandLine(commandline, CMDLINE_MAX))
            continue;   
        
    return 0;                         
}                   

我们将获取命令行参数的返回值设置为bool类型可以在我们直接输入回车时重新进入循环,再次打印命令行提示符并重新输入,与在bash中的行为一致。接下来我们就来实现一下这个函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <cstdio.h>
bool GetCommandLine(char *out, int size)
{
    //stdin:从键盘中输入(本质是从键盘文件中读取)
    char *c = fgets(out, size, stdin);
    if(c == NULL) return false;
    //去掉换行符'\n'
    out[strlen(out) - 1] = 0;
    //只有换行符的话返回false,循环重新开始
    if(strlen(out) == 0) return false;
    return true;
}

我们通过fgets函数来获得用户的输入:

QQ20250601-152503
QQ20250601-152503

fgets 是 C语言 中安全、可靠的行读取函数,它的安全性更高,只是需要我们手动处理换行符。

我们将处理好的字符串放入commandline中,方便下一步对我们输入的命令行参数进行解释。

3.3 命令行解析

上面我们已经获得了命令行参数,那么接下来就该对命令行参数进行分析:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()               
{                                                                           
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        //2.获取命令行参数
        char commandline[CMDLINE_MAX];  
        if(!GetCommandLine(commandline, CMDLINE_MAX))
            continue;                              
            
        //3.分析命令行参数           
        if(!CommandParse(commandline))
            continue;       
    return 0;                         
}                     

那么如何分析呢?也非常简单,我们只需要将命令行参数,也就是commandline以空格为分隔符,将其填入到我们的命令行参数表中即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define DELIM " "
//命令行参数表
#define ARGV_MAX 1024
char *g_argv[ARGV_MAX];
int g_argc = 0;

bool CommandParse(char *commandline)    
{    
    g_argc = 0;    
    g_argv[g_argc++] = strtok(commandline, DELIM);    
    if(g_argv[0] == NULL)    
        return false;    
    while((bool)(g_argv[g_argc++] = strtok(NULL, DELIM)));    
    g_argc--;    
    return true;    
}    

以指定字符分割字符串,这个时候就需要用到我们的strtok函数了:

QQ20250601-180308
QQ20250601-180308

使用strtok函数时我们需要注意第一次调用我们需要传入待分割的字符串,后续的调用传入NULL即可,因此我们只需要在一个whlie循环中不断调用即可,当字符串无法再分割时返回NULL,刚好结束while循环。

命令行解析所需要进行的最主要操作就是要将我们的命令行参数分割之后填入到我们的命令行参数表中。

3.4 执行命令

我们有了命令行参数表后就可以开始执行对应的命令了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        //2.获取命令行参数
        char commandline[CMDLINE_MAX];
        if(!GetCommandLine(commandline, CMDLINE_MAX))
            continue;

        //3.分析命令行参数
        if(!CommandParse(commandline))
            continue;
            
        //4.执行命令
        Execute();
    }

    return 0; 
}

我们执行命令时通过创建一个子进程,让子进程进行程序替换来执行对应的命令。那么对于那么多的程序替换函数,我们在这里该选择哪一个呢?这里我们有了命令行参数表,那么最适合我们的就是execvp函数了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Execute()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork false:");
        exit(1);
    }
    else if(id == 0)
    {
        execvp(g_argv[0], g_argv);
        exit(1);
    }

    pid_t rid = waitpid(id, NULL, 0);
}

这样一来,我们的自定义shell的基本逻辑就已经实现了,我们可以在自定义shell上执行像ls、ps这些命令。不过许多地方的细节还没有完善,接下来让我们继续。

3.5 内建命令

在 Linux 系统中,内建命令(Built-in Commands) 是 Shell直接提供的命令,无需调用外部程序,因此执行效率更高。也就是说在执行内建命令时,父进程不会创建子进程去调用外部程序,而是父进程自己去执行。我们目前常见的内建指令有cd、pwd、echo等等这些命令,所以在执行命令前,我们需要先判断命令是否为内建命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{                                  
    while(true)
    {                              
        //1.输出命令行提示符    
        PrintCommandPrompt();                            
        //2.获取命令行参数           
        char commandline[CMDLINE_MAX];
        if(!GetCommandLine(commandline, CMDLINE_MAX))
            continue;    

        //3.分析命令行参数
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();   
     
        //4.检查是否为内建命令
        if(IsBuiltInCommand())
            continue;

        //5.执行命令
        Execute();
    }                               
                                      
    return 0;
}               

判断的方法也很简单,我们直接用命令行参数表的第一个元素,也就是命令名去一一进行比较即可,这里我们就简单的对cd、echo这两个简单的命令进行编写:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool IsBuiltInCommand()
{
    if(!strcmp(g_argv[0], "cd"))
    {         
        CommandCd(); 
        return true;   
    }          
    else if(!strcmp(g_argv[0], "echo"))
    {       
        CommandEcho();
        return true; 
    }      
    //else...
    return false;
}   
3.5.1 cd

在执行cd命令时,我们还有cd -返回上次所在目录和cd ~返回家目录以及cd返回根目录这些特殊的的参数,这些都可以通过环境变量来实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const char* GetOldpwd()
{
    const char *oldpwd = getenv("OLDPWD");
    return oldpwd == NULL ? "None" : oldpwd;
}

const char* GetHome()
{
    const char *home = getenv("HOME");
    return home == NULL ? "None" : home;
}

void CommandCd()
{
    //cd
    int ret;
    std::string old_dir = get_current_dir_name();
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) exit(1);
        ret = chdir(home.c_str());
    }
    //cd where
    else
    {
        std::string where = g_argv[1];
        //cd -/cd ~
        if(where == "-")
        {
             ret = chdir(GetOldpwd());       
        }
        else if(where == "~")
        {
             ret = chdir(GetHome());
        }
        else 
        {
             ret = chdir(where.c_str()); 
        }
    }

    if(ret == -1)
    {
        perror("cd");
        lastcode = 1;
    }
    else
    {
        lastcode = 0;
        setenv("PWD", get_current_dir_name(), 1);
        setenv("OLDPWD", old_dir.c_str(), 1);
    }                      
}

对于返回上次所在目录,环境变量中有一个名为OLDPWD的变量专门用来记录,因此我们只需要在执行cd命令前先保存一下当前所处的路径,然后再执行完后用其去更新环境变量OLDPWD即可,同时,在我们cd命令执行完后,我们也需要更新环境变量PWD,使其的值是我们当前所在的目录。

3.5.2 echo

对于echo命令,我们除了可以在显示器上打印对应的字符,还可以查看环境变量对应的值以及通过echo $?查看上一个程序的退出码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int lastcode = 0;

void CommandEcho()
{
    if(g_argc == 1)
    {
        printf("\n");
    }
    else
    {
        std::string opt = g_argv[1];           
        if(opt == "$?")
        {
            std::cout << lastcode << std::endl;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;
        }
    }
    lastcode = 0;
}

其他程序的退出码我们可以通过waitpid来获得,只需要更改一下Execute函数即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Execute()
{
	//...
	
    int statue;
    pid_t rid = waitpid(id, &statue, 0);
    if(rid > 0)
        lastcode = WEXITSTATUS(statue);
}

4. 小结

这样一来,我们的自定义shell大致上就已经是完成了,当然对比真的shell还差的远的远。不过我们编写这个自定义shell的目的还是为了巩固之前所学的知识,对他们有更深刻的认识。

其实我们的进程与我们的函数有很大的相似性:exec/exit就像call/return

  • ⼀个C程序有很多函数组成。⼀个函数可以调⽤另外⼀个函数,同时传递给它⼀些参数。被调⽤的函数执⾏⼀定的操作,然后返回⼀个值。
  • ⼀个C程序可以fork/exec另⼀个程序,并传给它⼀些参数。这个被调⽤的程序执⾏⼀定的操作,然后通过exit来返回值。调⽤它的进程可以通过wait来获取exit的返回值。

这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux⿎励将这种应⽤于程序之内的模式扩展到程序之间。 如下图所示

QQ20250601-195832
QQ20250601-195832

4. 源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<iostream>   
#include<cstring>
#include<string>                    
#include<cstdlib>
#include<sys/types.h>                          
#include<sys/wait.h>
#include<unistd.h>               
#include<errno.h>

#define CMDLINE_MAX 1024
#define FORMAT "[%s@%s %s]# "
#define DELIM " "     
#define SLASH "/"
//命令行参数表
#define ARGV_MAX 1024         
char *g_argv[ARGV_MAX];
int g_argc = 0;
                    
//环境变量表
#define ENV_MAX 1024              
char *g_env[ENV_MAX];
int g_envs;

int lastcode = 0;
                                        
void InitEnv() 
{                                      
    extern char **environ;
    memset(g_env, 0, sizeof(g_env));
    g_envs = 0; 
    //1.模仿从配置文件中设置环境变量
    for(int i = 0; environ[i]; i++)
    {       
        g_env[i] = (char*)malloc(strlen(environ[i]) + 1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }                                
    g_env[g_envs] = NULL;        
                                  
    //2.导成环境变量
    for(int i = 0; g_env[i]; i++)
    {   
        putenv(g_env[i]);
    }                                 
                   
    environ = g_env;    
}        
const char* GetPwd()
{
    const char *pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}
                                                                                                                                                                                                                                      
const char* GetOldpwd()
{
    const char *oldpwd = getenv("OLDPWD");
    return oldpwd == NULL ? "None" : oldpwd;
}
const char* GetHome()
{
    const char *home = getenv("HOME");
    return home == NULL ? "None" : home;
}

const char* GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char* GetUser()
{
    const char *user = getenv("USER");
    return user == NULL ? "None" : user;
}

std::string GetPwdDir(const char *pwd)
{
    std::string dir = pwd;
    if(dir == SLASH)
        return SLASH;
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) 
        return "BUG";
    return dir.substr(pos + 1);
}

void MakeCommandPrompt(char *out, int size)
{
    snprintf(out, size, FORMAT, GetUser(), GetHostName(), GetPwdDir(GetPwd()).c_str());
}
void PrintCommandPrompt()
{
    char prompt[CMDLINE_MAX];
    MakeCommandPrompt(prompt, CMDLINE_MAX);
    printf("%s", prompt);
    fflush(stdout);                                                                                                                                                                                                                   
}

bool GetCommandLine(char *out, int size)
{
    char *c = fgets(out, size, stdin);
    if(c == NULL) return false;
    out[strlen(out) - 1] = 0;
    if(strlen(out) == 0) return false;
    return true;

}

bool CommandParse(char *commandline)
{
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline, DELIM);
    if(g_argv[0] == NULL)
        return false;
    while((bool)(g_argv[g_argc++] = strtok(nullptr, DELIM)));
    g_argc--;
    return true;
}

//test
void PrintArgv()
{
    for(int i = 0; i <= g_argc; i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }   
    printf("argc->%d\n", g_argc);
}

void Execute()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork false:");                                                                                                                                                                                                        
        exit(1);
    }
    else if(id == 0)
    {
        execvp(g_argv[0], g_argv);
        exit(1);
    }

    int statue;
    pid_t rid = waitpid(id, &statue, 0);
    if(rid > 0)
        lastcode = WEXITSTATUS(statue);
}

void CommandCd()
{
    //cd
    int ret;                                                                                                                                                                                                                          
    std::string old_dir = get_current_dir_name();
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) exit(1);
        ret = chdir(home.c_str());
    }
    //cd where
    else
    {
        std::string where = g_argv[1];
        //cd -/cd ~
        if(where == "-")
        {
             ret = chdir(GetOldpwd());       
        }
        else if(where == "~")
        {
             ret = chdir(GetHome());
        }
        else 
        {
             ret = chdir(where.c_str()); 
        }
    }

    if(ret == -1)
    {
        perror("cd");
        lastcode = 1;
    }
    else
    {
        lastcode = 0;
        setenv("PWD", get_current_dir_name(), 1);
        setenv("OLDPWD", old_dir.c_str(), 1);
    }    
}

void CommandEcho()
{
    if(g_argc == 1)                                                                                                                                                                                                                   
    {
        printf("\n");
    }
    else
    {
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            std::cout << lastcode << std::endl;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;
        }
    }
    lastcode = 0;
}

bool IsBuiltInCommand()
{
    if(!strcmp(g_argv[0], "cd"))
    {
        CommandCd();
        return true;
    }
    else if(!strcmp(g_argv[0], "echo"))
    {
        CommandEcho();
        return true;
    }
    //else...
    return false;
}

int main()
{
    InitEnv();

    while(true)
    {
        //1.输出命令行提示符
        PrintCommandPrompt();
        //2.获取命令行参数
        char commandline[CMDLINE_MAX];
        if(!GetCommandLine(commandline, CMDLINE_MAX))
            continue;

        //3.分析命令行参数
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();

        //4.检查是否为内建命令
        if(IsBuiltInCommand())
            continue;

        //5.执行命令
        Execute();
    }

    return 0; 
}

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Java基础重构-数据类型
那么堆和栈是怎么联系起来的呢? 我们在上面给堆分配了一个地址,吧堆的地址赋给 arr,arr就通过地址指向了数组,所以arr 想操纵数组时,就通过地址,而不是直接把实体都赋给它。这种我们不再叫它基本数据类型,而是叫引用类型数据。称为 arr 引用了堆内存当中的实体。
Petterp
2022/02/09
6800
Java基础重构-数据类型
Java堆和栈的区别
在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
Java团长
2018/08/07
1.6K0
js中的值类型和引用类型的区别
(1)值类型(基本类型):字符串(string)、数值(number)、布尔值(boolean)、undefined、null (这5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值)(ECMAScript 2016新增了一种基本数据类型:symbol es6.ruanyifeng.com/#docs/symbo… )
前端老鸟
2019/10/09
4.2K0
js中的值类型和引用类型的区别
Java_内存分配
new出的空间都是作为动态内存在堆中分配的,比如new出的对象的成员属性、使用new开辟的数组中的各个元素、使用new创建的基本数据类型等
用户10551528
2023/05/09
6710
Java_内存分配
Java Review (五、数组)
数组是编程语言中最常见的一种数据结构,可用于存储多个数据,每个数组元素存放一个数据,通 常可通过数组元素的索引来访问数组元素,包括为数组元素赋值和取出数组元素的值。
三分恶
2020/07/16
5220
闲谈Android中的内存泄漏
在长久以来的 Android 开发过程中,内存泄漏一直是一个比较头疼的问题。内存泄漏会导致应用卡顿,用户体验不佳,甚至会造成应用崩溃的严重后果。所以如何科学地进行内存管理一直是大家探讨的话题,从一开始主动使用 MAT 分析 hprof 文件,到后来 LeakCanary “被动”的接收内存泄漏消息。应用中发现内存泄漏的手段越来越多了,操作也越来越便捷,但内存泄漏的问题还是不能轻易忽视的,提高应用的体验和质量也是迫在眉睫。
俞其荣
2019/07/09
1.5K0
Java内存分配之堆、栈和常量池
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码定义一个变量时,java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另做他用。
技术从心
2019/08/06
1.6K0
Java内存分配之堆、栈和常量池
java学习---new的对象怎么被内存回收
如上面代码,简单说就是new User()的时候,会返回一个地址,并且将地址赋值给引用u,当这个引用被u持有的时候,java会认为这个对象时有用的,不会回收对象,如果你之后执行了好比说:
wust小吴
2019/07/08
2.8K0
java的栈内存和堆内存_Java本地方法栈
在方法中定义的一些基本类型的变量和对象的引用变量都在方法的栈内存中分配,当在一段代码块中定义一个变量时,Java就在栈内存中为这个变量分配内存空间,当超出变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立刻被另作他用。
全栈程序员站长
2022/10/03
1.1K0
java的栈内存和堆内存_Java本地方法栈
深入JVM内存区域管理,值得你收藏
JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包
阿伟
2020/02/19
4630
Java内存管理与垃圾回收
根据《Java虚拟机规范(第2版)》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图所示:
程序员飞飞
2020/02/27
1.1K0
Java内存管理与垃圾回收
Java的内存机制
Java 把内存划分成两种:一种是栈内存,另一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后(比如,在函数A中调用函数B,在函数B中定义变量a,变量a的作用域只是函数B,在函数B运行完以后,变量a会自动被销毁。分配给它的内存会被回收),Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。
哲洛不闹
2018/09/19
6190
Java的内存机制
java+内存分配及变量存储位置的区别
Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识。一般Java在内存分配时会涉及到以下区域:
bear_fish
2018/09/20
9600
图解 Java 数组与内存控制
Java的数组变量是一种引用类型的变量,数组变量并不是数组本身,它只是指向堆内存中的数组对象,改变一个数组变量所引用的数组,可以造成数组长度可变的假象。
CoderJed
2018/09/13
1.6K1
图解 Java 数组与内存控制
内存泄露从入门到精通三部曲之基础知识篇
1 首先以一个内存泄露实例来开始本节基础概念的内容: 实例1:(单例导致内存对象无法释放而泄露) 可以看出ImageUtil这个工具类是一个单例,并引用了activity的context。 试想这个场景,应用起来以后,转屏。转屏以后,旧MainActivity会destroy,新MainActivity会重建,导致单例ImageUtil重新getInstance。很不幸的是,由于instance已经不是空的了,所以ImageUtil不会重建,还持有之前的Context,也就是之前的那个MainAc
腾讯Bugly
2018/03/23
1.3K0
内存泄露从入门到精通三部曲之基础知识篇
Java的内存机制
Java 把内存划分成两种:一种是栈内存,另一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后(比如,在函数A中调用函数B,在函数B中定义变量a,变量a的作用域只是函数B,在函数B运行完以后,变量a会自动被销毁。分配给它的内存会被回收),Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。
飞奔去旅行
2019/06/13
6360
Java的内存机制
JavaScript之再学习
JavaScript 是一种面向对象的动态语言,它包含类型、运算符、标准内置( built-in)对象和方法。它的语法来源于 Java 和 C,所以这两种语言的许多语法特性同样适用于 JavaScript。需要注意的一个主要区别是 JavaScript 不支持类,类这一概念在 JavaScript 通过对象原型(object prototype)得到延续。另一个主要区别是 JavaScript 中的函数也是对象,JavaScript 允许函数在包含可执行代码的同时,能像其他对象一样被传递。
Abalone
2022/07/14
4110
JavaScript之再学习
操作系统中 heap 和 stack 的区别
概念: 堆栈是两种数据结构,是一种数据项按序排列的数据结构,只能在一端进行插入和删除操作。堆为队列优先,先进先出(FIFO)。栈为先进后出(FILO)。
全栈程序员站长
2022/07/01
6200
堆内存和栈内存
在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
用户2909867
2018/08/22
1.5K0
JAVA基础知识点:内存、比较和Final
1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题。(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间。 释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作。但同时,它也加重了JVM的工作。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。 2.什么叫java的内存泄露 在java中,
老白
2018/03/19
1.3K0
相关推荐
Java基础重构-数据类型
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档