标签归档:Java

Java SPI 机制分析及其优缺点

SPI 概述

SPI 全称为(Service Provider Interface),是 JDK 内置的一种服务发现机制。它可以动态的为某个接口寻找服务实现,有点类似 IOC(Inversion of Control)的思想,将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

使用 SPI 机制需要在 classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。

以 JavaMail 程序为例,协议层只需要定义好邮件发送接口,业务层实现对应的协议如 SMTP, IMAP, POP 就可以了。

JavaMail API

SPI 实例说明

接下来通过一个具体的例子来讲解 SPI 的用法,例子的代码在 github 上可以看到。

例子代码在IDEA中的结构如图:

java-spi-example

(1)首先我们提供一个接口类 IOperation 以及它的两个实现类 PlusOperationImplDivisionOperationImpl,都在 com.zhoukaibo.spi 这个包路径下。

1
2
3
4
5
6
7
8
--> 阅读全文

JVM最多能创建多少个线程: unable to create new native thread

有应用报出这样的异常“java.lang.OutOfMemoryError: unable to create new native thread”。甚至机器上执行shell命令也会报”-bash: fork: Resource temporarily unavailable”异常。机器上的其他应用如hadoop也会受影响:

一看以为内存不够导致无法创建新的线程,但是观察机器上的内存还有空闲,猜测是哪个地方对线程创建有限制。

首先需要排除操作系统对线程创建数的限制,参考:《JVM中可生成的最大Thread数量》一文,设置操作系统可以支持创建10万个线程:

当前测试环境为:

测试程序见本文最后面。测试结果:突破了网上所说的32000个线程数,成功创建了 10万个线程
(由于/proc/sys/kernel/pid_max默认为32768,所以网上很多测试程序测试JVM只能创建32000个线程。)

创建9W多个线程后,进程占用内存:VIRT=40.5g RES=4.7g,用free -g查看系统还有9G的空闲(free)内存。


JVM最多能启动的线程数参照公式:

  • MaxProcessMemory : 进程的最大寻址空间
  • JVMMemory : JVM内存
  • ReservedOsMemory : 保留的操作系统内存,如Native heap,JNI之类,一般100多M
  • ThreadStackSize : 线程栈的大小,jvm启动时由Xss指定

MaxProcessMemory:如32位的linux默认每个进程最多申请3G的地址空间,64位的操作系统可以支持到46位(64TB)的物理地址空间和47位(128T)的进程虚拟地址空间(linux 64位CPU内存限制)。

JVM内存:由Heap区和Perm区组成。通过-Xms和-Xmx可以指定heap区大小,通过-XX:PermSize和-XX:MaxPermSize指定perm区的大小(默认从32MB 到64MB,和JVM版本有关)。

线程栈ThreadStackSize:

Java程序中,每个线程都有自己的Stack Space。这个Stack Space的空间是独立分配的,与-Xmx和-Xms指定的堆大小无关。Stack Space用来做方法的递归调用时压入Stack Frame。所以当递归调用太深的时候,就有可能耗尽Stack Space,爆出StackOverflow的错误。对于32位JVM,缺省值为256KB,对于64位JVM,缺省值为512KB。最大值根据平台和特定机器配置的不同而不同。如果超过最大值,那么将报告java/lang/OutOfMemoryError消息。

--> 阅读全文

java集合类型转换:list,set,数组与map之间的转换

平常经常遇到java集合数据类型之间的转换,记录在此:

1. list转为set

2. set转为list

3. 数组转为set

此时,数组中的元素会去重。

4. set转数组

5. 数组转为定长list

此时list中有三个元素,不能进行add/remove操作,否则会报“java.lang.UnsupportedOperationException”。
Arrays.asList()返回的是一个定长的List,不能转换为ArrayList,只能转换为AbstractList。这是因为asList()返回的是某个数组的列表形式,返回的列表只是数组的另一个视图,而数组本身并没有消失,对列表的任何操作最终都反映在数组上,所以不支持remove,add方法。

6. 数组转为变长list

7. list转数组

8. map

java网络编程中ipv4和ipv6导致的机器名长短问题

使用了Java网络编程,涉及到ipv4和ipv6的问题,在hadoop集群中由于机器配置不一,会导致不同机器获取的机器名长短不一,从而引发一系列问题,如“hadoop或yarn集群任务数据本地化很差(后面会写篇文章进行分析)”。

FQDN是Fully Qualified Domain Name的缩写, 含义是完整的域名。例如, 一台机器主机名(hostname)是www, 域后缀(domain)是example.com, 那么该主机的FQDN应该是www.example.com.。其实FQDN最后是以”.”来结尾的, 但是大部分的应用和服务器都允许忽略最后这个点。

Linux下用户可以通过hostname命令查看并设置主机名. 用户也可以通过

hostname -f

命令得到该主机的FQDN。

java文档中的说法是:

java.net.preferIPv4Stack (default: false)
If IPv6 is available on the operating system, the underlying native socket will be an IPv6 socket. This allows Java(tm) applications to connect too, and accept … --> 阅读全文

java多线程编程之Future/FutureTask和Callable

有这样一种场景,用多线程发送数据到某个服务器,需要知道各个线程是否都发送成功,等所有线程都发送完成才能继续下一轮计算和发送。如果用传统的多线程方式,就需要启动多个线程,然后在每个线程中分别发送数据,外部通过某种方式等待各个线程全部都发送完成,再进行后面的计算等流程。这种实现方式的代码会比较臃肿,在java中提供了一种Callable+Future的方法,可以将异步的多线程调用变为同步方式。

Callable
在java的多线程编程中,有Thread和Runnable两种方式来新建线程,其中Runnable封装了一个异步运行的任务,可以认为是一个没有任何参数和返回值的异步方法。Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的,不同之处在于:

Runnable不会返回结果,并且无法抛出经过检查的异常。而Callable是有返回结果并且可能抛出异常的。
Runnable定义了run方法,而Callable定义了一个不带任何参数的叫做call的方法。
此外,Callable接口的类型参数也是返回值的类型。

Future
Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。

当使用Future对象时,你就可以启动一个计算,把计算结果给某线程,然后就去干自己的事。Future对象的所有者在结果计算好之后就可以得到它。

FutureTask
FutureTask包装器是一种很方便地将Callable转换为Future和Runnable的机制,它同时实现了两者的接口。例如:

Integer result = t.get(); // it’s a Future
所以可使用FutureTask包装Callable或Runnable对象。由于FutureTask实现了Runnable,可将 FutureTask提交给线程池的执行器类Executor执行。

使用Future+Callable+ExecutorService的例子

可见,使用Future+Callable模式对于某些需要等待多个线程执行结果的场景非常有用,在HBase的batch操作中就使用了这种模式。