当前位置:首页 » 存储配置 » java线程局部存储

java线程局部存储

发布时间: 2023-04-30 18:21:53

java 基本数据类型作为局部变量存储在哪

基本数据类型作为局部变量是放在栈中的,new出来的对象是放在堆中的,用static声明的变量是静态变量,静态变量和字符串常量是放在data segment中的

❷ 使用线程池时一定要注意的五个点

很多场景下应用程序必须能够处理一系列传入请求,简单的处理方式是通过一个线程顺序的处理这些请求,如下图:

单线程策略的优势和劣势都非常明显:

优势:设计和实现简单;劣势:这种方式会带来处理效率的问题,单线程的处理能力是有限,不能发挥多核处理器优势。

在这种场景下我们就需要考虑并发,一个简单的并发策略就是Thread-Per-Message模式,即为每个请求使用一个新的线程。

Thread-Per-Message策略的优势和劣势也非常明显:

优势:设计和实现比较简单,能够同时处理多个请求,提升响应效率;

劣势:主要在两个方面

1.资源消耗 引入了在串行执行中所没有的开销,包括线程创建和调度,任务处理,资源分配和回收以及频繁上下文切换所需的时间和资源。2.安全

有没有一种方式可以并发执行又可以克服Thread-Per-Message的问题?

采用线程池的策略,线程池通过控制并发执行的工作线程的最大数量来解决Thread-Per-Message带来的问题。可见下图,请求来临时先放入线程池的队列

线程池可以接受一个Runnable或Callable<T>任务,并将其存储在临时队列中,当有空闲线程时可以从队列中拿到一个任务并执行。

反例(使用 Thread-Per-Message 策略)

正例(使用 线程池 策略)

JAVA 中(JDK 1.5+)线程池的种类:

程序不能使用来自有界线程池的线程来执行依赖于线程池中其他任务的任务。

有两个场景:

要缓解上面两个场景产生的问题有两个简单的办法:

真正解决此类方法还是需要梳理线程池执行业务流程,不要在有界线程池中执行相互依赖的任务,防止出现竞争和死锁。

向线程池提交的任务需要支持中断。从而保证线程可以中断,线程池可以关闭。线程池支持 java.util.concurrent.ExecutorService.shutdownNow() 方法,该方法尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务的列表。

但是 shutdownNow() 除了尽力尝试停止处理主动执行的任务之外不能保证一定能够停止。例如,典型的实现是通过Thread.interrupt()来停止,因此任何未能响应中断的任务可能永远不会终止,也就造成线程池无法真正的关闭。

反例:

正例:

线程池中的所有任务必须提供机制,如果它们异常终止,则需要通知应用程序.

如果不这样做不会导致资源泄漏,但由于池中的线程仍然被会重复使用,使故障诊断非常困难或不可能。

在应用程序级别处理异常的最好方法是使用异常处理。异常处理可以执行诊断操作,清理和关闭Java虚拟机,或者只是记录故障的详细信息。

也就是说在线程池里执行的任务也需要能够抛出异常并被捕获处理。

任务恢复或清除操作可以通过重写 java.util.concurrent.ThreadPoolExecutor 类的 afterExecute() 钩子来执行。

当任务通过执行其 run() 方法中的所有语句并且成功结束任务,或者由于异常而导致任务停止时,将调用此钩子。

可以通过自定义 ThreadPoolExecutor 服务来重载 afterExecute()钩子。

还可以通过重载 terminated() 方法来释放线程池获取的资源,就像一个finally块。

反例:

任务意外终止时作为一个运行时异常,无法通知应用程序。此外,它缺乏恢复机制。因此,如果Task抛出一个NullPointerException ,异常将被忽略。

正例:

另外一种方式是使用 ExecutorService.submit() 方法(代替 execute() 方法)将任务提交到线程池并获取 Future 对象。

当通过 ExecutorService.submit() 提交任务时,抛出的异常并未到达未捕获的异常处理机制,因为抛出的异常被认为是返回状态的一部分,因此被包装在ExecutionException ,并由Future.get() 返回。

java.lang.ThreadLocal 类提供线程内的本地变量。根据Java API

ThreadLocal对象需要关注那些对象被线程池中的多个线程执行的类。

线程池缓存技术允许线程重用以减少线程创建开销,或者当创建无限数量的线程时可以降低系统的可靠性。

当 ThreadLocal 对象在一个线程中被修改,随后变得可重用时,在重用的线程上执行的下一个任务将能看到该线程上执行过的上一个任务修改的ThreadLocal 对象的状态。

所以要在使用线程池时重新初始化的ThreadLocal对象实例。

反例:

DiaryPool类创建了一个线程池,它可以通过一个共享的无界的队列来重用固定数量的线程。

在任何时候,不超过numOfThreads个线程正在处理任务。如果在所有线程都处于活动状态时提交其他任务,则 它们在队列中等待,直到线程可用。

当线程循环时,线程的线程局部状态仍然存在。

下表显示了可能的执行顺序:

时间任务线程池提交方法日期1t11doSomething1()星期五2t22doSomething2()星期一3t31doSomething3()星期五

在这个执行顺序中,期望从doSomething2() 开始的两个任务( t 2和t 3 doSomething2() 将当天视为星 期一。然而,因为池线程1被重用,所以t 3观察到星期五。

解决方案(try-finally条款)

符合规则的方案removeDay() 方法添加到Diary类,并在try‐finally 块中的实现doSomething1() 类的doSomething1() 方法的语句。finally 块通过删除当前线程中的值来恢复threadlocal类型的days对象的初始状态。

如果threadlocal变量再次被同一个线程读取,它将使用initialValue()方法重新初始化 ,除非任务已经明确设置了变量的值。这个解决方案将维护的责任转移到客户端( DiaryPool ),但是当Diary类不能被修改时是一个好的选择。

解决方案(beforeExecute())

使用一个自定义ThreadPoolExecutor 来扩展 ThreadPoolExecutor并覆盖beforeExecute() 方法。beforeExecute() 方法在Runnable 任务在指定线程中执行之前被调用。该方法在线程 “t” 执行任务 “r” 之前重新初始化 threadlocal 变量。

❸ java jvm 的内存到底是什么分配的,看得有点糊涂。

一个完整的Java程序运行过程会涉及以下内存区域:

l寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制。

l栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。

l堆:用来存放动态产生的数据,手败比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。

模薯猜l常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于旦型堆中。

l代码段:用来存放从硬盘上读取的源程序代码。

l数据段:用来存放static定义的静态成员。

❹ java里找变量来源

一、局部变量存储在方法栈中

在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其盯判对应在该方法中声明的变量随凯察改着栈的销毁而结束,这就局部变量只能在方法中有效的原因。在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。

(1)当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中

(2)当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。

二、全局变量存储在堆中

在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量

(1)当声明的是基本类型的变量其变量名及其值放在没亏堆内存中的

(2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中

❺ 主内存与java内存区域(堆,方法区)有什么区别

这两天看了一下深入浅出JVM这本书,推荐给高级的java程序员去看,对你了解JAVA的底层和运行机制有
比较大的帮助。
废话不想讲了.入主题:
先了解具体的概念:
JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)
堆区:
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
栈区:
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
方法区:
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
为了更清楚地搞明白发生在运行时数据区里的黑幕,我们来准备2个小道具(2个非常简单的小程序)。
AppMain.java
public class AppMain
//运行时, jvm 把appmain的信息都放入方法区
{
public static void main(String[] args) //main 方法本身放入方法区。
{
Sample test1 = new Sample( " 测试1 " ); //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面
Sample test2 = new Sample( " 测试2 " );
test1.printName();
test2.printName();
}
}
Sample.java
public class Sample //运行时, jvm 把appmain的信息都放入方法区
{
/** 范例名称 */
private name; //new Sample实例后, name 引用放入栈区里, name 对象放入堆里
/** 构造方法 */
public Sample(String name)
{
this .name = name;
}
/** 输出 */
public void printName() //print方法本身放入 方法区里。
{
System.out.println(name);
}
}
OK,让我们开始行动吧,出发指令就是:“java AppMain”,包包里带好我们的行动向导图,Let’s GO!

系统收到了我们发出的指令,启动了一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据,然后把Appmain类的类信息存放到运行时数据区的方法区中。这一过程称为AppMain类的加载过程。
接着,Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是:
Sample test1=new Sample("测试1");
语句很简单啦,就是让java虚拟机创建一个Sample实例,并且呢,使引用变量test1引用这个实例。貌似小case一桩哦,就让我们来跟踪一下Java虚拟机,看看它究竟是怎么来执行这个任务的:
1、 Java虚拟机一看,不就是建立一个Sample实例吗,简单,于是就直奔方法区而去,先找到Sample类的类型信息再说。结果呢,嘿嘿,没找到@@,这会儿的方法区里还没有Sample类呢。可Java虚拟机也不是一根筋的笨蛋,于是,它发扬“自己动手,丰衣足食”的作风,立马加载了Sample类,把Sample类的类型信息存放在方法区里。
2、 好啦,资料找到了,下面就开始干活啦。Java虚拟机做的第一件事情就是在堆区中为一个新的Sample实例分配内存, 这个Sample实例持有着指向方法区的Sample类的类型信息的引用。这里所说的引用,实际上指的是Sample类的类型信息在方法区中的内存地址,其实,就是有点类似于c语言里的指针啦~~,而这个地址呢,就存放了在Sample实例的数据区里。
3、 在JAVA虚拟机进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。OK,原理讲完了,就让我们来继续我们的跟踪行动!位于“=”前的Test1是一个在main()方法中定义的变量,可见,它是一个局部变量,因此,它被会添加到了执行main()方法的主线程的JAVA方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例,也就是说,它持有指向Sample实例的引用。
OK,到这里为止呢,JAVA虚拟机就完成了这个简单语句的执行任务。参考我们的行动向导图,我们终于初步摸清了JAVA虚拟机的一点点底细了,COOL!
接下来,JAVA虚拟机将继续执行后续指令,在堆区里继续创建另一个Sample实例,然后依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而获得printName()方法的字节码,接着执行printName()方法包含的指令。

❻ Java数组是存储在内存中的什么地方

因为我也不清楚你理解的内存指啥,说我的理解,猜测一下,但愿符合你的理解:
对程序而言,内存分为:
a、静态区块或叫全局区块,指程序加载的时候就分配的固定存储区;
b、栈内存,代码块执行的时候动态分配的内存块,代码运行完后,操作系统或运行平台负责自动回收这部分使用的内存。
c、堆内存块,由操作系统提供api程序自由分配管理的内存块。
java的内存管理机制,我个人认为是黑盒的,写java也很少有人去关心,但是如果看虚拟机的实现源码还是能知道的。(楼主懂c由兴趣自己看)。那么从逆向工程的角度我们可以猜测到:
静态类,静态变量,存储在,静态区块中。
类本身代码需要时再加载到内存中,java的动态代理实现的根本。既然是动态加载肯定是在堆中,不过加载进来的类代码可能永不会销毁(虚拟机运行区间内,可以加快运行平台的处理速度,java属于半编译半解释的一门语言,要解释执行的是预先编译好的指令,就是class类文件)。
我们创建的任何对象,时间是随机的,创建的时候才会分配内存,必然在堆上。虚拟机的垃圾回收机制要管理的刚好是这部分内存。
类中的方法,或静态代码块必定运行在占上,代码运行完,内存立即回收。此处有个例外,不同于c语言或操作系统实现。在方法中返回的任何对象:c需要动态分配存储空间,兵自行管理,或者申明为全局局部静态存储,代码运行完,对象才能保留下来。java可以直接保留下来。说明虚拟机自动给我们申请了动态内存保存对象。此处分配的内存也需要垃圾回收机制管理。没门语言都有原始类型,方法或函数中返回的原始类型不需要从堆上分配内存,直接复制给接收变量,或代码运行的形参所分配的空间中即可。
从操作系统理解,内存分为内核使用的内存与程序使用的内存,java运行在虚拟机上,虚拟机运行在操作系统上,内核内存属于操作系统自用内存,由操作系统负责管理,运用程序不能直接管理分配内核内存。应用程序可管理的是分配给自己的运行空间。因此java使用内存属于应用程序内存。内核内存管理方式windows与unix实现管理方式不一样,最大差别windows的每一个程序,内核需要使用掉一部分地址空间,余下的留给应用程序。如32位系统,总共可以使用的地址空间是4G内核要用掉其中1G或者2G。但是unix实现的系统应用程序可以直接使用4G地址空间。有兴趣请参考操作系统内核相关书籍。

热点内容
运营为什么区分ios和安卓 发布:2025-05-15 03:30:02 浏览:630
主播网站源码 发布:2025-05-15 02:50:56 浏览:168
中文编程语言有哪些 发布:2025-05-15 02:48:59 浏览:536
配置中心应急流程有哪些 发布:2025-05-15 02:37:31 浏览:670
php宏定义 发布:2025-05-15 02:32:54 浏览:271
咸鱼支付密码哪里改 发布:2025-05-15 02:32:53 浏览:521
存储机箱 发布:2025-05-15 02:31:31 浏览:837
编程很累吗 发布:2025-05-15 02:29:25 浏览:553
疫情期间访问国外网络 发布:2025-05-15 02:24:24 浏览:248
我的世界网易版游戏服务器 发布:2025-05-15 02:23:46 浏览:222