Real-time Specification for Java (RTSJ) fornece extensões para facilitar o desenvolvimento de aplicativos em tempo real (RT) usando a linguagem Java. Uma extensão desse tipo é um mecanismo para aplicativos para gerenciar áreas de memória fora do heap e para evitar atrasos que são causados por coleta de lixo (GC). Essas áreas de memória são referidas como memória com escopo definido e imortal . Para assegurar a integridade do heap e da área da memória imortal, RTSJ define regras para usar a memória com escopo definido. Essas regras controlam os relacionamentos permitidos entre escopos e as referências permitidas de um objeto em um escopo para objetos em outras áreas de memória. As regras permitem que escopos sejam usados em uma ampla gama de padrões e aplicativos de design, mas essa flexibilidade vem com o custo de complexidade. Como resultado, alguns desenvolvedores acham o uso de escopos difícil e vulnerável a erros. Este artigo apresenta uma técnica que pode ser usada para simplificar o uso da memória com escopo definido.
O padrão Lifecycle Memory Managed Periodic Worker Threads
Um requisito comum em um sistema RT é um conjunto de encadeamentos que são executados periodicamente e cooperam para concluir algumas tarefas. RTSJ inclui mecanismos específicos para executar encadeamentos de maneira periódica. O padrão Lifecycle Memory Managed Periodic Worker Threads (LMMPWT) tem como objetivo fornecer um modelo de gerenciamento de memória simplificado que é adequado para uma faixa de casos de uso em que encadeamentos periódicos não devem ser interrompidos por GC.
Para ser aplicável a aplicativos do mundo real, o padrão LMMPWT deve atender pelo menos os seguintes requisitos:
- Acomodar um ou mais encadeamentos trabalhando juntos para realizar uma tarefa.
- Fornecer execução sem interrupção por GC.
- Fornecer um mecanismo para que os encadeamentos em cooperação se comuniquem.
- Fornecer gerenciamento de ciclo de vida para objetos.
Modelo de gerenciamento de memória
O padrão LMMPWT restringe os tempos de vida dos objetos disponíveis àqueles mais úteis para um encadeamento periódico, enquanto minimiza o tempo de vida dos objetos e o total de memória necessário. Os tempos de vida de objetos suportados são:
- Retain Forever: Objetos com esse tempo de vida nunca
são coletados como lixo e estão acessíveis para todos os
encadeamentos.
- Retain Thread Group: Objetos com esse tempo de vida
estão disponíveis enquanto qualquer um dos encadeamentos de
um grupo de encadeamentos em cooperação não tiver sido
finalizado. Esses objetos estão acessíveis somente por
encadeamentos dentro do grupo de encadeamentos.
- Retain Thread: Objetos com esse tempo de vida estão
disponíveis somente enquanto um encadeamento específico está
em execução e estão acessíveis somente para esse
encadeamento.
- Retain Iteration: Objetos com esse tempo de vida estão disponíveis enquanto um encadeamento está executando uma iteração específica de sua tarefa. Esses objetos estão acessíveis para esse encadeamento somente durante a iteração na qual são criados.
Esse modelo de gerenciamento de memória se ajusta ao ciclo de vida de um conjunto típico de encadeamentos periódicos em cooperação para concluir um conjunto de tarefas. A lógica para cada encadeamento segue o modelo executar>esperar pelo próximo período>executar>esperar pelo próximo período, etc. Durante cada execução (ou, como irei referir a ela agora, iteração), o encadeamento pode alocar diversos objetos. Alguns objetos são necessários somente durante essa própria iteração, alguns são necessários em iterações subsequentes, alguns podem precisar ser compartilhados com outros encadeamentos no grupo de encadeamentos em cooperação e alguns podem precisar ser compartilhados fora desse grupo. O modelo suporta esses requisitos permitindo que o desenvolvedor crie objetos com o tempo de vida apropriado.
Esse modelo simplifica o design do aplicativo. Em vez de precisar considerar quais escopos são necessários, você simplesmente decide quanto tempo o objeto deve ser retido. Isso limita a granularidade de gerenciamento de memória, mas, na maioria dos casos, a simplicidade, a sustentabilidade e a facilidade de desenvolvimento superam as vantagens de um controle mais fino. Espera-se que a maioria dos objetos necessários para os aplicativos será alocada com um tempo de vida Retain Iteration, indicando que serão liberados na conclusão de cada iteração.
Para manter a integridade de objetos com diferentes tempos de vida, você deve seguir uma regra ao fazer referência a objetos: um objeto não pode fazer referência a outro objeto com um tempo de vida mais curto. Isso e consistente com as regras para escopos definidas em RTSJ e o cumprimento pode ser realizado pela implementação do escopo subjacente. A regra está resumida na Tabela 1:
Tabela 1. Regra para fazer referência a objetos com tempos de vida diferentes
| Tempo de vida de objeto que contém referência | Referência permitida a objeto com Retain Forever? | Referência permitida a objeto com Retain Thread Group? | Referência permitida a objeto com Retain Thread? | Referência permitida a objeto com Retain Iteration? |
|---|---|---|---|---|
| Retain Forever | Sim | Não | Não | Não |
| Retain Thread Group | Sim | Sim | Não | Não |
Retain Thread | Sim | Sim | Sim | Não |
Retain Iteration | Sim | Sim | Sim | Sim |
Um elemento chave de qualquer modelo de gerenciamento de memória está controlando quando objetos são coletados e a memória é retornada ao sistema. A Tabela 2 descreve esse aspecto do modelo para cada um dos tempos de vida de objetos suportados:
Tabela 2. Quando objetos são coletados e memória é retornada para cada tempo de vida de objeto
| Tempo de vida de objeto | Quando objetos são coletados e memória é recuperada |
|---|---|
Retain Forever | Nunca. |
Retain Thread Group | Quando todos os encadeamentos de um grupo de encadeamentos tiverem sido finalizados, todos os objetos com esse tempo de vida são coletados e a memória alocada para esse grupo é retornada ao sistema. |
Retain Thread | Quando um encadeamento é finalizado, todos os objetos com esse tempo de vida são coletados e a memória alocada para esse encadeamento é retornada ao sistema. |
Retain Iteration | Quando a iteração atual para um encadeamento é finalizada, todos os objetos com esse tempo de vida são coletados. A área de memória permanece comprometida com o encadeamento para a próxima iteração. Quando o encadeamento concluir sua última iteração e for finalizado, a memória alocada para esse encadeamento é retornada ao sistema. |
Você deve ser capaz de configurar os tempos de vida para objetos alocados. Para minimizar o trabalho necessário por objeto, o padrão deve minimizar os objetos para os quais você deve especificar o tempo de vida do objeto. Retain Iteration para ser o mais adequado para o padrão, porque você deve reter objetos além de uma iteração somente se o aplicativo precisar disso. Usar Retain Iteration permite ignorar objetos temporários que o aplicativo cria e que são criados por métodos que ele chama. Para objetos que devem ser retidos, é necessário identificar e entender o requisito para esses objetos de qualquer forma, portanto, especificar seus tempos de vida será um pequeno incremento do trabalho já necessário.
No caso geral, você criar um objeto usando:
Object ref = new Object(); |
O ideal é poder usar o mesmo código com alguma marcação adicional, como neste exemplo:
/*[Lifetime=Retain Thread]*/ Object ref = new Object(); |
O melhor mecanismo pode depender de seu ambiente e poderia ser implementado usando-se, por exemplo, pré-processadores, anotações ou objetos auxiliares. O modelo não depende do mecanismo selecionado; no entanto, o mecanismo deve permitir especificar o tempo de vida sem precisar que você entenda escopos e deve evitar desordenação do código para evitar que a sustentabilidade seja afetada. Discutirei uma possível abordagem na seção Implementação usando escopos deste artigo.
Também é necessário poder armazenar referências a objetos de diferentes tempos de vida. Especificamente, você irá querer poder criar objetos com um tempo de vida Retain Thread ou Retain Thread Group em uma iteração e acessá-los em uma iteração subsequente. As regras para referências entre objetos com diferentes tempos de vida pode proibir que variáveis de membros no próprio objeto de encadeamento contenham referências a esses objetos. O modelo precisa incluir um mecanismo para passar objetos raízes a cada iteração que pode ser usada para armazenar e recuperar objetos que têm um tempo de vida Retain Thread Group ou Retain Thread.
É possível implementar o modelo proposto usando escopos RTSJ padrão e a área de memória imortal. Escopos são usados para suportarem objetos com os tempos de vida definidos, conforme indicado na Tabela 3:
Tabela 3. Área de memória usada para cada tempo de vida de objeto
| Tempo de vida de objeto | Área de memória usada |
|---|---|
Retain Forever | Imortal. |
Retain Thread Group | Escopo de nível superior alocado quando um grupo de encadeamentos é iniciado. |
Retain Thread | Escopo alocado para encadeamento quando o encadeamento é iniciado e isso permanece na pilha de escopo de encadeamentos até ser finalizado. |
Retain Iteration | Escopo alocado para encadeamento quando o encadeamento é alocado. É realizada entrada e saída do escopo para cada iteração para que seja liberado entre iterações. |
Para um encadeamento executando uma iteração, a pilha de escopo seria conforme mostrada na Figura 1:
Figura 1. Pilha de escopo
As classes de API de amostra a seguir demonstram o uso do modelo proposto usando essa implementação baseada em escopo:
PeriodicThreadPeriodicThreadGroupObjectRootsHelperPeriodicThreadDescriptorStartPeriodicThreads
PeriodicThread é a classe base a
partir da qual encadeamentos do trabalhador do conjunto de
encadeamentos em cooperação devem ser derivados. Gerencia os
escopos em segundo plano. Define as constantes a seguir, que são
usadas para especificar tempos de vida de objetos:
RETAIN_FOREVERRETAIN_THREAD_GROUPRETAIN_THREADRETAIN_ITERATION
O método de execução nessa classe é
final de forma que as classes
derivadas não podem usá-la para especificar sua lógica. Em vez
disso, você deve substituir o método a seguir para que contenha
a lógica para o encadeamento:
threadLogic(ObjectRootsHelper helper) |
Esse método é chamado toda vez que o encadeamento precisar executar a lógica para uma iteração.
Uma instância de ObjectRootsHelper é
passada para ajudar o encadeamento a gerenciar objetos cujos
tempos de vida excedem a iteração. Como o próprio encadeamento
pode ser alocado fora de um escopo (neste caso, o escopo para o
Thread Group), pode não ser possível usá-lo para armazenar
referências a objetos com determinados tempos de vida.
ObjectRootsHelper fornece os métodos
a seguir, que podem ser usados para armazenar e recuperar
referências a esses objetos:
void add(int lifetime, String key,Object obj)void addSynchronized(int lifetime,String key, Object obj)Object get(int lifetime, String key);Object getSynchronized(lifetime,String key);
ObjectRootsHelper suporta gerenciar
objetos com tempos de vida
RETAIN_THREAD_GROUP e
RETAIN_THREAD.
Como a lógica em threadLogic é chamada
para cada iteração, o encadeamento precisa de um mecanismo para
indicar que concluiu sua última iteração e deve ser finalizado.
O método setTerminate() é fornecido
de forma que o encadeamento possa indicar à estrutura quando
deseja finalizar.
PeriodicThread também inclui o método a seguir:
public Object allocate(final Class classObject,
int lifespan,
final Object ... parms)
throws IllegalArgumentException
|
O método allocate() permite alocar um
objeto com um determinado tempo de vida. Por exemplo, quando
nenhum parâmetro for requerido:
token = (WatchedThreadToken) allocate(WatchedThreadToken.class,
PeriodicThread.RETAIN_THREAD_GROUP);
|
Ou quando parâmetros forem requeridos:
watchedThreads = (WatchedThreads) allocate(WatchedThreads.class,
PeriodicThread.RETAIN_THREAD,
new PrimInt(10),
new PrimInt(3));
|
Observe a maneira como primitivas são passadas. Listas de
argumentos com comprimento variável requerem que você passe
com.ibm.realtime.easyrtj.prmitive.PrimXXX
em vez de a própria primitiva quando o construtor da classe
incluir parâmetros de primitiva
(long,
int,
float,
double,
short).
Se um objeto criado por um encadeamento precisar realizar
alocação internamente, você deve passar uma referência ao
próprio encadeamento ao objeto para que os métodos
allocate() possam ser chamados quando
necessário.
Um único construtor é fornecido para
PeriodicThread:
PeriodicThread(SchedulingParameters scheduling,
RelativeTime period,
Long threadMemorySize,
Long iterationMemorySize,
PeriodicThreadGroup group)
|
Esse construtor é público, porque
PeriodicThread deve ser
subclassificado. No entanto, não é esperado chamar o construtor
diretamente. As classes auxiliares
PeriodicThreadDescriptor e
StartPeriodicThreads são fornecidas
de forma que não seja necessário construir
PeriodicThreads diretamente.
A classe PeriodicThreadDescriptor
descreve um encadeamento para ser iniciado como parte de um
grupo. Ela fornece um único construtor público:
PeriodicThreadDescriptor(int priority,
Class classObject,
long threadMemorySize,
long iterationMemorySize,
long period)
|
Esse construtor permite especificar a prioridade, os tamanhos de
memória e o período para um encadeamento, juntamente com a
classe que representa o encadeamento a ser executado (uma classe
que estende PeriodicThread).
Você desenvolver um array de
PeriodicThreadDescriptors e passa os
mesmos ao método startThreads
estático de StartPeriodicThreads
juntamente com o tamanho da memória a ser compartilhada entre os
encadeamentos. startThreads iniciam
os encadeamentos conforme definido pelos descritores de forma
que compartilhem uma memória do tamanho indicado (ou seja,
compartilhem objetos com um tempo de vida
RETAIN_THREAD_GROUP).
Em suma, para usar as classes de amostra, você:
- Cria uma ou mais classes que subclassificam
PeriodicThread, colocando a lógica necessária no métodothreadLogic. - Cria um
PeriodicThreadDescriptorpara cada encadeamento a ser executado como um grupo. - Inicia os encadeamentos chamando
startThreads, passando o array de descritores.
Código de segurança de amostra
Um caso de uso de RT típico é ter um grupo de encadeamentos em cooperação para realizar um conjunto de tarefas e para usar uma segurança para assegurar que os encadeamentos continuem ativos e realizem suas tarefas. Cada encadeamento deve notificar periodicamente a segurança para indicar que ainda está ativo. Um encadeamento de monitoramento emite um alarme se qualquer encadeamento não se apresentar dentro de um intervalo de tempo determinado.
O código de segurança de amostra (consulte Download) define
WatchedPeriodicThread como uma
subclasse de PeriodicThread. O método
threadLogic dessa classe é
final e subclasses colocam a lógica a
ser executada no método watchedLogic
. ObjectRootsHelper passado para
threadLogic é passado para o método
watchedLogic . Além disso, as
subclasses devem implementar
getThreadName para retornar o nome
exclusivo a ser associado ao encadeamento.
Em threadLogic para
WatchedPeriodicThread, código é
incluído para criar um objeto
WatchedThreadToken com um tempo de
vida RETAIN_THREAD_GROUP. Ele é então
compartilhado com outros encadeamentos armazenando-o com
ObjectRootsHelper, com o nome do
encadeamento como a chave. Toda vez que
threadLogic é executado, executa o
método watchedLogic fornecido pela
subclasse e, em seguida, chama kick
em seu WatchedThreadToken para
indicar que o encadeamento ainda está ativo.
WatchDogThread aloca um objeto
WatchedThreads com um tempo de vida
RETAIN_THREAD e usa o mesmo para
controlar a atividade dos encadeamentos que está acompanhando.
Toda vez que threadLogic é chamado
para WatchDogThread, valida se os
encadeamentos que está acompanhando ainda estão ativos e emite
um alarme se qualquer encadeamento estiver inativo.
(Simplesmente usa printf, mas isso
pode facilmente enviar um trap SNMP, por exemplo.)
Thread1 é um encadeamento periódico
(estende WatchedPeriodicThread) que é
executado por 10 iterações e então fica inativo devido a uma
exceção "inesperada". Thread2 é um
encadeamento periódico simples (estende
WatchedPeriodicThread) que continua a
ser executado para sempre.
A classe TestMain usa
StartPeriodicThreads.startThreads
para iniciar Thread1,
Thread2 e
WatchDogThread. Executar o programa
de teste mostra que WatchDogThread
começa a monitorar Thread1 e
Thread2 e, em seguida, emite um
alarme quando Thread1 fica inativo.
Esta amostra demonstra o uso do modelo com objetos que têm tempos
de vida RETAIN_THREAD_GROUP,
RETAIN_THREAD e
RETAIN_INTERATION (o padrão).
O download fornece um arquivo JAR que inclui a origem e as classes compiladas para a implementação de amostra e o aplicativo de segurança. Para executar a amostra, use a linha de comando a seguir com uma máquina virtual RT:
java -cp LMMPWTSample.jar com.ibm.realtime.easyrtj.sample.TestMain |
Abordagens alternativas, como coletores de lixo RT, reduzem a necessidade de usar escopos, mas é provável que escopos ainda sejam necessários em alguns aplicativos. Este artigo apresentou o padrão LMMPWT para simplificar uso de memória com escopo definido. Para demonstrar a viabilidade e utilidade do padrão, descreveu uma implementação de amostra usando escopos e forneceu um aplicativo de amostra mostrando o uso do modelo com objetos dos vários tempos de vida.
| Descrição | Nome | Tamanho | Método de download |
|---|---|---|---|
| Sample source code | j-rtj6.jar | 85KB | HTTP |
Informações sobre métodos de download
Aprender
- Java Virtual
Machine Specification: A segunda edição da especificação
da JVM está disponível para download.
- JSR 1: Real-time
Specification for Java: Você encontrará RTSJ no site
Java Community Process.
- "IBM® WebSphere® Real Time V1.0 delivers
predictable response times using Java standards": Leia o
anúncio de produto para o WebSphere Real Time.
- Zona de
tecnologia Java do developerWorks: Centenas de artigos
sobre cada aspecto da programação Java.
Obter produtos e tecnologias
- WebSphere Real Time: O WebSphere Real Time permite que
aplicativos dependentes de tempos de resposta precisos
aproveitem a tecnologia Java padrão sem sacrificarem
determinismo.
- Real-time Java technology: Visite esse site de pesquisa
IBM alphaWorks® para encontrar tecnologias de ponta para RT
Java.
Discutir
- Confira blogs do
developerWorks e participe da comunidade comunidade do developerWorks.

Michael Dawson formou-se pela University of Waterloo em 1989 como bacharel em engenharia de computação e, em 1991, na Queens University, obteve seu mestrado em engenharia elétrica, especializando-se em criptografia. Depois disso, passou diversos anos como consultor em segurança, desenvolvendo produtos em empresas que iam de empresas novas até a IBM. Teve funções de liderança em equipes de desenvolvimento de aplicativos de e-commerce e na entrega dos mesmos como serviços, incluindo serviços de comunicação EDI, processamento de cartão de crédito, leilões on-line, faturamento eletrônico e JVMs. As tecnologias usadas variavam de C/C++ a Java e plataformas e componentes Java EE através de uma variedade de sistemas operacionais. Michael entrou na IBM em 2006. Ele trabalha na equipe J9 JVM que implementa bibliotecas de classe Java e componentes principais de JVM.